1

So I created a gem. It has a class like this:

class MyRecord < ActiveRecord::Base
  def self.gem_method
  end
end

Now in Rails app, I have a class with same name in models directory but with a different method:

class MyRecord < ActiveRecord::Base
  def self.rails_method
  end
end

Now I added the gem to my Gemfile and ran bundle install and then launched rails console. And check out these results:

2.1.2 :002 > MyRecord.respond_to? :gem_method
 => true 
2.1.2 :003 > MyRecord.respond_to? :rails_method
 => false 

My expectation was that the MyRecord class in my Rails project would "open" up the MyRecord class in the gem and add methods to it. But obviously this is not what happened. What am I missing?

Daniel Viglione
  • 8,014
  • 9
  • 67
  • 101
  • Seems like your `MyRecord` in your Rails app doesn't get loaded. Did you name the file properly? `app/models/my_record.rb` Rails uses the Ruby naming conventions to load classes, meaning that classes might not be loaded if you put for example the class `MyRecord` in a file `test_record.rb`. – 3limin4t0r Jun 22 '18 at 10:28
  • @JohanWentholt MyRecord in Rails project is located in app/models/my_record.rb – Daniel Viglione Jun 22 '18 at 17:33
  • Is opening up a class working if you specify that it inherits from ActiveRecord each time? Did you try opening it with only `class MyRecord` and not `class MyRecord < ActiveRecord::Base` ? I don't know which would be called first though. – Guillaume Jun 22 '18 at 23:04

1 Answers1

2

I don't think you will achieve the desired result with this approach. What are you trying to do?

As far as I know you should be namespacing your gem classes properly anyway. Meaning you wrap all of your class definitions in a module named after your gem:

module MyGem
  class MyRecord < ActiveRecrod::Base
    #some stuff like def self.gem_method
  end
end

This way the definition of your classes don't interfere unintentionally with the application using the gem or in a way that is non transparent (i.e. confusing) for the user. If the user really wants to use the MyRecord class as delivered by the gem he can just refer to it like MyGem::MyRecord, e.g. he could do

class MyRecord < MyGem::MyRecord

This would be the solution if you want to really seperate the two classes.

If you actually aim to monkey patch a class defined in the application: just don't. Instead, provide the methods you want to be given by the gem in a module

module MyFunctionality
  def gem_method
    # ...
  end
end

and let the user include this by the class that should be able to access these methods:

class MyRecord < ActiveRecord::Base
  include MyFunctionality
  # ...
end

This needs to be actively done by the user of your gem but this way it does not happen unintentionally. So the "problem" you described is actually a feature for better transperancy. If I come up with an explanaition why it technically works as you describe, I'll let you know. But for the moment I suggest you accept it and solve it in a way that is more compliant with ruby best practices.

If you want to read more about Monkey Patching and module inclusions; The top answer to this question describes very well what you should do and why: When monkey patching a method, can you call the overridden method from the new implementation?

Please let me know if you have questions about my answer.

Sinto
  • 3,915
  • 11
  • 36
  • 70
Jay Schneider
  • 325
  • 1
  • 7
  • I actually ultimately did do this (module scope). But I am still curious of why the open class feature did not work in the Rails application. – Daniel Viglione Jun 22 '18 at 17:33
  • I have two ideas what you could try if you really want the monkey-patchey behaviour of the resulting class having both methods `rails_method` and `rails_method` available: First: Try replacing the class name `MyRecord` by `MyRecord.class_eval` to prevent loading problems Second: What happens if you omit the inheritance in your gem, i.e. don't write `class MyRecord < ActiveRecord::Base` but just `class MyRecord` instead. Or do your already need AR functionality in your gem? *edit: Misclicked and transmitted comment too early – Jay Schneider Jun 25 '18 at 07:46