Dr Nic

[ANN] Dr Nic’s Magic Models 0.8 – Validate Anything, Anytime, Anywhere

Ladies and Gentlemen, welcome one and all to the greatest Magic Show in Programming. If you bought free tickets to the last show, then you’ll be prepared to pay double for this show because its twice as good. Magic Models provide virtual validations on all database constraints!

In the first run of the sell-out Magic Models – “I can’t believe its an ActiveRecord” – Super Show – you were amazed as we stripped away the necessary essentials of an ActiveRecord – right in front of your eyes – the necessary became unnecessary.

We removed the validates_presence_of validations… and the Magic Model still validated.

We removed the has_many, belongs_to, and has_many :through associations… and the Magic Model still associated.

We removed the actual class definition itself – the very thing that provides the ActiveRecord CRUD layer… and the Magic Model still CRUDded.

You applauded loudly, and then walked out the front door surruptitiously ignoring the donation jar.

Today. Ladies and Gentlemen, I have removed the donation jar entirely so as to not distract your singular focus on the All New Magic Show!

Drums rolling in the background…

Let us start with a humble, unassuming database schema. Today’s schema syntax of choice? SQL.

CREATE TABLE `groups` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) NOT NULL UNIQUE,
  `description` varchar(50) default NULL,
  `some_int` integer default NULL,
  `some_float` float default NULL,
  PRIMARY KEY  (`id`)
) TYPE=InnoDB;

The first change to note, is that all Magic Models and their associations are now being generated at startup time, and not dynamically.

>> ActiveRecord::Base.send :subclasses
=> [CGI::Session::ActiveRecordStore::Session, Group]

This is the current solution to supporting the :include option on find calls. Debate the pros/cons of this on the forum. It may be helpful for the various individuals trying to get Magic Models working with Streamlined.

Now… the magic!!! Watch carefully…

>> g = Group.new
>> g.name = "x" * 51
=> "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
>> g.valid?
=> false
>> g.errors.full_messages
=> ["Name is too long (maximum is 50 characters)"]

Crikey! That’s a beauty! That name is just too big for the database and boy does the validation automatically tell you that. Fantastic isn’t it!?

Ok, that’s enough Steve Irwin impressions.

>> g = Group.new
>> g.name = "x" * 50
=> "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
>> g.valid?
=> true

Good.

Ok, more tricks… more drumrolls…

>> g.some_int = 99.9
=> 99.9
>> g.some_float = "string"
=> "string"
>> g.valid?
=> false
>> g.errors.full_messages
=> ["Some float is not a number", "Some int is not a number"]

Tada!!!

And now for the final Virtual Validation magic trick… I draw your attention to the SQL: the name field is UNIQUE. Could the magic models… could they… no… maybe? Oh maybe they could support validation over unique fields?!?!?!

100 dancing girls and guys in silver spandex appear on the stage. Of course, more drums are rolling…

>> g = Group.new
=> #<Group:0x3884eb8 @attributes={"name"=>"", ... >
>> g.name = "test"
=> "test"
>> g.save
=> true
>> g2 = Group.new
=> #<Group:0x3884eb8 @attributes={"name"=>"", ... >
 @new_record=true>
>> g2.name = "test"
=> "test"
>> g2.save
=> false
>> g2.errors.full_messages
=> ["Name has already been taken"]

Dr Nic takes a bow and is escorted off by his 100 dancers and 3 large elephants that turned up late.

Installation

gem install dr_nic_magic_models

The at the top of your Ruby script or at the bottom of your Rails’ environment.rb file, place the line:

require 'dr_nic_magic_models'

Epilogue

Matthew Painter made this possible. He is the author, producer, and cheer leader for this release. Whilst I was fart-arsing around writing Dr Nic’s GrandCivs, and he was doing something more important – making the Magic Models the ultimate magic show in town. And he has a funky website with a draggable background.

The original ideas for the extended Virtual Validations came from Red Hill Consulting: though again it was Matthew who did the work to integrate it into the Magic Models.

The whole of Magic Models architecture has been refactored. Matthew did that.

My 2+ gig of Gmail account filled with diff files. Matthew did that. You get the picture. Thanks Matt!

Anything, Anytime, Anywhere

“Anything, Anytime, Anywhere” was the slogan of the Goodies – a British TV show that was rerun over and over again after school in Australia. Their fame amongst school kids in the 70s and 80s, gives them Rock God status amongst midaged Australians today. They toured Australia in 2005 with 13 sell-out shows. I didn’t get tickets. If you did get tickets, I don’t want to know about it because I won’t talk to you.

[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: