Ruby on Rails 设计模式(第一部分):简介和策略对象
这篇文章是 Ruby on Rails 设计模式系列文章的第一篇。
其他部分请见:
第二部分
什么是设计模式?
模式是问题/解决方案对的形式化表达,用于做出面向对象的设计决策。
简而言之,模式是一组通用规则,用于实现其他开发人员可以轻松重复的目标。
为什么设计模式很重要?
首先,模式的目的是将现有的设计知识整理成册,这样开发人员就不必不断地重复造轮子。
其次,设计模式使设计师之间的沟通更加高效。
未经测试的胖模型、控制器、助手和视图是一场技术灾难,如果你不测试你的代码,就很难发现使用模式的必要性。
那么,如何才能从设计模式中获益呢?运用以下概念:
- 隔离性:如果与数据库查询相关的逻辑是隔离的,那么你可以轻松地使用存根测试。同样的规则也适用于索引或第三方代码等。
- 可读性:仅通过阅读类名就可以知道给定的代码大致在做什么。
- 可扩展性:可以轻松修改现有代码,很多地方不需要改变逻辑。
- 单一职责:一个方法或一个类只需负责一项操作。
- 可测试性:感谢提到的好处,它变得更容易,因为我们只需要测试一小部分而不是大方法,连接到外部服务并同时执行业务逻辑。
策略对象
让我们从最简单的模式开始。策略对象是一种处理权限、角色和策略的模式,每次需要检查某事或某人是否有权执行某个操作时都可以使用它。像 pundit、Cancan 和 Cancancan 这样的工具都实现了这种模式。
命名约定
文件名通常带有_policy
后缀,类名以Policy
结尾
。方法名始终以?
字符结尾。
例如:PostsPolicy#web_section?
纯策略对象由以下简单规则定义:
- 返回必须是布尔值
- 逻辑必须简单
- 在方法内部,我们应该只调用传递的对象上的方法
例如:
class PostsPolicy
def initialize(post)
@post = post
end
def web_section?
active? && @post.section == ‘web’
end
def active?
@posts.where(active: true, pending: false)
end
end
策略对象的主要用途是简单地调用其他方法并使用数据进行比较。
它们是轻量级、简单的纯 Ruby 对象,用于管理整个项目的权限。
它们也易于测试,是复杂条件的完美替代方案。
复杂条件的一个例子:
class PostsController < ApplicationController
def create
if @blog.mode == ‘live’ && @blog.authors.size > 0
&& (current_user.role == ‘admin’
|| (current_user.role == ‘moderator’ && current_user.verified_email))
# create
end
end
end
上述条件检查很长、很丑陋、难以阅读,可以应用策略对象模式。
让我们开始创建PostsCreationPolicy
class PostsCreationPolicy
attr_reader :user, :blog
def initialize(user, blog)
@user = user
@blog = blog
end
def self.create?(user, blog)
new(user, blog).create?
end
def create?
blog_with_authors? && author_is_allowed?
end
private
def blog_with_authors?
blog.mode == ‘live’ && blog.authors.size > 0
end
def author_is_allowed?
is_admin? || moderator_is_verified?
end
def is_admin?
user.role == ‘admin’
end
def moderator_is_verified?
user.role == ‘moderator` && user.verified_email
end
end
我们的带有策略对象的控制器如下所示:
class PostsController < ApplicationController
def create
if PostsCreationPolicy.create?(current_user, @blog)
#create
end
end
end
模型中的策略
重构复杂查询的另一种方法是在模型类上创建小型策略方法:
class User < ActiveRecord::Base
def is_admin?
role == ‘admin’
end
def is_moderator?
role == ‘moderator’
end
def is_authorable?
return true if is_admin?
is_moderator? && verified_email
end
end
class Blog < ActiveRecord::Base
def live?
mode == ‘live’
end
def any_authors?
authors.any?
end
def publishable?
live? && any_authors?
end
end
现在我们可以重构我们的控制器:
class PostsController < ApplicationController
def create
if @blog.publishable? && current_user.is_authorable?
# create
end
end
end
正如您所见,这是一种编写相同逻辑的更小且更易读的方式。
结论
策略模式是一个小概念,却能带来大效果。每次处理复杂条件时,请考虑应用策略对象。
使用 RSpec 进行测试时,您无需使用数据库记录,您的策略是纯 Ruby 对象,并且测试速度会更快。