默认的Rails 4项目生成器现在在控制器和模型下创建了"concerns"目录。我已经找到了一些关于如何使用路由关注点的解释,但是没有关于控制器或模型的内容。
我很确定这与社区中当前的"DCI趋势"有关,并希望尝试一下。
问题是,我应该如何使用这个功能,是否有惯例来定义命名/类层次结构以使其工作?我如何在模型或控制器中包含一个关注点?
默认的Rails 4项目生成器现在在控制器和模型下创建了"concerns"目录。我已经找到了一些关于如何使用路由关注点的解释,但是没有关于控制器或模型的内容。
我很确定这与社区中当前的"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
我一直在研究使用模型关注点来简化臃肿的模型,并减少模型代码冗余。以下是一些解释和例子:
假设有一个文章模型,一个事件模型和一个评论模型。一篇文章或事件可以有许多评论。而每个评论都属于某篇文章或某个事件。
传统上,这些模型可能长这样:
评论模型:
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都有相同的一部分代码。通过使用concerns,我们可以将这个共同的部分提取到一个独立的模块Commentable中。
为此,在app/models/concerns中创建一个commentable.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
考虑一个事件模型。一个事件有很多参与者和评论。
通常情况下,事件模型可能看起来像这样:
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
具有许多关联的模型往往会积累越来越多的代码,变得难以管理。Concerns 提供了一种将臃肿的模块变得更加模块化和易于理解的方法。
可以使用 Concerns 对上述模型进行重构,如下所示:
在 app/models/concerns/event 文件夹中创建 attendable.rb 和 commentable.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
可评论.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
现在使用Concerns,您的Event模型将被简化为
class Event < ActiveRecord::Base
include Commentable
include Attendable
end
* 在使用concerns时,建议选择基于“领域”而不是“技术”的分组。 基于领域的分组可以是“可评论的”,“可照片化的”,“可出席的”。 技术分组将意味着“ValidationMethods”,“FinderMethods”等
def self.my_class_method
),实例方法以及类作用域中的方法调用和指令。无需使用module ClassMethods
。 - A Fader Darklyadd_item
,那么你就会遇到麻烦。我记得当一些验证器停止工作时,我曾经认为Rails出了问题,但是有人在concern中实现了any?
。我提出了一个不同的解决方案:像另一种语言中的接口一样使用concern。它不是定义功能,而是定义对处理该功能的单独类实例的引用。然后你就有了更小、更整洁的类,每个类只做一件事... - A Fader Darkly这篇文章帮助我理解了concerns。
# app/models/trader.rb
class Trader
include Shared::Schedule
end
# app/models/concerns/shared/schedule.rb
module Shared::Schedule
extend ActiveSupport::Concern
...
end
我觉得这里的大部分例子展示了module
的威力,而不是ActiveSupport::Concern
如何为module
增加价值。
示例1:更易读的模块。
因此,如果没有concerns,一个典型的module
将是这样的。
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
你会发现实例方法、类方法和包含的块会更加整洁。Concerns 将会为你适当地注入它们。这是使用 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
,所以Host
类必须include Foo
(但等一下,为什么Host
想要了解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
模块本身上的类中。
在关注文件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