Having trouble extending a class from a Rails plugin

I have class in a plugin that I want to crack open and add some functionality to for a particular application. So, I created a file by the same name as the class in my app/models folder and added some methods to the class, but, I can't seem to get Rails (2.2.2) to pick-up the extended definition.

I did find while trying to debug the problem that if I paste the extended definition into, say, environment.rb it works fine. Also, if I require_dependency "#{RAILS_ROOT}/app/models/my_class.rb" in the plugin's version of the class it also works fine (obviously not desirable).

All of this leads me to believe that my problem has something to do with the class loader, but, I can't figure it out. I've laid out an example below that illustrates my problem. Any help would be much appreciated.

Thanks, Jason

# vendor/plugins/my_plugin/init.rb require 'my_plugin'

# vendor/plugins/my_plugin/lib/my_plugin.rb module Jason   class MyClass     def a_method       # ...     end   end end

# app/models/my_class.rb module Jason   class MyClass     def another_method       # ...     end   end end

$ script/console

Jason::MyClass.new.another_method

NoMethodError: undefined method `another_method' for #<Jason::MyClass:0x408b444>

Unfortunately it will never work the way you want it to.

Place this code into an initializer file (under the initializers folder) or place it under the "lib" folder and do an explicit require.

Also, if all you want to do is to add a method to a class defined elsewhere, do it like this:

Jason::MyClass.class_eval do   def another_method     # do whatever you want to do here.   end end

Thanks for the reply, Maurício. Out of curiosity, what is the reason that this does not work the way that I thought it would. I am planning to delve into the Rails class loader code tonight, but, if you can shed any light on this I'd much appreciated it.

Place this code into an initializer file (under the initializers folder) or place it under the "lib" folder and do an explicit require.

I've actually had similar problems trying to use initializers with plugins in the past. Specifically, I've tried to use an initializer to define application specific values for constants used by my plugin. For example:

# config/initializers/my_class.rb class Jason::MyClass   MY_CONSTANT = "the application's value for this constant" end

Rails, in this case, appears to load the class definition from the initializer and stops there, i.e., it never loads the rest of the class definition from the plugin. So, calling a method defined in the plugin's version of the class like a_method, would also result in the NoMethodError...

$ script/console

Jason::MyClass.new.a_method

NoMethodError: undefined method `a_method' for #<Jason::MyClass:0x408b444>

Jason::MyClass.class_eval do   def another_method     # do whatever you want to do here.   end end

I actually tried this approach. I placed this exact code in my app/models/my_class.rb version of the class and it results in the same NoMethodError. However, if I place it in environment.rb it works like a charm! :-/

It seems to me that once the class has been defined either by the plugin or by an initializer (it appears the initializer is loaded, then the plugin, then the model), Rails will not allow any modifications to it. Any additional thoughts?

Jason

You just figured out the problem by yourself :slight_smile:

If the class is already loaded, Rails will not try to load it again, no matter what you do, that's why I showed you the class_eval approach. Using class_eval (instead of directly "defining" a class) will make Rails try to load the class from somewhere else (that would be your plugin) and you woudn't have any method missing or class not being loaded issues.

Once again, place that code into an initializer and you're done, but placing it under app/models won't work.

Maurício Linhares wrote:

Thanks again for the reply Maurício.

You just figured out the problem by yourself :slight_smile:

I'm curious as to why it works if I paste the extended class definition into environment.rb. I also found that if I include the following at the end of my environment.rb it works as I thought it might. Do you know why this is an exception to the rule?

# config/environment.rb require 'jason/my_class'

If the class is already loaded, Rails will not try to load it again, no matter what you do, that's why I showed you the class_eval approach. Using class_eval (instead of directly "defining" a class) will make Rails try to load the class from somewhere else (that would be your plugin) and you woudn't have any method missing or class not being loaded issues.

I did try the class_eval approach, however, based on your comment, I was putting the code in the wrong spot. I had placed the code into the app/models/my_class.rb. I guess it needs to go into an initializer. Do you know why that is?

Thanks again for the reply! Jason