Extending Rails Classes - Where?


#1

Where is the best place to stash Ruby files that add functionality to
Rails classes (in my case, ActiveRecord::Base)? I think my module is
being loaded too late, and causing a class method to be undefined.

Here’s what I tried: I wrote a module to add a new class method to
ActiveRecord::Base following a pattern that I picked up from
http://guides.rubyonrails.org/plugins.html; in my module implement the
included callback and have it extend the target class with my class
methods (actually singleton methods on the metaclass of my class
object if I’m understanding all of this weirdness correctly):

def self.included(base)
  base.send :extend, ClassMethods
end

module ClassMethods
  def my_method
  end
end

Then outside of the module definition I tell ActiveRecord::Base to
include my module:

ActiveRecord::Base.send(:include, MyModule)

And according to my (obviously poorly written) unit test, this all
worked wonderfully:

def test_responds_to_my_method
assert MyModel.respond_to?(:my_method, true)
end

Great! Except…

class MyModel < ActiveRecord::Base
my_method # undefined local variable or method `my_method’
end

So it seems reasonable to me that this might happen if my_model.rb is
loaded first, then my module, then the test. Additional evidence is
that if I add a “debugger” line to my included callback, I’m never
sent to the debugger. Unless I remove the call to my_method, then I
am.

So if my hypothesis is correct, it’s a bad idea to add my modules to
lib\modules and append that to the end of my load path. But then
where should I stash modules that update rails classes?


#2

Hi,

On 28/05/2009, at 2:06 PM, Brian wrote:

object if I’m understanding all of this weirdness correctly):

def self.included(base)
base.send :extend, ClassMethods
end

module ClassMethods
def my_method
end
end

This is not a class method, is it? it’s an instance method. a class
method would be

def self.my_method
end

end

Great! Except…

class MyModel < ActiveRecord::Base
my_method # undefined local variable or method `my_method’
end

I don’t follow what you want to do here… you’re calling the method
inside the class definition. That makes no sense. What did you want to
happen?
In other words, you’re running a method WHILE declaring the class.

Perhaps you wanted something like this:

class MyModel < ActiveRecord::Base
def some_other_method
my_method
end
end

that will work, but my_method is still here not a class method… it’s
an instance method… (as far as my understanding goes) because of the
way the module is defined.


Learn: http://sensei.zenunit.com/
Last updated 20-May-09 (Rails, Basic Unix)
Blog: http://random8.zenunit.com/
Twitter: http://twitter.com/random8r


#3

On May 28, 5:06 am, Brian removed_email_address@domain.invalid wrote:

So it seems reasonable to me that this might happen if my_model.rb is
loaded first, then my module, then the test. Additional evidence is
that if I add a “debugger” line to my included callback, I’m never
sent to the debugger. Unless I remove the call to my_method, then I
am.

So if my hypothesis is correct, it’s a bad idea to add my modules to
lib\modules and append that to the end of my load path. But then
where should I stash modules that update rails classes?

The problem is not so much where you put it but when it’s loaded. If
you just stick a file in lib/blah rails isn’t going to magically trip
over it. It will load your module if elsewhere you say MyModule, and
in production mode it will load it at some point. So you need to
require it explicitly and you need to do so at the right point, and
that happens to be from an initializer (ie a file in config/
initializers) - these are run after rails has been loaded, but before
your application classes are.

Fred


#4

On May 28, 7:59 am, Julian L. removed_email_address@domain.invalid wrote:

I don’t follow what you want to do here… you’re calling the method
inside the class definition. That makes no sense. What did you want to
happen?
In other words, you’re running a method WHILE declaring the class.

That’s reasonable enough. Just like acts_as_list, has_many, etc…

Fred


#5

Thanks, Fred! This works as expected now.

I’ve made a config/initializers/modules.rb. For now I only require
this one module, but I’ll probably just have this require everything
in lib/modules so that I don’t have to worry about the next one.

On May 28, 4:13 am, Frederick C. removed_email_address@domain.invalid


#6

On May 28, 2:59 am, Julian L. removed_email_address@domain.invalid wrote:

This is not a class method, is it? it’s an instance method. a class
method would be

def self.my_method
end

I have all of 20 hours of Ruby experience now, so I’ll probably screw
up this explanation. The whole include/extend thing is still very
confusing to me, but I’m having ActiveRecord::Base call include on my
module and attaching a callback so that I can do more.
ActiveRecord::Base is an instance of the Class class, so when the
callback uses extend, it is the ActiveRecord::Base instance itself
(and ActiveRecord::Base objects) that pick up the new my_method
instance method. My understanding is that Ruby then attaches this to
a metaclass for the ActiveRecord::Base instance, making it a singleton
method. And I think that’s all class methods really are; singleton
methods on the metadata class for an instance of the Class class. So
through module trickery I have created a new class method.

If I have this wrong, let me know. I don’t like to write code that I
don’t understand, so the sooner I wrap my head around the actual
mechanics of how this works, the better.

I don’t follow what you want to do here… you’re calling the method
inside the class definition. That makes no sense. What did you want to
happen?
In other words, you’re running a method WHILE declaring the class.

For any language other than ruby, I would agree that this doesn’t make
sence. :slight_smile:

Apparently it’s standard practice though for this language. You can
run methods right in the middle of your class definition. You can try
this out pretty easily in irb:

irb(main):001:0> class A
irb(main):002:1> puts “hello!”
irb(main):003:1> end
hello!
=> nil

Strange! But useful. In my case I’m actually planning to create
three class methods similar to my_method above. When run these will
generate instance methods for my models (similar to associations).


#7

On Thu, May 28, 2009 at 10:02 AM, Brian removed_email_address@domain.invalid
wrote:

 end

I have all of 20 hours of Ruby experience now, so I’ll probably screw
through module trickery I have created a new class method.

If I have this wrong, let me know. I don’t like to write code that I
don’t understand, so the sooner I wrap my head around the actual
mechanics of how this works, the better.

No, you have this quite right, and extending a class/module with a
‘class methods’ module inside another module using the included hook
is the standard way to add class methods.


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale