Ruby Ruby 能够 include 一个模块在另外某个模块之前或者之后吗?

ibachue · 2013年05月05日 · 最后由 zlx_star 回复于 2013年05月06日 · 5833 次阅读

Hi all, 这个问题我难以用一句话来讲清楚,就干脆上代码吧 我的问题是这样的:

# 假设现在有若干个模块 A B C D E ...

# 先给类F include进若干个模块
class F
  include A
  include B
  include C
end

# 此时看ancestors,可以发现,C B A按照include时相反顺序出现在了F的后面
F.ancestors # => [F, C, B, A, Object, PP::ObjectMixin, Kernel, BasicObject]

# 如果之后(在自定义的某个插件中,不能修改原来的代码)希望给F补充一个模块D
class F
  include D
end

# 那么此时D模块就会出现在F的后面,C B A的前面,意思是将按照这个顺序搜索方法
F.ancestors # => [F, D, C, B, A, Object, PP::ObjectMixin, Kernel, BasicObject]

# 但是此时我希望给F再补充一个模块E
class F
  ??? include E ???
end

# 然后希望在输出ancestors的时候得到这样一个结果,即E被include在了B和A之间的位置,该如何实现?
F.ancestors # => [F, D, C, B, E, A, Object, PP::ObjectMixin, Kernel, BasicObject]

也许把 E 这个模块 include 在 B 模块里会是个解决方案

module B
  include E
end

但是这样毫无疑问的会影响到所有 include B 这个模块的类,并不是我希望得到的效果。 当然肯定不能修改 class F 的源代码。 所以请教大家,对此有什么好的并且比较简单的解决方案吗?谢谢

实际情况是,include 这个关键字都很少用到。。。

#1 楼 @sevk 这个问题是个很理论很元编程的问题。。而且 include 这个关键字用得非常厉害

module ActionController
  class Base < Metal
    abstract!

    def self.without_modules(*modules)
      modules = modules.map do |m|
        m.is_a?(Symbol) ? ActionController.const_get(m) : m
      end

      MODULES - modules
    end

    MODULES = [
      AbstractController::Layouts,
      AbstractController::Translation,
      AbstractController::AssetPaths,

      Helpers,
      HideActions,
      UrlFor,
      Redirecting,
      Rendering,
      Renderers::All,
      ConditionalGet,
      RackDelegation,
      Caching,
      MimeResponds,
      ImplicitRender,

      Cookies,
      Flash,
      RequestForgeryProtection,
      ForceSSL,
      Streaming,
      DataStreaming,
      RecordIdentifier,
      HttpAuthentication::Basic::ControllerMethods,
      HttpAuthentication::Digest::ControllerMethods,
      HttpAuthentication::Token::ControllerMethods,

      # Before callbacks should also be executed the earliest as possible, so
      # also include them at the bottom.
      AbstractController::Callbacks,

      # Append rescue at the bottom to wrap as much as possible.
      Rescue,

      # Add instrumentations hooks at the bottom, to ensure they instrument
      # all the methods properly.
      Instrumentation,

      # Params wrapper should come before instrumentation so they are
      # properly showed in logs
      ParamsWrapper
    ]

    MODULES.each do |mod|
      include mod
    end

    # Rails 2.x compatibility
    include ActionController::Compatibility

    ActiveSupport.run_load_hooks(:action_controller, self)
  end
end

看看 ActionController::Base 的代码,几乎都是在 include 各种模块,本身根本没有写什么东西。。

#1 楼 @sevk 在看看 ActiveRecord::Base 的模块

include ActiveRecord::Persistence
extend ActiveModel::Naming
extend QueryCache::ClassMethods
extend ActiveSupport::Benchmarkable
extend ActiveSupport::DescendantsTracker

extend Querying
include ReadonlyAttributes
include ModelSchema
extend Translation
include Inheritance
include Scoping
extend DynamicMatchers
include Sanitization
include AttributeAssignment
include ActiveModel::Conversion
include Integration
include Validations
extend CounterCache
include Locking::Optimistic, Locking::Pessimistic
include AttributeMethods
include Callbacks, ActiveModel::Observing, Timestamp
include Associations
include IdentityMap
include ActiveModel::SecurePassword
extend Explain

# AutosaveAssociation needs to be included before Transactions, because we want
# #save_with_autosave_associations to be wrapped inside a transaction.
include AutosaveAssociation, NestedAttributes
include Aggregations, Transactions, Reflection, Serialization, Store

都是在 include 或者 extend,你怎么能说 include 很少用到呢?

extend ActiveSupport::Concern

我只知道 include 的时候会把那个 module 直接加到 class 的上部,所以产生了那个顺序,不过还不知道能不能去动态地调整顺序。。等待高手:)

哦,原来 include 这么给力。学习了。。。

module A
  def self.included(base)
    if base == F
      base.send :include, E
    end
  end
end

#7 楼 @cantin 确实是一种解决方案

#7 楼 @cantin 嗯 这个确实蛮可行的 但是要有一个前提就是这段代码的执行时间必须在 A 这个模块被 include 之前 不过这个前提应该不难实现的 Thx

PS: 下次如果见到 Matz 一定要问问他为什么 include 没有 before/after 选项 为什么不提供对 ancestors 直接的修改功能 还有上次说得为什么 ancestors 里不输出 metaclass

#9 楼 @iBachue 这个是 self.included 是 hook 方法,它在 A include 进来后就会执行。这也是为什么要在 A 写这个方法而不是在 B 写

按照 ActiveSupport::Concern 的解决方案 应该是都 include Concern 在 module 中解决顺序问题,然后 class 无需关心顺序

好吧..看到最后句话了.. 那就再加个专用的 module 维护顺序

#12 楼 @jjym 你不觉得太复杂了吗。。。。😓

#13 楼 @iBachue 比每次 include 到类里再维护要简单啊..

#14 楼 @jjym 你给个例子?

#14 楼 @jjym 你说的 concern 是为了解决 module 和 module 的依赖交给 module 自己去解决。但 @iBachue 的需求是 module 之间没有依赖关系,他只是想改变 ancestors 的顺序。

#15 楼 @iBachue 好吧..你不能改 F..那无解了..

不管怎么解决这个问题,对于项目来说任意改变 module 的顺序都有大风险。

Use the right tool to do the right work, guy!

Module#prepend?

我错了

需要 登录 后方可回复, 如果你还没有账号请 注册新账号