缺省的Rails 4项目生成器现在在控制器和模型下创建目录“关注点”。我找到了一些关于如何使用路由关注点的解释,但没有关于控制器或模型的解释。
我很确定这与当前社区中的“DCI趋势”有关,我想尝试一下。
问题是,我应该如何使用这个功能,为了使它工作,如何定义命名/类层次结构是否有一个约定?如何在模型或控制器中包含关注点?
缺省的Rails 4项目生成器现在在控制器和模型下创建目录“关注点”。我找到了一些关于如何使用路由关注点的解释,但没有关于控制器或模型的解释。
我很确定这与当前社区中的“DCI趋势”有关,我想尝试一下。
问题是,我应该如何使用这个功能,为了使它工作,如何定义命名/类层次结构是否有一个约定?如何在模型或控制器中包含关注点?
所以我自己发现了。这实际上是一个非常简单但强大的概念。它与代码重用有关,如下面的例子所示。基本上,这个想法是提取公共和/或特定于上下文的代码块,以清理模型,避免它们变得过于臃肿和混乱。
作为一个例子,我将给出一个众所周知的模式,即可标记模式:
# app/models/product.rb
class Product
include Taggable
...
end
# app/models/concerns/taggable.rb
# notice that the file name has to match the module name
# (applying Rails conventions for autoloading)
module Taggable
extend ActiveSupport::Concern
included do
has_many :taggings, as: :taggable
has_many :tags, through: :taggings
class_attribute :tag_limit
end
def tags_string
tags.map(&:name).join(', ')
end
def tags_string=(tag_string)
tag_names = tag_string.to_s.split(', ')
tag_names.each do |tag_name|
tags.build(name: tag_name)
end
end
# methods defined here are going to extend the class, not the instance of it
module ClassMethods
def tag_limit(value)
self.tag_limit_value = value
end
end
end
因此,按照Product示例,您可以将Taggable添加到您想要的任何类中,并共享其功能。
DHH很好地解释了这一点:
在Rails 4中,我们将邀请程序员使用关注点 默认的app/models/concerns和app/controllers/concerns目录 自动成为加载路径的一部分。连同 关注点包装器,它的支持足以做到这一点 轻量级保理机制大放异彩。
这篇文章帮助我理解了人们的担忧。
# app/models/trader.rb
class Trader
include Shared::Schedule
end
# app/models/concerns/shared/schedule.rb
module Shared::Schedule
extend ActiveSupport::Concern
...
end
我一直在阅读关于使用模型关注皮肤脂肪模型以及DRY你的模型代码。下面是一个例子的解释:
1)耗尽模型代码
考虑文章模型、事件模型和评论模型。一篇文章或一件事有很多评论。评论属于Article或Event。
传统上,模型可能是这样的:
评价模型:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
文章模型:
class Article < ActiveRecord::Base
has_many :comments, as: :commentable
def find_first_comment
comments.first(created_at DESC)
end
def self.least_commented
#return the article with least number of comments
end
end
事件模型
class Event < ActiveRecord::Base
has_many :comments, as: :commentable
def find_first_comment
comments.first(created_at DESC)
end
def self.least_commented
#returns the event with least number of comments
end
end
我们可以注意到,Event和Article都有一段重要的代码。使用关注点,我们可以在一个单独的模块Commentable中提取这个公共代码。
为此,创建一个可注释对象。app/models/concerns中的Rb文件。
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments, as: :commentable
end
# for the given article/event returns the first comment
def find_first_comment
comments.first(created_at DESC)
end
module ClassMethods
def least_commented
#returns the article/event which has the least number of comments
end
end
end
现在你的模型是这样的:
评价模型:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
文章模型:
class Article < ActiveRecord::Base
include Commentable
end
事件模型:
class Event < ActiveRecord::Base
include Commentable
end
2)长皮肤的胖模特。
考虑一个事件模型。一个活动有许多参与者和评论。
通常,事件模型看起来像这样
class Event < ActiveRecord::Base
has_many :comments
has_many :attenders
def find_first_comment
# for the given article/event returns the first comment
end
def find_comments_with_word(word)
# for the given event returns an array of comments which contain the given word
end
def self.least_commented
# finds the event which has the least number of comments
end
def self.most_attended
# returns the event with most number of attendes
end
def has_attendee(attendee_id)
# returns true if the event has the mentioned attendee
end
end
具有许多关联的模型往往会积累越来越多的代码,从而变得难以管理。关注点提供了一种简化脂肪模块的方法,使它们更加模块化且易于理解。
上面的模型可以使用下面的关注点进行重构: 创建一个可出席者。Rb和可注释的。app/models/concerns/event文件夹下的Rb文件
attendable.rb
module Attendable
extend ActiveSupport::Concern
included do
has_many :attenders
end
def has_attender(attender_id)
# returns true if the event has the mentioned attendee
end
module ClassMethods
def most_attended
# returns the event with most number of attendes
end
end
end
commentable.rb
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments
end
def find_first_comment
# for the given article/event returns the first comment
end
def find_comments_with_word(word)
# for the given event returns an array of comments which contain the given word
end
module ClassMethods
def least_commented
# finds the event which has the least number of comments
end
end
end
现在使用关注点,您的事件模型简化为
class Event < ActiveRecord::Base
include Commentable
include Attendable
end
*在使用关注点时,建议采用“基于领域”的分组,而不是“技术”分组。基于域的分组就像“可评论的”,“可拍照的”,“可出席的”。技术分组将意味着“ValidationMethods”,“FinderMethods”等
在关注点中,创建文件filename.rb
例如,我想在我的应用程序中,属性create_by存在更新值为1,updated_by为0
module TestConcern
extend ActiveSupport::Concern
def checkattributes
if self.has_attribute?(:created_by)
self.update_attributes(created_by: 1)
end
if self.has_attribute?(:updated_by)
self.update_attributes(updated_by: 0)
end
end
end
如果你想实际传递参数
included do
before_action only: [:create] do
blaablaa(options)
end
end
之后,在你的模型中包括如下内容:
class Role < ActiveRecord::Base
include TestConcern
end
值得一提的是,使用关注点被很多人认为是一个坏主意。
就像这个人 还有这个
一些原因:
There is some dark magic happening behind the scenes - Concern is patching include method, there is a whole dependency handling system - way too much complexity for something that's trivial good old Ruby mixin pattern. Your classes are no less dry. If you stuff 50 public methods in various modules and include them, your class still has 50 public methods, it's just that you hide that code smell, sort of put your garbage in the drawers. Codebase is actually harder to navigate with all those concerns around. Are you sure all members of your team have same understanding what should really substitute concern?
忧虑很容易让你伤到自己的腿,小心对待它们。
我觉得这里的大多数示例都展示了模块的强大功能,而不是ActiveSupport::Concern如何为模块增加价值。
例1:更多可读模块。
所以不用担心这是一个典型的模块。
module M
def self.included(base)
base.extend ClassMethods
base.class_eval do
scope :disabled, -> { where(disabled: true) }
end
end
def instance_method
...
end
module ClassMethods
...
end
end
在用ActiveSupport::Concern重构之后。
require 'active_support/concern'
module M
extend ActiveSupport::Concern
included do
scope :disabled, -> { where(disabled: true) }
end
class_methods do
...
end
def instance_method
...
end
end
你可以看到实例方法、类方法和包含的块不那么乱。关注点将为您适当地注入它们。这是使用ActiveSupport::Concern的一个优点。
例2:优雅地处理模块依赖关系。
module Foo
def self.included(base)
base.class_eval do
def self.method_injected_by_foo_to_host_klass
...
end
end
end
end
module Bar
def self.included(base)
base.method_injected_by_foo_to_host_klass
end
end
class Host
include Foo # We need to include this dependency for Bar
include Bar # Bar is the module that Host really needs
end
在本例中,Bar是Host真正需要的模块。但是由于Bar与Foo有依赖关系,主机类必须包括Foo(但是等等为什么主机想知道Foo?可以避免吗?)
所以Bar在它所到的任何地方都添加依赖项。这里的排序也很重要。这给庞大的代码库增加了很多复杂性和依赖性。
在用ActiveSupport::Concern重构之后
require 'active_support/concern'
module Foo
extend ActiveSupport::Concern
included do
def self.method_injected_by_foo_to_host_klass
...
end
end
end
module Bar
extend ActiveSupport::Concern
include Foo
included do
self.method_injected_by_foo_to_host_klass
end
end
class Host
include Bar # It works, now Bar takes care of its dependencies
end
现在看起来很简单。
如果你在想为什么我们不能在Bar模块中添加Foo依赖?这不会起作用,因为method_injected_by_foo_to_host_klass必须被注入到一个类中,包括Bar而不是Bar模块本身。
来源:Rails ActiveSupport::Concern