如何在Rails 4中使用关注点

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

我非常确定这与社区现有的“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中,我们将邀请程序员使用默认的应用/模型/关注点和应用/控制器/关注目录,这些目录自动成为加载路径的一部分。 再加上ActiveSupport :: Concern包装器,这个支持就足以让这个轻量级因式分解机制变得光彩照人。

我一直在阅读有关使用模型的问题来皮肤化肥胖模型,以及干你的模型代码。 这里是一个例子的解释:

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都有一个共同的重要代码。 使用关注点,我们可以在一个单独的模块Commentable中提取这个通用代码。

为此,在app / models /关键字中创建一个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.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 

而现在使用关注,你的事件模型减少到

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

*在使用问题时,建议采用基于“域”的分组而不是“技术”分组。 基于域的分组类似于“可评论的”,“可照相的”,“可参与的”。 技术分组将意味着“验证方法”,“FinderMethods”等

值得一提的是,使用问题被许多人认为是坏主意。

  1. 像这个家伙
  2. 和这个

一些原因:

  1. 在幕后发生了一些黑暗的魔法 – 关注是修补include方法,有一个完整的依赖处理系统 – 太复杂的东西,这是微不足道的旧的Ruby混合模式。
  2. 你的班级不会干燥。 如果你在各个模块中填充50个公共方法并包含它们,你的类仍然有50个公共方法,只是你隐藏了代码的味道,把垃圾放在抽屉里。
  3. 实际上,Codebase实际上很难驾驭所有这些问题。
  4. 你确定你的团队中的所有成员都有相同的理解什么应该真正的替代关注?

担心是很容易的方法来打击自己的腿,小心他们。

这个职位帮助我理解关切。

 # 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 

在这个例子中, BarHost真正需要的模块。 但是因为BarFoo有依赖关系,所以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 

现在看起来很简单。

如果你在想为什么不能在Bar模块本身中添加Foo依赖? 这不会工作,因为method_injected_by_foo_to_host_klass必须注入类包括Bar不在Bar模块本身。

来源: Rails的ActiveSupport ::关注

在考虑使文件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 

之后包括在你的模型中这样的:

 class Role < ActiveRecord::Base include TestConcern end 
Interesting Posts