提问



默认的Rails 4项目生成器现在在控制器和模型下创建目录Concer。我已经找到了一些关于如何使用路由问题的解释,但没有关于控制器或模型的解释。


我很确定它与社区当前的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很好地解释了这一点:[44]



  在Rails 4中,我们将邀请程序员使用问题
  默认应用程序/模型/关注点和app/controllers/concerns目录
  它们自动成为加载路径的一部分。再加上
  ActiveSupport :: Concern包装器,它足以支持这一点
  轻量化因子机制闪耀。


其它参考1


我一直在阅读关于使用模型关注来修饰胖模型以及干掉模型代码。以下是对示例的解释:


1)干掉型号代码



考虑文章模型,事件模型和评论模型。文章或事件有很多评论。评论属于文章或事件。


传统上,模型可能如下所示:


评论模型:


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


我们可以注意到,事件和文章都有一个共同的重要代码。使用关注点,我们可以在单独的模块中提取此公共代码。


为此,在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


Activity模型:


class Event < ActiveRecord::Base
  include Commentable
end


2)皮肤肥胖模型。



考虑事件模型。一个Activity有很多参与者和评论。


通常,事件模型可能如下所示


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文件夹中创建attendable.rbcommentable.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


现在使用Concerns,您的事件模型减少到


class Event < ActiveRecord::Base
  include Commentable
  include Attendable
end


*在使用问题时,建议选择基于域的分组而不是技术分组。基于域的分组就像可评论,可照片,可以参加。技术分组将意味着ValidationMethods,FinderMethods等

其它参考2


值得一提的是,使用顾虑被很多人认为是坏主意。



  1. 喜欢这个家伙

  2. 和这一个



一些原因:[45] [46]



  1. 在幕后发生了一些黑暗魔法 - 关注是修补include方法,有一个完整的依赖处理系统 - 对于那些微不足道的老Ruby混合模式来说太复杂了。

  2. 你的课程也不干。如果你在各种模块中填充50个公共方法并包含它们,你的类仍然有50个公共方法,只是你隐藏了代码味道,有点把你的垃圾放在抽屉里。

  3. 代码库实际上更难以解决所有这些问题。

  4. 您确定您团队中的所有成员都有同样的理解,应该真正替代什么?



关注是轻松拍摄自己腿部的方法,小心他们。

其它参考3


这篇文章帮助我理解了问题。[47]


# app/models/trader.rb
class Trader
  include Shared::Schedule
end

# app/models/concerns/shared/schedule.rb
module Shared::Schedule
  extend ActiveSupport::Concern
  ...
end

其它参考4


我觉得这里的大多数例子都证明了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


在这个例子中BarHost真正需要的模块。但由于Bar依赖于FooHost类必须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模块本身的类中注入。


来源: Rails ActiveSupport ::关注[48]

其它参考5


关注make文件filename.rb


例如,我想在我的应用程序中,属性create_by存在,将值更新为1,更新为update_by


 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
 


之后包含在你的模型中:


 class Role < ActiveRecord::Base
  include TestConcern
end