- loading...
[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.
Welcome! 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.