How to use concerns in Rails 4
默认的Rails4项目生成器现在在控制器和模型下创建目录"关注点"。我发现了一些关于如何使用路由问题的解释,但是没有关于控制器或模型的解释。
我非常肯定这与社区中当前的"DCI趋势"有关,我想尝试一下。
问题是,我应该如何使用这个特性,是否有一个关于如何定义命名/类层次结构以使其工作的约定?如何在模型或控制器中包含问题?
所以我自己发现了。它实际上是一个非常简单但功能强大的概念。它与代码重用有关,如下面的示例所示。基本上,这个想法是提取通用和/或特定于上下文的代码块,以便清理模型,避免它们变得太胖和混乱。
作为一个例子,我将放一个众所周知的模式,可标记模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | # 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 |
因此,在产品示例之后,您可以将Taggable添加到您想要的任何类中,并共享其功能。
DHH很好地解释了这一点:
In Rails 4, we’re going to invite programmers to use concerns with the
default app/models/concerns and app/controllers/concerns directories
that are automatically part of the load path. Together with the
ActiveSupport::Concern wrapper, it’s just enough support to make this
light-weight factoring mechanism shine.
号
我一直在读关于使用模型关注皮肤脂肪模型和干涸您的模型代码。下面是一个示例说明:
1)烘干型号代码考虑一个文章模型、一个事件模型和一个评论模型。一篇文章或一个事件有许多评论。注释属于文章或事件。
传统上,模型可能如下所示:
注释模型:
1 2 3 | class Comment < ActiveRecord::Base belongs_to :commentable, polymorphic: true end |
文章模型:
1 2 3 4 5 6 7 8 9 10 11 | 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 |
。
事件模型
1 2 3 4 5 6 7 8 9 10 11 | 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 |
正如我们可以注意到的,事件和文章都有一段重要的代码。使用关注点,我们可以在单独的可注释模块中提取此公共代码。
为此,在app/models/concerns中创建一个commentable.rb文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 |
。
现在你的模型是这样的:
注释模型:
1 2 3 | class Comment < ActiveRecord::Base belongs_to :commentable, polymorphic: true end |
文章模型:
1 2 3 | class Article < ActiveRecord::Base include Commentable end |
事件模型:
1 2 3 | class Event < ActiveRecord::Base include Commentable end |
。2)皮肤化脂肪模型。
考虑一个事件模型。一个活动有许多与会者和评论。
通常,事件模型可能如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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 |
具有许多关联和其他方面的模型趋向于积累越来越多的代码并变得不可管理。关注点提供了一种使脂肪模块更模块化和易于理解的方法。
可以使用以下关注点重构上述模型:在app/models/concerns/event文件夹中创建
可出席.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
号
现在使用关注点,您的事件模型减少到
1 2 3 4 | class Event < ActiveRecord::Base include Commentable include Attendable end |
号
*尽管使用关注点,但建议使用基于"域"的分组,而不是"技术"分组。基于域的分组类似于"可评论"、"可拍照"、"可出席"。技术分组是指"验证方法"、"finderMethods"等。
值得一提的是,使用关注点被许多人认为是坏主意。
一些原因:
担心是一种很容易让你自己陷入困境的方法,小心对待它们。
这篇文章帮助我理解了关注点。
1 2 3 4 5 6 7 8 9 10 | # app/models/trader.rb class Trader include Shared::Schedule end # app/models/concerns/shared/schedule.rb module Shared::Schedule extend ActiveSupport::Concern ... end |
号
我觉得这里的大多数例子都证明了
示例1:更可读的模块。
因此,不必担心典型的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
在使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
号
您可以看到实例方法、类方法和包含的块比较简单。关注点会为您适当地注入它们。这是使用
示例2:优雅地处理模块依赖项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
在这个例子中,
因此,
用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 |
。
现在看起来很简单。
如果你在想,为什么我们不能在
来源:rails activesupport::concern
考虑到这一点,将文件名设为.rb
例如,我希望在我的应用程序中,属性create_by exists将值更新为1,将值更新为0
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
号
之后,在您的模型中包括如下内容:
1 2 3 | class Role < ActiveRecord::Base include TestConcern end |
号