Answers:
所以我自己发现了。这实际上是一个非常简单但功能强大的概念。如以下示例所示,它与代码重用有关。基本上,这个想法是提取公共的和/或上下文特定的代码块,以清理模型并避免它们变得过于繁琐和混乱。
例如,我将放置一个众所周知的模式,即可标记模式:
# 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对此进行了很好的解释:
在Rails 4中,我们将邀请程序员对默认的app / models / concerns和app / controllers / concerns目录使用关注点,这些目录自动成为加载路径的一部分。与ActiveSupport :: Concern包装器一起使用,它足以使此轻量级分解机制发挥作用。
我一直在阅读有关使用模型关注点对脂肪模型进行皮肤化以及干燥模型代码的内容。以下是带有示例的说明:
考虑文章模型,事件模型和评论模型。文章或事件有很多评论。评论属于文章或事件。
传统上,模型可能如下所示:
评论模型:
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
我们可以注意到,事件和文章都有大量的通用代码。使用关注点,我们可以在一个单独的模块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
具有许多关联的模型,否则具有积累越来越多的代码并变得难以管理的趋势。关注提供了一种使脂肪模块皮肤化的方法,使它们更加模块化且易于理解。
可以使用以下关注点来重构上述模型:创建一个attendable.rb
和commentable.rb
在app / models / concerns / event文件夹中文件。
Attenable.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
*在使用关注点时,建议您进行基于“域”的分组而不是“技术”分组。基于域的分组就像“ Commentable”,“ Photoable”,“ Attendable”。技术分组将表示“ ValidationMethods”,“ FinderMethods”等
def self.my_class_method
),实例方法以及类范围内的方法调用和指令。无需module ClassMethods
add_item
,例如,如果两个问题都实现了,那就太麻烦了。我记得当某些验证器停止工作时,我以为Rails坏了,但是有人any?
担心地实施了。我提出了一个不同的解决方案:像使用其他语言的界面一样使用关注点。它没有定义功能,而是定义了对处理该功能的单独类实例的引用。然后,您会有较小的,更整洁的类来做一件事……
值得一提的是,使用顾虑被许多人视为坏主意。
原因如下:
include
,有一个完整的依赖项处理系统-对于琐碎的旧Ruby mixin模式而言,这太复杂了。担心是射杀自己腿部的简单方法,请谨慎对待。
这篇文章帮助我理解了担忧。
# 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:更具可读性的模块。
因此,不用担心这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
您会看到实例方法,类方法和所包含的块变得不太混乱。顾虑将为您适当注入。这是使用的优势之一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
现在看起来很简单。
如果您在考虑为什么我们不能Foo
在Bar
模块本身中添加依赖项?那是行不通的,因为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