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

Maurício Linhares
http://alinhavado.wordpress.com/ (pt-br) | http://blog.codevader.com/
(en)

On Wed, Apr 8, 2009 at 5:27 PM, Jason F.

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

On Wed, Apr 8, 2009 at 6:40 PM, Jason F.
[email protected] wrote:

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?

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
http://alinhavado.wordpress.com/ (pt-br) | http://blog.codevader.com/
(en)

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