Steve D
Steve D

Reputation: 393

Metaprogramming in Ruby with derived classes

I'm trying to write a method that prints class variable names and their values. As an example:

class A
    def printvars
        ???
    end
end

class <<A
    def varlist(*args)
        ???
    end
end

class B < A
    varlist :c
    def initialize(c)
        @c = c
    end
b = B.new(10)
b.printvars()

And I would like the output to be c => 10. But I don't know what goes in the ???. I've tried using a self.class_eval in the body of varlist, but that won't let me store args. I've also tried keeping a hash in the class A and just printing it out in printvars, but the singleton class is a superclass of A and so has no access to this hash. So far everything I've tried doesn't work.

I think something similar must be possible, since Rails does something related with its validates_* methods. Ideally I could make this work exactly as expected, but even a pointer to how to print just the variable names (so just c as output) would be most appreciated.

Upvotes: 1

Views: 48

Answers (2)

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84453

Just use Module#class_variables

As far as I can tell, you're vastly over-complicating this. All you need is the pre-defined Module#class_variables method. You can call this directly on the class, or invoke it through self if you want to bind it to an instance of the class. For example:

class Foo
  @@bar = "baz"

  def show_class_variables
    self.class.class_variables
  end
end

Foo.class_variables
#=> [:@@bar]

foo = Foo.new
foo.show_class_variables
#=> [:@@bar]

Upvotes: 1

Chuck Vose
Chuck Vose

Reputation: 4580

You might like this answer: What is attr_accessor in Ruby?

Basically, as you surmised, varlist needs to be a class method which takes a variable list of arguments (*args). Once you have those arguments you could try any number of things using send, respond_to?, or maybe even instance_variable_get. Note, none of those are really recommended, but I wanted to answer your question a bit.

The other half is that you should probably look into method_missing in order to understand how things like validates_* are working. The * part necessitates that you do something like method_missing because you can't actually do module_eval until you know what you're looking for. In the case of the magic rails methods, you don't necessarily ever know what you're looking for! So we rely on the built in method_missing to let us know what got called.

For funzies, try this in IRB:

class A
  def method_missing(method, *args, &block)
    puts method, args.inspect
  end
end
A.new.banana(13, 'snakes')
A.new.validates_serenity_of('Scooters', :within => [:calm, :uncalm])

Does that help?

Upvotes: 1

Related Questions