views:

159

answers:

4

I would like to write a method in ruby that takes a class with certain methods and modifies its behavior by adding methods or changing how existing methods work. I would like to do this in a way that doesn't modify the base class so basically I want a function that takes a class and returns a new modified class without harming the initial class. I'm pretty sure this is possible but I'm not sure where to start.

+1  A: 

I'd suggest using inheritance or a mixin; in my opinion, the use of a mixin would be a wiser idea though using inheritance is easier for a newbie.

Remember, you can always inherit from the class and change behavior or wrap it with new code as desired.

class Mammal
  def speak
    "..."
  end
end

class Cat < Mammal
  def speak
    "meow"
  end
end

class Lion < Cat
  def speak
    "get ready for a big " + super + "!"
  end
end

module Asexual_Critter
  def reproduce(critter_list)
    puts "*poink!*"
    critter_list << self.clone
  end
end

class Mutated_Kitty < Cat
  include Asexual_Critter # inane example I know, but functional...
end

Just remember that if you want to play with this not to do:

critters = [Mutated_Kitty.new]
begin
  critters.each { |c| c.reproduce(critters) }
end while critters.length > 0

Or else you'll be in for a long wait until you run out of RAM, or perhaps segfault.

The Wicked Flea
+1  A: 

You have a couple options:

  • you can use x = Class.new(Parent) { def meth; puts "hello"; super; puts "bye"; end } to dynamically define a class and override methods (& define new ones)
  • you can use a Delegator

So for instance, if you wanted to dynamically create classes that logged certain method calls:

class Class
  def logging_subclass(*methods)
    Class.new(self) do
      methods.each do |method|
        define_method(method) do |*args,&blk|
          puts "calling #{method}"
          ret = super(*args,&blk)
          puts "#{method} returned #{ret.inspect}"
          ret
        end
      end
    end
  end
end

class One
  def foo
    "I'm foo!"
  end
end

# this prints nothing
One.new.foo #=> returns :foo

# this prints:
#   > calling foo
#   > foo returned "I'm foo!"
One.logging_subclass(:foo).new.foo #=> returns :foo

Note that you need ruby 1.9 to support capturing do |&blk| (capturing blocks in block arguments).

rampion
Can't you leave off the argument list from the super call altogether? Super is supposed to use the same arguments as the current method unless you give it something else. But Class.new sounds like exactly the thing David is looking for.
Chuck
You can in a normal definition, but not in `define_method`, it's a `Runtime Error: implicit argument passing of super from method defined by define_method() is not supported. Specify all arguments explicitly.` If davidk01 is always redefining the same method, then the normal `def meth ... end` syntax will work.
rampion
A: 

I'm sorry if i understand your question wrong, but google "ruby dsl", u might find this thing very useful to your problem. also check out the questions asked about this thing in here.

check out this article if u don't know how to start:

[http://www.jroller.com/rolsen/entry/building_a_dsl_in_ruby][1]

i did something like that before by saving the changes i made into a text file, and read that file all over again when the program restarted or reload it's settings.

Raafat
A: 

Thanks guys. My original motivation was to try to mimic the functionality of monad transformers from haskell in ruby and the suggestions about using mixins and using Class.new(Parent) I think will do the job.

davidk01