↩ More Magic

Get Version

# Dr Nic’s Magic Models

If you’ve used Ruby on Rails you’ll have written at least one model class like this:

    class Person < ActiveRecord::Base
      has_many :memberships
      has_many :groups, :through => :memberships
      belongs_to :family
      validates_presence_of :firstname, :lastname, :email
    end

A few minutes later you’ll have wondered to yourself,

> Why do I have write my own `has_many`, `belongs_to`, and `validates_presence_of` commands if all the data is in the database schema?

Now, for the very first time, your classes can look like this:

    class Person < ActiveRecord::Base
    end

or, if you are lazy…

    class Person < ActiveRecord::Base; end

or, if you read right to the end of this page, this…

    # Go fish.

Magic and mystery abound. All for you. Impress your friends, amaze your mother.

NOTE: The gratuitous use of Dr Nic’s in the name should only enhance the mystical magikery, for magic needs a magician; and I love magic. I always wanted to create my own magic trick. So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too, if Harry were 32 and better dressed.

## Installation

To install the Dr Nic’s Magic Models gem you can run the following command to fetch the gem remotely from RubyForge:

    gem install dr_nic_magic_models

or download the gem manually and run the above command in the download directory.

Now you need to `require` the gem into your Ruby/Rails app. Insert the following line into your script (use `config/environment.rb` for your Rails apps):

    require 'dr_nic_magic_models'

Your application is now blessed with magical mystery.

## David Copperfield eat your Ruby-crusted heart out

Let’s demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas):

    rails magic_show -d sqlite3
    cd magic_show
    ruby script/generate model Person
    ruby script/generate model Group
    ruby script/generate model Membership

Update the migration `001_create_people.rb` with:

    class CreatePeople < ActiveRecord::Migration
      def self.up
        create_table :people do |t|
          t.column :firstname, :string, :null => false
          t.column :lastname, :string, :null => false
          t.column :email, :string, :null => false
        end
      end

      def self.down
        drop_table :people
      end
    end

Similarly, update the `def self.up` method of `002_create_groups.rb` with:

        create_table :groups do |t|
          t.column :name, :string, :null => false
          t.column :description, :string
        end

and `003_create_memberships.rb` with:

        create_table :memberships do |t|
          t.column :person_id, :integer, :null => false
          t.column :group_id, :integer, :null => false
        end

And run your migrations to create the three tables:

    rake db:migrate

### And now for some woofle dust ...

At the end of `config/environment.rb` add the following line:

    require 'dr_nic_magic_models'

Now, let’s do a magic trick. First, let’s check our model classes (`app/models/person.rb` etc):

    class Person < ActiveRecord::Base
    end
    class Group < ActiveRecord::Base
    end
    class Membership < ActiveRecord::Base
    end

Nothing suspicious here. We have no validations and no associations. Just some plain old model classes.

UPDATE: To turn on magic validations, you now need to invoke `generate_validations` on defined classes. So, update your model classes:

    class Person < ActiveRecord::Base
      generate_validations
    end
    class Group < ActiveRecord::Base
      generate_validations
    end
    class Membership < ActiveRecord::Base
      generate_validations
    end

For this trick, we’ll need an ordinary console session. Any old one lying around the house will do.

    ruby script/console

Now a normal model class is valid until you explicitly add `validates_xxx` commands. With Dr Nic’s Magic Models:

    person = Person.new
    => #<Person:0x393e0f8 @attributes={"lastname"=>"", "firstname"=>"", "email"=>""}, @new_record=true>
    person.valid?
    => false
    person.errors
    => #<ActiveRecord::Errors:0x3537b38 @errors={
    	"firstname"=>["can't be blank", "is too long (maximum is 255 characters)"],
    	"lastname"=>["can't be blank", "is too long (maximum is 255 characters)"],
    	"email"=>["can't be blank", "is too long (maximum is 255 characters)"]},
    	 @base=#<Person:0x3538bf0 @errors=#<ActiveRecord::Errors:0x3537b38 ...>, @new_record=true,
    		@attributes={"lastname"=>nil, "firstname"=>nil, "email"=>nil}>>

Kapoow! Instant validation! (NOTE: not as instant as it used to be – remember – you need to call `generate_validations` on each class as required)

Because you specified the three columns as `:null => false`, your ActiveRecord models will now automagically generated `validates_presence_of` for each non-null field, plus several other validations (since version 0.8.0).

Ok, we’re just warming up.

Your models normally require association commands (`has_many`, `belongs_to`, etc, as demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for.

Let’s just watch what Dr Nic’s Magic Models can do without any effort at all…

    person = Person.create(:firstname => "Nic", :lastname => "Williams", :email => "drnicwilliams@gmail.com")
    group = Group.create(:name => "Magic Models Forum", :description => "http://groups.google.com/magicmodels")
    membership = Membership.create(:person => person, :group => group)
    person.memberships.length
    => 1
    membership.person
    => <Person:0x38898e8 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
    "id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
    group.memberships
    => [<Membership:0x3c8cd70 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]

That final association trick is a ripper. Automatic generation of `has_many :through` associations…

    >> person.groups
    => [<Group:0x39047e0 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
    >> group.people
    => [<Person:0x3c33580 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
    "id"=>"1", "email"=>"drnicwilliams@gmail.com"}>]

### Drum roll…

Ladies and gentlemen. For my final feat of magical mastery, I’ll ask you to do something you’ve never done before. This illusion is akin to the floating lady illusion that has been passed down through generations of magicians.

Exit your console session.

DELETE your three model classes: `person.rb, group.rb, and membership.rb` from the `app/models` folder. (You can always get them back via the model generator… be fearless!)

    rm app/models/*.rb

Re-launch your console.

drums are still rolling…

Be prepared to applaud loudly…

    >> Person
    => Person

You applaud loudly, but watch for more…

    >> Person.new.valid?
    => false
    >> person = Person.find(1)
    => <Person:0x3958930 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
    "id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
    >> person.valid?
    => true
    >> person.memberships
    => [<Membership:0x393a000 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
    >> person.groups
    => [<Group:0x390df60 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]

### Tada!

The end.

### Use modules to scope your magic

Only want to pick up tables starting with `blog_`?

    module Blog
      magic_module :table_name_prefix => 'blog_'
    end

    Blog::Post.table_name #	=> 'blog_posts'

## Dr Nic’s Blog

http://www.drnicwilliams.com – for future announcements and other stories and things.

## Articles about Magic Models

- Announcement
- BTS – Class creation

## Forum

http://groups.google.com/group/magicmodels

## Licence

This code is free to use under the terms of the MIT licence.

## Contact

Comments are welcome. Send an email to Dr Nic Williams or via his blog at http://www.drnicwilliams.com

Dr Nic, 30th April 2007
Theme extended from Paul Battley