= Active Record -- Less painful object-relational mapping

Active Record implements the object-relational mapping (ORM) 
pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] of the same name
as described by Martin Fowler, which connects a single business object
(or inheritance hierarchy) to a single table. Fowler describes it as follows:

  "An object that wraps a row in a database table or view, encapsulates 
       the database access, and adds domain logic on that data."

This differs from data-mapper[http://www.martinfowler.com/eaaCatalog/dataMapper.html] solutions, 
like Hibernate[http://www.hibernate.org/] for Java, by foregoing the inconvenience of operating
everything through a service interface at the expence of marrying the business object to the ORM solution.

So instead of (Hibernate example):

   long pkId = 1234;
   DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
   // something interesting involving a cat...
   sess.save(cat);
   sess.flush(); // force the SQL INSERT

Active Record lets you:

   pkId = 1234
   cat = Cat.find(pkId)
   # something even more interesting involving a the same cat...
   cat.save

Although this is a fundemental difference in approach, it's the least of 
the simplifications that Active Record brings.

A short rundown of the major features:

* Associations between objects controlled by simple meta-programming macros.
   class Firm < ActiveRecord::Base
     has_many  :clients
     has_one   :account
     belong_to :conglomorate
   end

* Validation rules that can differ for new or existing objects.
   class Post < ActiveRecord::Base
     def validate # validates on both creates and updates
       errors.add_on_empty "title"
     end

     def validate_on_update
       errors.add_on_empty "password"
     end
   end
 
* Lifecycle callbacks on everything (instantiation, saving, destroying, validating, etc).
   class Person < ActiveRecord::Base
     def before_destroy # is called just before Person#destroy
       CreditCard.find(credit_card_id).destroy
     end
   end

* Lifecycle observers
   class CommentObserver < ActiveRecord::Observer
     def after_create(comment) # is called just after Comment#save
       NotificationService.send_email("david@loudthinking.com", comment)
     end
   end

* Inheritance hierarchies 
   class Company < ActiveRecord::Base; end
   class Firm < Company; end
   class Client < Company; end
   class PriorityClient < Client; end

* Database abstraction through simple adapters
   ActiveRecord::Base.establish_dbi_connection(uri, username, pass)
   ActiveRecord::Base.establish_mysql_connection(host, table, username, pass)

* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]


== Simple example (1/2): Defining tables and classes

Data definitions are specified only in the database. Active Record queries the database for 
the column names (that then serves to determine which attributes are valid) on regular
objects instantiation through the new constructor and relies on the column names in the rows
with the finders.
 
   # CREATE TABLE companies (
   #   id int(11) unsigned NOT NULL auto_increment,
   #   client_of int(11),
   #   name varchar(255),
   #   type varchar(100),
   #   PRIMARY KEY  (id)
   # )

Active Record automatically links the "Company" object to the "companies" table

   class Company < ActiveRecord::Base
     has_many :people, :class_name => "Person"
   end

   class Firm < Company
     has_many :clients
  
     def people_with_all_clients
      clients.inject([]) { |people, client| people + client.people }
     end
   end

The foreign_key is only necessary because we didn't use "firm_id" in the data definition
 
   class Client < Company
     belong_to :firm, :foreign_key => "client_of"
   end

   # CREATE TABLE people (
   #   id int(11) unsigned NOT NULL auto_increment,
   #   name text,
   #   company_id text,
   #   PRIMARY KEY  (id)
   # )

Active Record can't guess the table name itself from exceptions like these, so we help it...

   class Person < ActiveRecord
     belong_to :company

     def table_name() "people" end
   end

== Simple example (2/2): Using the domain

Picking a database connection for all the active records

   ActiveRecord::Base.establish_mysql_connection("host", "user", "pass", "table")

Create some fixtures

   firm = Firm.new("name" => "Next Angle")
   # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm")
   firm.save

   client = Client.new("name" => "37signals", "client_of" => firm.id)
   # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm")
   client.save

Lots of different finders

   # SQL: SELECT * FROM companies WHERE id = 1
   next_angle = Company.find(1)

   # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm'
   next_angle = Firm.find(1)    

   # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle'
   next_angle = Company.find_first "name = 'Next Angle'"

   next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first

The supertype, Company, will return subtype instances

   Firm === next_angle

All the dynamic methods added by the has_many macro

  next_angle.has_clients?  # true
  next_angle.clients_count # total number of clients
  all_clients = next_angle.clients

Constrained finds makes access security easier when ID comes from a web-app

   # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2
   thirty_seven_signals = next_angle.find_in_clients(2)

Bi-directional associations thanks to the "belong_to" macro

   thirty_seven_signals.has_firm? # true
   thirty_seven_signals.firm?(next_angle) # true


== Examples

Active Record ships with a bunch of examples that should give you a good feel for
operating usage. Be sure to edit the <tt>examples/shared_setup.rb</tt> file for your
own database before running the examples.

It's also highly recommended to have a look at the unit tests. Read more in link:files/RUNNING_UNIT_TESTS.html


== Database support

Active Record ships with connection adapters for DBI[http://ruby-dbi.sourceforge.net/] 
and MySQL/Ruby[http://www.tmtm.org/en/mysql/ruby/] (compatible with
Ruby/MySQL[http://www.tmtm.org/ruby/mysql/README_en.html]), but I expect to add
other adapters shortly. The adapters are less than 100 lines of code fulfilling 
the interface specified by ActiveRecord::ConnectionAdapters::AbstractAdapter.
Writing a new adapter should be a small task.

Note: The DBI adapter currently calls a MySQL-specific function called "last_id".
I'll add some logic to make it call the appropriate function for the other databases
that is supported by DBI shortly. (If you know exactly which for a DBI-supported DB,
please send me a note).


== Philosophy 

Convention over Configuration:
* No XML-files
* Lots of reflection
* Some degree of "magic"

Admit the Database:
* Lets you drop down to SQL for odd cases and performance
* Doesn't attempt to duplicate or replace data definitions


== Download

The latest version of Active Record can be found at

* http://rubyforge.org/project/showfiles.php?group_id=182

Documentation can be found at 

* http://activerecord.rubyforge.org


== Installation

You can install Active Record with the following command.

  % [sudo] ruby install.rb

from its distribution directory.


== License

Active Record is released under the same license as Ruby.


== Support

The Active Record homepage is http://activerecord.rubyforge.org. You can find the Active Record
RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says:

   Feel free to submit commits or feature requests.  If you send a patch,
   remember to update the corresponding unit tests.  If fact, I prefer
   new feature to be submitted in the form of new unit tests.

For other information, feel free to ask on the ruby-talk mailing list
(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.