acts_as_magic_model

Rails plugin to allow for extensible models, where inter-model relations are inferred from the database’s structure, and not necessarily from explicit declarations in the models.

This means, when one of your models acts_as_magic_model, this plugin will try to infer all the basic relations (has_many, belongs_to and has_and_belongs_to_many) its base table has with other tables in the database, adapting the model correspondingly.

Installing the plugin

The easiest way is through Rails’ own scripts/plugins utility. The first thing you should do is to tell Rails where the plugin’s tree is located.

If you want to follow the latest changes, you will probably prefer following the project’s trunk, however, for production, I strongly suggest you to stick to a stable release. Depending on what you prefer, from your project’s base directory, type:

$ ./script/plugin source http://magicmodel.rubyforge.org/svn/trunk/

Or, to follow a given release (say, 0.2 - Of course, check on the latest stable release before doing this):

$ ./script/plugin source http://magicmodel.rubyforge.org/svn/tags/0.2/

Then, ask Rails to install the plugin:

$ ./script/plugin install acts_as_magic_model

If you use Subversion for tracking your project’s development, you will probably want to mark the plugin as an external repository. To do so, add the -x switch:

$ ./script/plugin install -x acts_as_magic_model

Using the plugin

In order to use this plugin, you only have to declare it in your model. As an example, say you have the proverbial project-management system, where you have projects (each of which can be of several different types) and people, and a HABTM relation between people and projects. So, you have the following tables (expressed as migrations here):

create_table :people, :force => true do |t|
  t.column :name, :string
end

create_table :project_types, :force => true do |t|
  t.column :name, :string
end

create_table :projects, :force => true do |t|
  t.column :name, :string
  t.column :project_type_id, :integer
end

create_table :people_projects, :force => true, :id => false do |t|
  t.column :person_id, :integer
  t.column :project_id, :integer
end

Using acts_as_magic_model, you can skip declaring the relations in your models, like this: (of course, each of the classes still has to be declared in its own file)

class Person < ActiveRecord::Base
  acts_as_magic_model
end

class Project < ActiveRecord::Base
  acts_as_magic_model
end

class ProjectType < ActiveRecord::Base
  acts_as_magic_model
end

And the gaps will be filled in for you - that is, you can go on and ask for Project.find(:first).people, ProjectType.find(:first).projects.size, Person.find(:first).projects.map {|pr| pr.project_type}, and whatever you fancy.

Dynamically created models

If any table is related to the models you are declaring as magic does not have a corresponding model explicitly declared, an empty one will be generated - That is, if you create a new table:

create_table :items, :force => true do |t|
  t.column :name, :string
  t.column :description, :string
end

add_column :people, :item_id

When you call Person for the first time, it will create a Item model, just as if you had declared it like:

class Item < ActiveRecord::Base
  has_many :people
end

But keep in mind that Item will not exist before Person is first touched.

If, besides this plugin, you are also using acts_as_catalog, an extra #attribute_name method will be added to models referencing a catalog, returning the catalog’s name. That is, in the above Person/Project/ProjectType example, you will be able to call Project.find(:first).project_type_name - Although this does not look like a huge improvement over project_type.name, but I’ve found it usual to ask for variations of «Project.respond_to?(“#table_name”)»

  • This will make those constructs easier

TO DO

  • Add the ability to specify whether to auto-declare missing classes or just skip them

  • Allow to be called globally, auto-declaring /all/ of the tables as classes with acts_as_magic_model - would be great for prototyping at least! (i.e. called from environment.rb?)

  • Find some basic constraints and auto-declare validations

Author, copyright and licensing.

This plugin was written by Gunnar Wolf <[email protected]>, Instituto de Investigaciones Económicas, UNAM.

Licensing information

This plugin is under a MIT license. For further information, please check the LICENSE file.

Getting the code

The plugin project’s home page can be found at rubyforge.org/projects/magicmodel/

The Git tree can be cloned anonymously:

git clone git://rubyforge.org/magicmodel.git

You can also browse the Git repository via the web interface, and even generate tarballs for specific points in time at:

http://magicmodel.rubyforge.org/git?p=magicmodel.git;a=tree