提问人:yagooar 提问时间:1/27/2013 最后编辑:Arslan Aliyagooar 更新时间:8/29/2023 访问量:247777
如何在 Rails 4 中使用关注点
How to use concerns in Rails 4
问:
默认的 Rails 4 项目生成器现在在控制器和模型下创建目录 “concerns”。我发现了一些关于如何使用路由问题的解释,但没有找到关于控制器或模型的解释。
我很确定这与社区中当前的“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对此有很好的解释:
在 Rails 4 中,我们将邀请程序员使用 默认 app/models/concerns 和 app/controllers/concerns 目录 它们自动成为加载路径的一部分。与 ActiveSupport::关注包装器,它足以支持使它 轻量级保理机构闪耀。
评论
这篇文章帮助我理解了担忧。
# app/models/trader.rb
class Trader
include Shared::Schedule
end
# app/models/concerns/shared/schedule.rb
module Shared::Schedule
extend ActiveSupport::Concern
...
end
评论
我一直在阅读有关使用模型关注点来对脂肪模型进行皮肤化以及干燥模型代码的文章。以下是带有示例的解释:
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.rb
commentable.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”等
评论
def self.my_class_method
module ClassMethods
add_item
any?
在关注使文件 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
值得一提的是,许多人认为使用关注点是个坏主意。
一些原因:
- 在幕后发生了一些黑暗的魔法 - 关注的是修补方法,有一个完整的依赖处理系统 - 对于一些微不足道的旧 Ruby 混合模式来说太复杂了。
include
- 你的课程同样枯燥。如果你在各种模块中塞满了 50 个公共方法并包含它们,你的类仍然有 50 个公共方法,只是你隐藏了代码的味道,有点把你的垃圾放在抽屉里。
- 代码库实际上更难驾驭所有这些问题。
- 你确定你的团队的所有成员都有相同的理解,什么应该真正取代关注吗?
担忧是向自己的腿开枪的简单方法,要小心。
评论
我觉得这里的大多数例子都展示了 的力量,而不是如何增加价值。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
来源: Rails ActiveSupport::Concern
评论