[ANN] Active Record 0.8.1: Object-level transactions, generic connector

What's new in Active Record 0.8.1?

···

==================================

The release of 0.8.0 prompted a discussion of object-level transactions and the Transaction::Simple library by Austin Ziegler was mentioned. Thanks to this wonderful library, Active Record now supports object-level transactions on top of the database-backed ones. You just have to name the active records that you want to place under object-transactions, like this:

     Account.transaction(david, mary) do
       david.withdrawal(100)
       mary.deposit(100)
     end

On exception or return of false, both the database and the objects will be rolled back to their pre-transactional state. It's entirely optional to use the object-transaction. Just don't pass any active records to the transaction method.

This release also features a new generic connection method, which breaks the interface of the old style. So be sure to update. Two examples:

   ActiveRecord::Base.establish_connection(:adapter => "sqlite", :dbfile => "dbfile")

   ActiveRecord::Base.establish_connection(
     :adapter => "mysql",
     :host => "localhost",
     :username => "me",
     :password => "secret",
     :database => "activerecord"
   )

Read more in:
http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000081

Also in 0.8.1:

* Fixed SQLite adapter so objects fetched from has_and_belongs_to_many have proper
   attributes (t.name is now name). [Spotted by Garrett Rooney]

* Fixed SQLite adapter so dates are returned as Date objects, not Time objects
   [Spotted by Gavin Sinclair]

* Fixed requirement of date class, so date conversions are succesful regardless of
   whether you manually require date or not.

Get the release and read more at http://activerecord.rubyonrails.org/

Call for help!

Do you have working knowledge with and access to either Oracle, ODBC, Sybase, or DB2, I'd be really grateful if you would consider writing an adapter for Active Record. Adapters are usually just around 100 lines of code. You'll have three examples to look at, a well-specified interface[1], and almost 100 test cases to make it real easy. Luke Holden reports that he spent just a few hours getting SQLite and PostgreSQL adapters working.

[1] http://ar.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/AbstractAdapter.html

Active Record -- Object-relation mapping put on rails

Active Record connects business objects and database tables to create a persistable
domain model where logic and data is presented in one wrapping. It's an implementation of the object-relational mapping (ORM) pattern by the same name as described by Martin Fowler:

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

Active Records main contribution to the pattern is to relieve the original of two stunting problems: lack of associations and inheritance. By adding a simple domain language-like set of macros to describe the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the gap of functionality between the data mapper and active record approach.

A short rundown of the major features:

* Automated mapping between classes and tables, attributes and columns.
    class Product < ActiveRecord::Base; end

    ...is automatically mapped to the table named "products", such as:

    CREATE TABLE products (
      id int(11) NOT NULL auto_increment,
      name varchar(255),
      PRIMARY KEY (id)
    );

    ...which again gives Product#name and Product#name=(new_name)

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

* Aggregations of value objects controlled by simple meta-programming macros.
    class Account < ActiveRecord::Base
      composed_of :balance, :class_name => "Money",
                  :mapping => %w(balance amount)
      composed_of :address,
                  :mapping => [%w(address_street street), %w(address_city city)]
    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

* Callbacks on the entire lifecycle (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

* Observers for the entire lifecycle
    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

* Transaction support on both a database and object level. The latter is implemented
   by using Transaction::Simple

     # Just database transaction
     Account.transaction do
       david.withdrawal(100)
       mary.deposit(100)
     end

     # Database and object transaction
     Account.transaction(david, mary) do
       david.withdrawal(100)
       mary.deposit(100)
     end

* Direct manipulation (instead of service invocation)

   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

* Database abstraction through simple adapters (~100 lines) with a shared connector

    ActiveRecord::Base.establish_connection(:adapter => "sqlite", :dbfile => "dbfile")

    ActiveRecord::Base.establish_connection(
      :adapter => "mysql",
      :host => "localhost",
      :username => "me",
      :password => "secret",
      :database => "activerecord"
    )

* Logging support for Log4r and Logger

     ActiveRecord::Base.logger = Logger.new(STDOUT)
     ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")

Philosophy

Active Record attempts to provide a coherent wrapping for the inconvenience that is object-relational mapping. The prime directive for this mapping has been to minimize the amount of code needed to built a real-world domain model. This is made possible by relying on a number of conventions that make it easy for Active Record to infer complex relations and structures from a minimal amount of explicit direction.

Convention over Configuration:
* No XML-files!
* Lots of reflection and run-time extension
* Magic is not inherently a bad word

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

David Heinemeier Hansson wrote:

What's new in Active Record 0.8.1?

The release of 0.8.0 prompted a discussion of object-level transactions and the Transaction::Simple library by Austin Ziegler was mentioned. Thanks to this wonderful library, Active Record now supports object-level transactions on top of the database-backed ones. You just have to name the active records that you want to place under object-transactions, like this:

    Account.transaction(david, mary) do
      david.withdrawal(100)
      mary.deposit(100)
    end

I am so excited to try this out! Thanks for a great tool David and associates! I can't wait for Ruby on Rails.

Carl

Excellent work so far, David. I've been playing around with ActiveRecord, (re-)building a web application (re- because I started building it during our spring semester, although it was never in a working or complete state) using AR to map to the database.

I tried installing from the tarball, and got the following error:

% irb1.8 -r active_record
/usr/local/stow/ruby-1.8.1/lib/ruby/site_ruby/1.8/active_record/transactions.rb:1:in `require': No such file to load -- active_record/vendor/simple.rb (LoadError)
         from /usr/local/stow/ruby-1.8.1/lib/ruby/site_ruby/1.8/active_record/transactions.rb:1

Digging a little deeper, it appears that install.rb just neglects to install simple.rb. Here's a patch:

8<----- cut here
--- install.rb.old Thu Jun 3 17:48:27 2004
+++ install.rb Thu Jun 3 17:48:55 2004
@@ -42,6 +42,7 @@
   active_record/support/class_attribute_accessors.rb
   active_record/support/class_inheritable_attributes.rb
   active_record/vendor/mysql.rb
+ active_record/vendor/simple.rb

···

-

  # the acual gruntwork
8<----- cut here

Keep up the good work—like many folks, I'm really excited to see the other half of Rails.

Pacem in terris / Mir / Shanti / Salaam / Heiwa
Kevin R. Bullock