tags:

views:

943

answers:

1

Hi,

I'm playing around with Ruby and I have written the following code:

module IdAndNameRedefine
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def use_id_and_name_from_module(use_attribute)
      class_eval <<CODE
        def id_and_name
          "\#{id}-\#{#{use_attribute}.downcase}"
        end
CODE
    end
  end
end


class Model
  include IdAndNameRedefine

  attr_reader :id, :name1, :name2

  def initialize(id, name1, name2)
    @id = id
    @name1 = name1
    @name2 = name2
  end

  def id_and_name
    "#{id}-#{name1}"
  end

  use_id_and_name_from_module :name2
end

model = Model.new(1, "TesT", "Test number TWO")
puts model.id_and_name

When I'm trying to do here is to override the class method id_and_name in class Model with a method dynamicly inserted by the IdAndNameRedefine-module. When that module is included, it creates a "static" method (really, a class method of the Model.class as I understands it) and when use_id_and_name_from_module is called, it creates a class method in Model which redefines the id_and_name to use whatever attribute of Model asked for.

My question is.. Are there any better way of doing this, or is this the "correct" way of doing it? I'm not really sure if I like the class_eval with takes a string where I need to escape values etc. to make this work.

+4  A: 

You don't have to pass a string to class_eval. It can take a block instead. In fact, I can't think of a single occasion where I've had to pass a string to class_eval. So we can rewrite the ClassMethods module like so:

module ClassMethods
  def use_id_and_name_from_module(use_attribute)
    class_eval do 
      define_method(:id_and_name) {"#{id}-#{send(use_attribute).downcase}"}
    end
  end
end

But in this particular case, we're just telling self to class_eval, meaning we're already in that class's context. So it can actually be shortened to:

module ClassMethods
  def use_id_and_name_from_module(use_attribute)
    define_method(:id_and_name) {"#{id}-#{send(use_attribute).downcase}"}
  end
end

(I just wanted to show how class_eval really works, since you seemed most interested in that part.)

Chuck
Thanks! I thought it looked kinda stupid, but I couldn't figure out a better way of doing it. I do believe that I tried define_method too, but that point I had the `use_id_and_name_from_module`-call in the top of the class definition, thus it didn't work (gets overwritten by the declaration in later in class I guess).Just out of curiosity; Is there any way of placing the `use_id_and_name_from_module` in the top of the class and make sure it can't be overridden in later declarations?
Thorbjørn Hermansen
You could override method_added and re-add your version whenever somebody tries to replace it. Seems a bit convoluted, though. Better just not to redefine the method in the first place.
Chuck