如何在 Rails 4 中使用关注点

How to use concerns in Rails 4

提问人:yagooar 提问时间:1/27/2013 最后编辑:Arslan Aliyagooar 更新时间:8/29/2023 访问量:247777

问:

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

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

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

Ruby-on-Rails Ruby-on-Rails-4 DCI

评论


答:

631赞 yagooar 2/26/2013 #1

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

举个例子,我将放置一个众所周知的模式,即可标记模式:

# 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对此有很好的解释:

在 Rails 4 中,我们将邀请程序员使用 默认 app/models/concerns 和 app/controllers/concerns 目录 它们自动成为加载路径的一部分。与 ActiveSupport::关注包装器,它足以支持使它 轻量级保理机构闪耀。

评论

11赞 ciscoheat 2/26/2013
DCI 处理 Context,使用角色作为标识符将心智模型/用例映射到代码,并且不需要使用包装器(方法在运行时直接绑定到对象),因此这与 DCI 无关。
2赞 Rune FS 2/26/2013
@yagooar即使在运行时包含它也不会使它成为 DCI。如果您希望看到 ruby DCI 示例实现。看看 fulloo.infogithub.com/runefs/Moby 的例子,或者如何使用栗色在Ruby中做DCI以及DCI runefs.com 是什么(DCI是什么,是我最近刚刚开始的一系列文章)
2赞 yagooar 3/11/2013
@RuneFS & CiscoHeat,你们都是对的。我只是再次分析了文章和事实。而且,上周末我参加了一个 Ruby 会议,其中有一场是关于 DCI 的演讲,最后我对它的理念有了更多的了解。更改了文本,因此根本没有提及 DCI。
9赞 febeling 9/17/2013
值得一提的是(可能包含在示例中),类方法应该在一个专门命名的模块 ClassMethods 中定义,并且该模块也由基类 ActiveSupport::Concern 扩展。
1赞 Ryan Crews 10/29/2013
谢谢你的这个例子,主要是 b/c 我很愚蠢,并在 ClassMethods 模块中使用 self.whatever 定义我的类级方法,这不起作用 =P
57赞 aminhotob 3/18/2013 #2

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

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

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

评论

4赞 10/10/2018
这个答案并不能解释任何事情。
388赞 Aaditi Jain 9/16/2014 #3

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

1) 更新型号代码

考虑 Article 模型、Event 模型和 Comment 模型。一篇文章或一个事件有很多评论。评论属于“文章”或“事件”。

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

注释模型:

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 都有一段重要的代码。使用关注点,我们可以将此通用代码提取到一个单独的模块 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

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 文件夹中创建一个 and 文件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

评论.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”等

评论

8赞 Chloe 9/6/2015
所以关注点只是一种使用继承或接口或多重继承的方式?创建一个公共基类并从该公共基类进行子类化有什么问题?
3赞 Ziyan Junaideen 11/7/2015
事实上,@Chloe,我有些红色,一个带有“关注”目录的 Rails 应用程序实际上是一个“关注”......
0赞 A Fader Darkly 11/26/2015
您可以使用“included”块来定义所有方法,并包含:类方法(with)、实例方法以及类范围内的方法调用和指令。无需def self.my_class_methodmodule ClassMethods
1赞 A Fader Darkly 11/26/2015
我担心的问题是它们直接向模型添加功能。因此,如果两个关注点都实现了,例如,你就搞砸了。我记得当一些验证者停止工作时,我以为 Rails 坏了,但有人在关注中实现了。我提出了一个不同的解决方案:像使用不同语言的界面一样使用关注点。它不是定义功能,而是定义对处理该功能的单独类实例的引用。然后你有更小、更整洁的班级,做一件事......add_itemany?
0赞 RahulOnRails 12/6/2016
@aaditi_jain:请更正小改动,以免误会。即“在 app/models/concerns/event 文件夹中创建一个 attendable.rd 和 commentable.rb 文件” --> attendable.rd 必须是 attendable.rb 谢谢
7赞 Sajjad Murtaza 1/15/2015 #4

在关注使文件 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
102赞 Dr.Strangelove 3/23/2015 #5

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

  1. 就像这个人一样
  2. 还有这个

一些原因:

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

担忧是向自己的腿开枪的简单方法,要小心。

评论

2赞 toobulkeh 7/23/2015
我知道 SO 不是讨论的最佳场所,但还有什么其他类型的 Ruby mixin 可以让你的课堂保持枯燥呢?似乎你的论点中的原因 #1 和 #2 是相反的,除非你只是为了更好的 OO 设计、服务层或我缺少的其他东西?(我不反对 - 我建议添加替代品会有所帮助!
3赞 Dr.Strangelove 8/4/2015
使用 github.com/AndyObtiva/super_module 是一种选择,使用良好的旧 ClassMethods 模式是另一种选择。使用更多的对象(如服务)来干净地分离关注点绝对是要走的路。
4赞 Adam 7/25/2018
投反对票,因为这不是问题的答案。这是一种意见。我确信这种观点有其优点,但它不应该是 StackOverflow 上问题的答案。
4赞 Dr.Strangelove 7/26/2018
@Adam 这是一个固执己见的答案。想象一下,有人会问如何在 rails 中使用全局变量,肯定会提到有更好的方法来做事(即 Redis.current 与 $redis)可能是主题初学者的有用信息?软件开发本质上是一门固执己见的学科,没有办法绕过它。事实上,我认为意见是答案和讨论,哪个答案在stackoverflow上是最好的,这是一件好事
2赞 Adam 7/27/2018
当然,在回答这个问题时提到它似乎没问题。不过,您的答案中没有任何内容实际上回答了 OP 的问题。如果你只想警告某人为什么他们不应该使用关注点或全局变量,那么这将是一个很好的评论,你可以将其添加到他们的问题中,但它并不是一个好的答案。
52赞 Siva 12/3/2015 #6

我觉得这里的大多数例子都展示了 的力量,而不是如何增加价值。moduleActiveSupport::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

在此示例中是真正需要的模块。但是既然有依赖类必须(但是等等,为什么想知道呢?可以避免吗?BarHostBarFooHostinclude 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

现在看起来很简单。

如果你在想为什么我们不能在模块本身中添加依赖关系?这是行不通的,因为必须注入到一个不包含在模块本身上的类中。FooBarmethod_injected_by_foo_to_host_klassBarBar

来源Rails ActiveSupport::Concern

评论

0赞 Dave Newton 4/3/2020
FWIW 这大致是从文档中复制粘贴的。