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

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

问题是,我应该如何使用这个功能,为了使它工作,如何定义命名/类层次结构是否有一个约定?如何在模型或控制器中包含关注点?


当前回答

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

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

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

其他回答

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

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

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

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

作为一个例子,我将给出一个众所周知的模式,即可标记模式:

# 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目录 自动成为加载路径的一部分。连同 关注点包装器,它的支持足以做到这一点 轻量级保理机制大放异彩。

在关注点中,创建文件filename.rb

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

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

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

就像这个人 还有这个

一些原因:

There is some dark magic happening behind the scenes - Concern is patching include method, there is a whole dependency handling system - way too much complexity for something that's trivial good old Ruby mixin pattern. Your classes are no less dry. If you stuff 50 public methods in various modules and include them, your class still has 50 public methods, it's just that you hide that code smell, sort of put your garbage in the drawers. Codebase is actually harder to navigate with all those concerns around. Are you sure all members of your team have same understanding what should really substitute concern?

忧虑很容易让你伤到自己的腿,小心对待它们。

我觉得这里的大多数示例都展示了模块的强大功能,而不是ActiveSupport::Concern如何为模块增加价值。

例1:更多可读模块。

所以不用担心这是一个典型的模块。

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有依赖关系,主机类必须包括Foo(但是等等为什么主机想知道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::Concern