Rails active_support/concern.rb 是否有点多此一举?

bjmark · November 16, 2013 · Last by hooopo replied at November 17, 2013 · 3326 hits

下面是源码中对问题的描述及利用 concern 的解决方式

module ActiveSupport
  # A typical module looks like this:
  #
  #   module M
  #     def self.included(base)
  #       base.extend ClassMethods
  #       base.class_eval do
  #         scope :disabled, -> { where(disabled: true) }
  #       end
  #     end
  #
  #     module ClassMethods
  #       ...
  #     end
  #   end
  #
  # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
  # written as:
  #
  #   require 'active_support/concern'
  #
  #   module M
  #     extend ActiveSupport::Concern
  #
  #     included do
  #       scope :disabled, -> { where(disabled: true) }
  #     end
  #
  #     module ClassMethods
  #       ...
  #     end
  #   end
  #
  # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
  # and a +Bar+ module which depends on the former, we would typically write the
  # following:
  #
  #   module Foo
  #     def self.included(base)
  #       base.class_eval do
  #         def self.method_injected_by_foo
  #           ...
  #         end
  #       end
  #     end
  #   end
  #
  #   module Bar
  #     def self.included(base)
  #       base.method_injected_by_foo
  #     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
  #
  # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
  # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
  #
  #   module Bar
  #     include Foo
  #     def self.included(base)
  #       base.method_injected_by_foo
  #     end
  #   end
  #
  #   class Host
  #     include Bar
  #   end
  #
  # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
  # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
  # module dependencies are properly resolved:
  #
  #   require 'active_support/concern'
  #
  #   module Foo
  #     extend ActiveSupport::Concern
  #     included do
  #       def self.method_injected_by_foo
  #         ...
  #       end
  #     end
  #   end
  #
  #   module Bar
  #     extend ActiveSupport::Concern
  #     include Foo
  #
  #     included do
  #       self.method_injected_by_foo
  #     end
  #   end
  #
  #   class Host
  #     include Bar # works, Bar takes care now of its dependencies
  #   end

首先我认为这根本不是个问题。 Bar 依赖于 Foo,那么先 include Foo,再 include Bar,通过代码说出来把事情说清楚,简单明了,易于理解。

其次,如果不想 include Foo,只想 include Bar,下面的方法是不是更简单些呢?

module Foo
  def self.included(base)
    base.class_eval do
      def self.method_injected_by_foo
        puts "method_injected_by_foo"
      end
    end
  end
end

module Bar
  def self.included(base)
    base.send(:include, Foo)
    base.method_injected_by_foo
  end
end

class A
  #include Foo
  include Bar
end

1,你觉得直接 include ActiveRecord::Base 简单,还是按照 Readme 去 include 一大堆 module 简单?

2,这样写如果 include 层级太多说不定会 SystemStackError,而且其实和 Concern 比没少多少阿,Concern 还是利用 ruby 的惯用语法,更容易理解,写法看起来更舒服

Rails = 别人写的代码

我觉得他这样写让这种用法自然多了啊,是不错的 tip

作者的本意我想更多的是希望形成某种开发规约

@everett ,我想也是。 但总觉的这里有点过度包装。过度包装的特征是当时看着很得意,2 周后再看,自己也看不懂了。

如果你的 USB 有两个插口,说明书还要求你先插 A 再插 B 才可以用,你会用么? 如果哪个插件的文档这样写“使用方法是 include A,B,C…Z,注意顺序不能乱哈”一定有人觉得哪里不对了… 哪一天 B C D 中某一个模块去掉了或改名字或加模块了,插件作者又要通知使用者:“把那个模块去掉,要不然程序不能运行了”

本来是兼容的修改变成了不兼容了,why.

至于你最后说的那种情况,自己想怎么整就怎么整啦,又不是 concern 使用的场景…

You need to Sign in before reply, if you don't have an account, please Sign up first.