简体   繁体   English

Ruby on Rails Monkey修补Gem的模型

[英]Ruby on Rails Monkey Patching a Gem's Model

This might be silly, but I'm including a gem which represents all the models I need for my project. 这可能很愚蠢,但我要包含一个代表我项目所需模型的宝石。 I want to add a method, to_custom_string to one of those models, Person . 我想在其中一个模型Person添加一个方法to_custom_string

I was attempting to do this via (following this example): config/initializers/extensions/person.rb 我试图这样做(按照这个例子): config/initializers/extensions/person.rb

Which contained something like: 其中包含的内容如下:

class Person < ActiveRecord::Base
  def to_custom_string
    address.street.to_s
  end
end

The Person class in the gem has a has_one :address association. gem中的Person类具有has_one :address关联。

The problem I was experiencing was that this patch seems to override the Person class from the gem, instead of patching it. 我遇到的问题是这个补丁似乎覆盖了gem中的Person类,而不是修补它。 What's crazy is that this override behavior was only experienced via rake (all of the associations declared in the Person class from the gem are lost). 令人抓狂的是,这种覆盖行为只能通过rake来体验(来自gem的Person类中声明的所有关联都会丢失)。

My rake task was something like: 我的佣金任务是这样的:

namespace :convert
  task :all_persons => :environment do
    Person.where(:param => value).includes(:address).find_in_batches(:batch_size => 2000) do |persons|
      persons.each do |person|
        puts person.to_custom_string
      end
    end
  end
end

calling bundle exec rake convert:all_persons gave me: 调用bundle exec rake convert:all_persons给了我:

Association named 'address' was not found; perhaps you misspelled it?

But copying and pasting the code in the rake task into rails console worked fine. 但是将rake任务中的代码复制并粘贴到rails控制台中工作正常。

My current solution is to copy the code for Person from the gem into my app/models directory, and have my to_custom_string method there, which I know is wrong. 我目前的解决方案是将Person的代码从gem复制到我的app/models目录中,并在那里使用我的to_custom_string方法,我知道这是错误的。

Can someone please explain why a) irb preserved my Person associations, but rake did not, and b) how I can get rake to cooperate? 有人可以解释为什么a)irb保留了我的Person协会,但是rake没有,和b)我怎么可以得到佣金合作?

Thank you! 谢谢!

First of all instead of reopening the class I would create a Module and include it into the Person. 首先,我将创建一个Module并将其包含在Person中,而不是重新打开该类。 So it would look like that 所以它看起来像那样

  module CustomString
    def to_custom_string
      address.street.to_s
    end
  end

  Person.send(:include, CustomString)

Also it seems like the Person model is not yet available at the point of running the initializer. 此外,似乎Person模型在运行初始化程序时尚不可用。 You may want to put this in your application.rb if still doesn't work. 如果仍然不起作用,您可能希望将它放在application.rb中。

  config.railties_order = [ModelEngine::Engine, :main_app, :all]

I guess the reason why it works in irb and not in rake is because they look up classes differently. 我猜它在irb而不是rake中工作的原因是因为它们以不同的方式查找类。 Irb (which I believe you run by running rails console) loads all the classes at once therefore it loads the classes from engine, then it runs the initializer where you have the classes from engine already defined. Irb(我相信你通过运行rails控制台运行)一次加载所有类,因此它从引擎加载类,然后它运行初始化程序,你已经定义了引擎中的类。 I guess (though I'm not sure) Rake in development mode uses lazy loading of constants. 我猜(虽然我不确定)Rake在开发模式下使用延迟加载常量。 So it doesn't load all the classes at the very beginning and only when it finds a constants that is undefined. 所以它不会在最开始时加载所有类,只有在找到未定义的常量时才会加载。 Then it starts looking for a file that may define that constant. 然后它开始寻找可以定义该常量的文件。 Since you put some Person in initializer it doesn't look up the engine's model at all cause at the point it sees Person it has the Person definition already. 由于你在初始化程序中放置了一些Person,因此它不会查找引擎的模型,因为它看到Person已经具有Person定义。 That's why the inclusion of module instead of reopening the class may help -> it enforces that it will lookup the Person constant from engine. 这就是为什么包含模块而不是重新打开类可能会有所帮助 - >它强制它将从引擎中查找Person常量。

I think it will work as long as you just reopen the class, without inheriting from ActiveRecord::Base again. 我认为只要你重新打开这个类就可以工作,而不必再次继承ActiveRecord :: Base。 So, like this: 所以,像这样:

class Person
  def custom_string
    address.to_street.to_s
  end
end

Edit: 编辑:

You might also need to add a line like this before you reopen the class: 在重新打开课程之前,您可能还需要添加这样的一行:

require_dependency ModelEngine::Engine.root.join('app', 'models', 'person').to_s

where ModelEngine::Engine is just the class for the engine that contains all your models. 其中ModelEngine :: Engine只是包含所有模型的引擎的类。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM