Forum: Ruby on Rails Extending Rails Classes - Where?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Brian (Guest)
on 2009-05-28 08:07
(Received via mailing list)
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?
Julian L. (Guest)
on 2009-05-28 11:01
(Received via mailing list)
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
Frederick C. (Guest)
on 2009-05-28 12:14
(Received via mailing list)
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
Frederick C. (Guest)
on 2009-05-28 12:15
(Received via mailing list)
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
Brian (Guest)
on 2009-05-28 17:30
(Received via mailing list)
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>
Brian (Guest)
on 2009-05-28 18:03
(Received via mailing list)
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.  :)

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).
Rick D. (Guest)
on 2009-05-28 19:00
(Received via mailing list)
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
This topic is locked and can not be replied to.