views:

50

answers:

2

Is it possible to add a core method to the Module class in Ruby? I want to do something like this (messed around with the idea yesterday, just whipped this together in a second):

module MyModule
  base do
    has_many :something
  end
end

# implementation, doesn't work though... reason for the question
Module.class_eval do
  def self.base(&block)
    class_eval do
      def self.included(base)
        base.class_eval(&block)
      end
    end
  end
end

If I create a module, I can't access that method:

$ module User; end
$ User.base
NoMethodError: undefined method `base' for User:Module

Any ways to do this?

Update

This works! Thanks @Jörg W Mittag. Makes it much easier to read for Rails has_many and such:

class Module
  def base(&block)
    metaclass = class << self; self; end
    # add the method using define_method instead of def x.say so we can use a closure
    metaclass.send :define_method, :included do |base|
      base.class_eval(&block) unless block.nil?
      base.class_eval <<-EOF
        extend  #{name}::ClassMethods
        include #{name}::InstanceMethods
      EOF
    end
    block
  end
end

Like this example:

module UserRoleBehavior
  base do
    has_many :user_roles
    has_many :roles, :through => :user_roles
  end

  module ClassMethods
    # ...
  end

  module InstanceMethods
    # ...
  end
end

class User < ActiveRecord::Base
  include UserRoleBehavior
end

Cheers!

+1  A: 

I believe you add it by defining the method in the module module:

class Module
    def new_method
       ...
    end
end

Be very careful with this, as it effects all code that uses Module.

Bruce Armstrong
module is a Class, so that doesn't work :/
viatropos
Seems I was one keyword away...
Bruce Armstrong
+4  A: 

If you want to add a method to the Module class, you simply add a method to the Module class:

class Module
  def base
    # ...
  end
end

In your code, you were using

def self.base
  # ...
end

This syntax (def foo.bar) is the syntax for adding a singleton method named bar to the object referenced by foo, which is actually really just adding a regular instance method to the singleton class of the object referenced by foo.

So, in your code you were adding a singleton method named base to the object referenced by self, which inside of class_eval is the class object itself, in this case Module. The reason why singleton methods are called singleton methods, is because they only exist on a single object. In your case, the method base exists only on the object Module, not on any other object, and in particular not on the object User. In other words: the only way to call the base method is as Module.base, because that is the only object that you added it to.

It took me quite a while to wade through your three(!) nested class_evals to find out what it is that you are trying to achieve. Which wasn't made easier by the fact that your code doesn't even actually work, even if defined on the correct class, because def creates a new scope, and so block is undefined in the innermost class_eval.

Apparently, what you are trying to do is this:

class Module
  def base(&block)
    define_singleton_method(:included) do |base|
      base.class_eval(&block)
    end
  end
end
Jörg W Mittag
there it is! awesome, thanks!
viatropos
RE: "In other words: the only way to call the base method is as Module.base, because that is the only object that you added it to." Well technically you can also call `base` on the `Class` class too, since singleton methods are inherited ;)
banister
viatropos
@viatropos: in which code, mine or yours? If it's your code, then yes, that's what you would expect: `def` creates a new scope, so there is no variable named `block` in scope, as I already explained in my answer. If it's my code, then you must have a typo somewhere.
Jörg W Mittag
Jörg W Mittag