如何安全地从gem中包含模型实例方法

时间:2016-12-08 17:43:31

标签: ruby-on-rails ruby module

我正在开发一个gem,它为Rails模型添加了一个功能,目前具有以下结构:

# lib/my_gem/loader.rb

module MyGem
  module Loader
    def loader
      include I18nAttributes::InstanceMethods
    end
  end
end
ActiveRecord::Base.send :extend, MyGem::Loader
 
# lib/my_gem/instance_methods.rb

module MyGem
  module InstanceMethods
    def foo
      puts '-> foo from gem'
      bar                       # <-- HERE
    end

    def bar
      puts '-> bar from gem'
    end
  end
end

在Rails模型中,该功能现在添加了:

# app/models/model.rb

class Model < ActiveRecord::Base
  loader
end

这使模型实例可以访问gem中的实例方法:

irb(main):001:0> m = Model.new
irb(main):002:0> m.foo
-> foo from gem
-> bar from gem

但是,当模型实现它自己的bar

# app/models/model.rb

class Model < ActiveRecord::Base
  loader

  def bar
    puts '-> bar from model'
  end
end

模型中的bar方法带有阴影:

irb(main):001:0> m = Model.new
irb(main):002:0> m.foo
-> foo from gem
-> bar from model

以上都不是一个惊喜,但应避免碰撞方法的风险:

  • 上面的gem代码(标有bar)中是否有HERE - 行的替代方法,以确保模块中定义的bar - 方法甚至可以使用如果模型定义了它自己的bar版本?

  • 是否有更好的模式可以消除这种风险?

感谢您的提示!

更新

使用gem中的内部方法不污染模型实例的方法是嵌套类:

# lib/my_gem/instance_methods.rb

module MyGem
  module InstanceMethods
    class Inernal
      def bar
        puts '-> bar from gem'
      end
    end

    def foo
      puts '-> foo from gem'
      Internal.new.bar
    end
  end
end

在我的情况下会非常方便,因为嵌套类可以拥有它自己的一组实例变量。你怎么看,一种可接受的模式?

1 个答案:

答案 0 :(得分:0)

该模型能够覆盖您的方法,而且您无能为力。这是继承如何在Ruby中工作的基础。我不确定为什么即使有可能,你也会不顾一切地阻止这样的事情。

大多数宝石采取的方法是尽可能减少模块的占用空间。这通常意味着将您通常拥有的方法作为私有实例方法移植到另一个模块,这样它们就不会污染目标类:

module MyExtension
  module Stub
    def loaded
      include MyExtension::InstanceMethods
    end
  end

  module InstanceMethods
    def bar
      MyExtension::Support.format(:bar)
    end
  end

  module Support
    def self.format(text)
      "#{text} from module"
    end
  end
end

这意味着只有Stub中定义的方法才会在类级别导入。其余的按需加载。

扩展您的基类:

class ModelBase
end

ModelBase.send(:extend, MyExtension::Stub)

然后在派生类中使用它:

class MyModel < ModelBase
  loaded
end

MyModel.new.bar

仍然无法避免覆盖。