638

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

我很确定这与社区当前的“DCI趋势”有关,并想尝试一下。

问题是,我应该如何使用此功能,是否有关于如何定义命名/类层次结构以使其工作的约定?如何在模型或控制器中包含关注点?

4

6 回答 6

627

所以我自己发现了。它实际上是一个非常简单但功能强大的概念。它与代码重用有关,如下例所示。基本上,这个想法是提取常见和/或特定于上下文的代码块,以清理模型并避免它们变得过于臃肿和混乱。

作为示例,我将放置一个众所周知的模式,可标记模式:

# 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 包装器一起,它足以使这种轻量级分解机制大放异彩。

于 2013-02-25T22:50:30.447 回答
387

我一直在阅读有关使用模型问题对脂肪模型进行皮肤化以及干燥模型代码的文章。以下是带有示例的解释:

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

我们可以注意到,Event 和 Article 都有一段重要的代码。使用关注点,我们可以在单独的可注释模块中提取此公共代码。

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

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

具有许多关联的模型倾向于积累越来越多的代码并变得难以管理。关注点提供了一种对脂肪模块进行皮肤化的方法,使它们更加模块化和易于理解。

可以使用以下关注点重构上述模型:在 app/models/concerns/event 文件夹中创建一个attendable.rb和文件commentable.rb

可参加的.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

现在使用关注点,您的事件模型减少到

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

* 在使用关注点时,建议使用基于“域”的分组而不是“技术”分组。基于域的分组就像“可评论”、“可拍照”、“可参加”。技术分组将意味着“ValidationMethods”、“FinderMethods”等

于 2014-09-15T22:50:58.713 回答
102

值得一提的是,许多人认为使用关注点是个坏主意。

  1. 喜欢这个人
  2. 和这个

一些原因:

  1. 幕后发生了一些黑暗的魔法 - 关注的是修补include方法,有一个完整的依赖处理系统 - 对于琐碎的旧 Ruby 混合模式来说太复杂了。
  2. 你的课也同样枯燥。如果你在不同的模块中填充 50 个公共方法并包含它们,你的类仍然有 50 个公共方法,只是你隐藏了代码气味,有点把你的垃圾放在抽屉里。
  3. 代码库实际上更难处理所有这些问题。
  4. 你确定你的团队的所有成员都对真正应该用什么来代替关注有相同的理解吗?

担忧很容易让自己在腿上开枪,小心它们。

于 2015-03-23T14:11:17.293 回答
57

这篇文章帮助我理解了担忧。

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

# app/models/concerns/shared/schedule.rb
module Shared::Schedule
  extend ActiveSupport::Concern
  ...
end
于 2013-03-17T19:20:20.663 回答
50

我觉得这里的大多数示例都module展示ActiveSupport::Concernmodule.

示例 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依赖关系(但是等等为什么想知道?可以避免吗?)。FooHostinclude FooHostFoo

所以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模块本身的类。

资料来源: Rails ActiveSupport::Concern

于 2015-12-03T09:11:42.950 回答
7

在关注制作文件filename.rb

例如,我想在我的应用程序中存在属性 create_by 将值更新为 1,更新为 0 为 updated_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

如果你想在行动中传递参数

included do
   before_action only: [:create] do
     blaablaa(options)
   end
end

之后像这样包含在您的模型中:

class Role < ActiveRecord::Base
  include TestConcern
end
于 2015-01-15T08:45:04.500 回答