Gem 请问怎么能覆盖 Gem 里的方法

birbird · 2015年08月08日 · 最后由 lazing 回复于 2015年08月10日 · 1749 次阅读

我想改变 Gem 的行为,需要重写 Gem 的一个方法,重写的方法需要用到原来的实现,改变其中的一部分。

Gem 的代码

module CarrierWave
  module Mongoid
    include CarrierWave::Mount

    def mount_uploader(column, uploader=nil, options={}, &block)
      field options[:mount_on] || column

      super

      ...

      class_eval <<-RUBY, __FILE__, __LINE__+1
        # 我要重写的方法,
        def #{column}=(new_file)
          column = _mounter(:#{column}).serialization_column
          # mongoid won't upload a new file if there was no file previously.
          write_uploader(column, '_old_') if self.persisted? && read_uploader(column).nil?
          send(:"\#{column}_will_change!")
          super
        end

        ... 很多其他的方法,维持不变

      RUBY
    end
  end # Mongoid
end # CarrierWave

我写的代码

module CarrierWave
  module MongoidExtensions
    def mount_uploader(column, uploader=nil, options={}, &block)
      super

      puts "New mount_uploader"
      class_eval <<-RUBY, __FILE__, __LINE__+1
        def #{column}=(cache_name)
          puts "重写了"
        end
      RUBY
    end
  end

  module Mongoid
    prepend MongoidExtensions
  end
end

但我的代码看起来没起作用,给我的感觉是 prepend 的 mount_uploader 完全没有生效,因为 puts "New mount_uploader" 都没有出来。 请问应该怎么搞? 谢啦先!

共收到 9 条回复

你重写的部分仅是方法 mount_upload 的定义,问题他在你重写前已经调用完了。你可以直接一点,去model那边直接覆盖 那个column的set方法

#1楼 @serco 谢谢回复,去 model 里直接覆盖确实能调到,但这样所有 model 里就都要加这种重复的代码了。mount_uploader 本来就是用来解决这个问题的,他给 model 加上了一堆方法。请问还有其他方法么,改变 mount_uploader 的行为的。

还有,我不明白你说的「问题他在你重写前已经调用完了」,我这代码是放在 config\initializers 里的,他肯定先于调用 mount_uploader 的地方,为啥不生效呢?

放到config/initializers/下面

#3楼 @zhang_soledad 我就是放在 config/initializers/ 里面的

@birbird,可以直接monkey patch,另外,找找有没有gem已经做了这部分工作,如果这个问题很普遍的话

覆盖的时候这么写行不行? if is_a? Xxx 覆盖 else 原方法 end

Monkey patch or you can sent a pull request to gem author.

#2楼 @birbird gem 里这行执行早于你写的prepend Mongoid::Document::ClassMethods.send(:include, CarrierWave::Mongoid)

正确方式是在 initializer 里 Mongoid::Document::ClassMethods.send(:include, YOUR_MODULE_NAME)

最好的选择,fork 一个自己版本,在项目中引用自己的库。成熟的话可以推送到原有项目。

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