Dr Nic

Extending _why’s Creature class

Many Rubist’s first explanation of metaprogramming is by why the lucky stiff (_why)’s Why’s (Poignant) Guide to Ruby, chapter 6, section 3.

You go on a dragon-hunting, adventure game using sexy Ruby syntax (a domain-specific language/DSL for adventure games?). Here is some sample syntax for defining a monster class:

 class Dragon < Creature
   life 1340     # tough scales
   strength 451  # bristling veins
   charisma 1020 # toothy smile
   weapon 939    # fire breath
 end

The life, strength, charisma and weapon class methods are generated by a traits class method called against the Creature class (read the chapter).

class Creature
  traits :life, :strength, :charisma, :weapon
end

Read this chapter section many times and admire the beauty of the idea (and amuse yourself with his writing style!).

But there is one small improvement that could be made: currently, after setting the trait methods (e.g. life 1340 sets the life trait to 1340), you cannot access the class's trait values directly via their original trait method. That is, you cannot call Dragon.life to retrieve the value 1340.

This is due to a limitation of the define_method method being used. The relevant code from _why's book is:

   def self.traits( *arr )
     # 2. Add a new class method to for each trait.
     arr.each do |a|
       metaclass.instance_eval do
         define_method( a ) do |val|
           @traits ||= {}
           @traits[a] = val
         end
       end
     end

The method creator define_method uses a block to define the generated method body. The parameters for the block (val in the example above) become the arguments of the method once its been added to the class. That is, if we call traits :life on our Creature class, then a class method will be generated that requires one argument - the value of the trait. That is, it will generate the following method:

class Creature
  def life(val)
    @traits ||= {}
    @traits[:life] = val
  end
end

Now, back to the problem. How do we support the syntax Dragon.life? To achieve this, the generated method would need to look like:

class Creature
  def life(val = nil)
    @traits ||= {}
    return @traits[:life] if not val
    @traits[:life] = val
  end
end

That is, we need a default value for our method argument. But... blocks don't allow parameters to have default values. We cannot do the following:

         define_method( a ) do |val = nil|
           @traits ||= {}
           return @traits[a] if not val
           @traits[a] = val
         end

A pity, yes.

So, we need to generate our methods differently. The solution is as follows:

      metaclass.class_eval <<-EOS
        def #{a}(val=nil)
          @traits ||= {}
          return @traits[:#{a}] if not val
          @traits[:#{a}] = val
        end
      EOS

The eval methods allow a string to be passed to them. So, we shall pass it a string that defines a new method the old fashioned way: using the def method constructor. Thus it allows us to have default values for our arguments.

QED.

ALTERNATIVE from Chris @ Errtheblog.com

Use the splat! (*) to allow zero or more arguments. Pluck the first one off to represent the incoming argument or nil.

def self.traits( *arr )
  arr.each do |a|
    metaclass.instance_eval do
       define_method( a ) do |*val|
         val = val.first
         @traits ||= {}
         return @traits[a] if not val
         @traits[a] = val
       end
     end
  end
end

There are unanswered questions about its support for 2+ arguments (where we only need support for 0 or 1) that shall remain unanswered for the sake of this simple hack. But feel free to start throwing exceptions around if your users need protection from themselves.

So, cattr_accessor doesn’t work like it should?

Rails’ active_support library adds some wonderful functions into standard Ruby classes. Some we all use day-in-day out are attr_accessor and its class-level equivalent, cattr_accessor.

But cattr_accessor doesn’t work the way you (read, “me”) thought at first glance when you use subclasses. I thought if I declared a class accessor in the superclass, then I would have independent class attributes for all my subclasses. Apparently not…

>> class Parent; cattr_accessor :val; end
=> [:val]
>> class Child1 < Parent; end
=> nil
>> class Child2 < Parent; end
=> nil
>> Child1.val = 4
=> 4
>> Child2.val
=> 4
>> Child2.val = 5
=> 5
>> Child1.val
=> 5

Child1.val and Child2.val seem to be the same value. Not very independent at all. Internally, the classes share a common class attribute. This is useful in certain circumstances, but not what I was looking for.

Instead, I found class_inheritable_accessor.

>> class Parent; class_inheritable_accessor :val; end
=> [:val]
>> class Child1 < Parent; end
=> nil
>> class Child2 < Parent; end
=> nil
>> Child1.val = 4
=> 4
>> Child2.val = 4
=> 4
>> Child2.val = 5
=> 5
>> Child1.val
=> 4

Lovely. Each subclass will have an independent value once you’ve assigned it a value explicitly, else it will pick up the value from its superclass.

UPDATE: class_inheritable_accessor and co. actually clone the superclass’s inherited attributes, rather than just referencing them.

[BTS] Magic Models vs ActiveRecords – Efficiency

Dr Nic’s Magic Models are magical, that goes without saying. But are they efficient at being magical? Do you get the same speed in your application as if you explicitly defined your classes and your associations will your application execute faster?

Yes. The cost of accessing a Magic Model and a normal ActiveRecord is exactly the same – once the Magic Model has been initially generated. Its just a normal ActiveRecord like all your other ActiveRecords: they are loaded into memory when you first ask for them. Normal ActiveRecords are loaded into memory from your /app/models/.rb file; Magic Models are generated classes. The Magic Models would in fact be quicker to load as there is no file access required.

The cost of accessing Automatic Associations versus normal Active Record associations is also exactly the same. To understand the inner workings of associations, see err.the_blog’s latest article.

The point to take away from the discussion is that associations are a generated method on your class. Once you’ve generated it once (at the time you call has_many for example), you have it permanently. So there is only a small, one-time cost for the Automatic Associations – to see which association to generate for the incoming method call. But once you’ve generated the association, its yours permanently.

[BTS] Magic Models – Class creation

[BTS] = Behind the Scenes; also a news-like TV show we used to watch as school kids (in Australia) that explored world news events and then our teachers would make us write reports about it. (UPDATE: this is a lie; it was BTN – Behind the News) Anyway….

Class creation – the magical way

As an educational exercise, it is definitely interesting to look at how the Magic Model’s Invisible Classes [1] are generated. Inside the gem [2], please turn to the file /lib/module.rb I’ve broken it up below too.

class Module
  alias :normal_const_missing :const_missing

  def const_missing(class_id)
    begin; return normal_const_missing(class_id); rescue; end

If you open the Irb/Console and type Foo, you’ll get an “unitialized constant Foo” error. This is raised by the const_missing method in the Module class. Yes, there’s a core class called Module. Type Module.methods.sort to see all the fun things it can do, or look up the API.

Like so many things in Ruby you can override this method, just like you might override the method_missing on a class (Rails does this on its ActiveRecord to support the find_by_... magical methods.)

When you load Magic Models (via the require 'dr_nic_magic_models' statement) it renames/aliases the current const_missing method and then overrides it.

The first thing it does it call the original const_missing to see if it returns anything useful. Why? Rails also overrides this method. Rails doesn’t preload all your model classes at start up. Instead when you first ask for a model class, it loads it via the const_missing method call. So Magic Models won’t do anything until Rails has had a chance to try an load the class from a app/models/foo.rb file, for example.

    unless table_name = DrNicMagicModels::Schema.models[class_id]
      raise NameError.new("uninitialized constant #{class_id}") if
                    DrNicMagicModels::Schema.models.enquired? class_id
    end

The DNMM::Schema class is in charge of mapping an unknown class name (class_id) to one of your database’s tables. To prevent nasty infinite recursion, we raise the “uninitialized constant” error if another const_missing request comes in for the same class_id.

    klass = Class.new ActiveRecord::Base
    const_set class_id, klass

The Class API takes a superclass as a parameter. Here, we want our Invisible Model to be an ActiveRecord.

By setting the anonymous class object to a constant value, e.g. Foo, it automatically names the class. E.g. klass.name == 'Foo'

    klass.set_table_name table_name

Finally, we force the new ActiveRecord class to use the table name that DNMM::Scheme found. You just can’t trust that the class will know what its table name is.

If you find yourself using this sort of code, then here’s some bonus code. When you create your class, you can install some code at the same time.

    klass_code = %q{def to_s; 'I''m magically generated'; end }
    klass_code = lambda {klass_code}
    klass = Class.new ActiveRecord::Base, &klass_code

Feedback

Was this easy to read? Too compact/too long? Let me know below.

If you go on and build something magically cool after reading this, definitely let me know.

Moral of the Story

You can live as a programmer without ever knowing any meta-programming features of Ruby. Heck, we all did it in Java/C++/C# and never knew we needed them. A lot of powerful API features exist in Rails that you’d never be able to build in many other languages; the ActiveRecord library is a tribute to meta-programming magic.

UPDATE: I had previously called Module a module, when its a class. Fixed.

[1] I just like calling them Invisible Models.

[2] Gems are installed inside your Ruby installation folder. Try something like ....\lib\ruby\gems\1.8\gems\ and you'll see all your favourite gems, like rails-1.1.5, active-record-1.14.4, etc.

[ANN] Dr Nic’s Magic Models

Magic hatWelcome! Welcome! Welcome! Ladies and Gentlemen, today you shall be thrilled and dazzled by wonders of magical mystery. Dr Nic’s Magic Models will now be unveiled to all. Mystery and magic that you will be able to perform at home.
Within your ActiveRecord models, never again will you need to write:

  • validates_presence_of validations
  • has_many and belongs_to statements
  • has_many :through statements!!

And for the finale, you will be amazed and astounded as you watch ActiveRecord models appear from nowhere. No class definition, just any old database tables you have lying around the home is all you’ll need to perform this trick!

No cover charge. No free steak knives. No heavy lifting involved.

Installation, DIY magical instructions, and a world of mystery awaits you at:
http://magicmodels.rubyforge.org

Original forum notification: http://www.ruby-forum.com/topic/74957

UPDATE: Digg this!

UPDATE 2: Conversations about the Magic Models have been conducted on Ruby/Rails forums too: