[ANN] Active Record 0.8.2: Inheritable callback ques

What's new in Active Record 0.8.2?

···

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

Besides the overwriteable callback methods, it's now also possible to register callbacks through the use of the callback macros. Their main advantage is that the macros add behavior into a callback que that is kept intact down through an inheritance hierarchy. Example:

   class Topic < ActiveRecord::Base
     before_destroy :destroy_author
   end

   class Reply < Topic
     before_destroy :destroy_readers
   end

Now, when Topic#destroy is run only destroy_author is called. When Reply#destroy is run both destroy_author and destroy_readers is called. Contrast this to the situation where we've implemented the save behavior through overwriteable methods:

   class Topic < ActiveRecord::Base
     def before_destroy() destroy_author end
   end

   class Reply < Topic
     def before_destroy() destroy_readers end
   end

In that case, Reply#destroy would only run destroy_readers and _not_ destroy_author. So use the callback macros when you want to ensure that a certain callback is called for the entire hierarchy and the regular overwriteable methods when you want to leave it up to each descendent to decide whether they want to call super and trigger the inherited callbacks.

Additionally, these new callback macros will accept method fragments, which will be evaluated with the binding of the callback spot.

All this might seem pretty hard to understand why it's really cool. That's until you see the first application of it:

* Added :dependent option to has_many and has_one, which will automatically destroy associated objects when the holder is destroyed:

     class Album < ActiveRecord::Base
       has_many :tracks, :dependent => true
     end

   All the associated tracks are destroyed when the album is.

Pretty neat, hu?

Read more about inheritable callback ques:
http://ar.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Read more about dependent option for has_many/one:
http://ar.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M000005

Also in 0.8.2:

* Added Base.create as a factory that'll create, save, and return a new object in
   one step.

* Automatically convert strings in config hashes to symbols for the _connection methods.
   This allows you to pass the argument hashes directly from yaml. (Luke)

* Fixed the install.rb to include simple.rb [Spotted by Kevin Bullock]

* Modified block syntax to better follow our code standards outlined in
   http://rails.rubyonrails.org/show/CodingStandards

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

Hang out with the Ruby on Rails crowd

Come by the IRC channel #rubyonrails on Freenode. Design decisions are aired here and you'll be able to ask questions about Active Record and the framework in general. Oh, and we're really friendly too!

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 as methods or ques 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

    class Account < ActiveRecord::Base
      after_find :eager_load, 'self.class.announce(#{id})'
    end

   Learn more in link:classes/ActiveRecord/Callbacks.html

* 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.2?

[snip]

Hi,

I just tested it and it seems really easy to link Classes to tables. However, I encountered
a blocking problem: it seems the column that's the primary key has to be named id. From the code
in the docs (http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000057\) it seems this is hardcoded.
Is it planned to have the name of the "id column" extracted from the database, or at least to let the user specify a name. I have none of my columns name id.... :slight_smile:

I hope there's a way to do what I describe, as Active Record seems to be really cool.

Raph

David Heinemeier Hansson wrote:

All this might seem pretty hard to understand why it's really cool. That's until you see the first application of it:

* Added :dependent option to has_many and has_one, which will automatically destroy associated objects when the holder is destroyed:

    class Album < ActiveRecord::Base
      has_many :tracks, :dependent => true
    end

  All the associated tracks are destroyed when the album is.

Pretty neat, hu?

This all does look pretty cool, because it has the potential to make application development truly rapid. I do have some concerns about performance though. How does this approach compare with an ON DELETE CASCADE, for example? It seems like if you kept more of the application logic in the database things would run much faster. On the other hand, this approach also has some definite drawbacks.

Carl

David Heinemeier Hansson wrote:

All this might seem pretty hard to understand why it's really cool. That's until you see the first application of it:

* Added :dependent option to has_many and has_one, which will automatically destroy associated objects when the holder is destroyed:

    class Album < ActiveRecord::Base
      has_many :tracks, :dependent => true
    end

  All the associated tracks are destroyed when the album is.

One other question: Are these operations atomic? Does a transaction get started before the cascading deletes occur and end when they are finished? Because otherwise you could have orphaned rows if the server gets hosed while you are still in the process of deleting something's children.

Raphael Bauduin wrote:

I just tested it and it seems really easy to link Classes to tables. However, I encountered
a blocking problem: it seems the column that's the primary key has to be named id. From the code
in the docs (http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000057\) it seems this is hardcoded.
Is it planned to have the name of the "id column" extracted from the database, or at least to let the user specify a name. I have none of my columns name id.... :slight_smile:

I usually prefer to name my primary ID columns <tablename>id just to avoid confusion. For example, the primary key of the user table is called userid. On many-table JOINS it gets confusing to have a lot of IDs floating around. So I agree that being able to change the id column to something else would be handy. Another thing that some people might be confused about is when you have a table whose primary key consists of multiple columns that are combinatorially unique. In these cases I think the solution is to add an integer primary key and use it for the ID column, and then make a unique index across the fields that you wish to be combinatorially unique.

Carl

One other question: Are these operations atomic? Does a transaction get started before the cascading deletes occur and end when they are finished? Because otherwise you could have orphaned rows if the server gets hosed while you are still in the process of deleting something's children.

They will be in 0.8.3 ;). It's in the CVS right now. I'll probably follow the pattern of late and release 0.8.3 version tomorrow. Lots of good new stuff in it.

···

--
David Heinemeier Hansson,
http://www.instiki.org/ -- A No-Step-Three Wiki in Ruby
http://www.basecamphq.com/ -- Web-based Project Management
http://www.loudthinking.com/ -- Broadcasting Brain
http://www.nextangle.com/ -- Development & Consulting Services

Couldn't the table meta data be used to find out which field(s) is / are part of the primary key?

Regards,

Peter

I still haven't delved into ActiveRecord's code as much as I want to, but in
theory these things should be supportable.

Kansas, which is similar to ActiveRecord but takes a few different
approaches, allows things like this:

create table jukebox (
  id int unsigned primary key,
  serial_number int unsigned unique,
  blah blah blah)

create table albums (
  id int unsigned primary key,
  jukebox_serial int,
  blah blah blah)

create table tracks (
  id int unsigned primary key,
  album_id int,
  blah blah blah)

class Jukebox
  to_many(:albums, :Albums, :jukebox_serial, :serial_number)
end

class Albums
  to_many(:tracks, :Tracks, :album_id)
  belongs_to(:jukebox, :Jukebox, :serial_number, :jukebox_serial)
end

class Tracks
  belongs_to(:album, :Albums, :album_id)
end

So, a Jukebox has many Albums. However, instead of using Jukebox's primary
key, serial_number on the Jukebox maps to jukebox_serial, which is simply a
unique key, on the Album.

Albums have many tracks, and are correlated through the primary key on Album
and the album_id field on Track. An Album also belongs to a Jukebox, and is
correlated via the jukebox_serial field on the Album and the serial_number
field on the Jukebox.

Tracks belong to an Album, and are correlated via the album_id field of the
Track to the primary key of the Album.

I don't yet have complete support for multiple field primary keys, but
that's not far off. There is a release with some added documentation
explaining the stuff above, among other things, coming hopefully by the end
of the day.

Kirk Haines

···

On Sat, 5 Jun 2004 00:41:55 +0900, Carl Youngblood wrote

I usually prefer to name my primary ID columns <tablename>id just to
avoid confusion. For example, the primary key of the user table is
called userid. On many-table JOINS it gets confusing to have a lot
of IDs floating around. So I agree that being able to change the id
column to something else would be handy. Another thing that some
people might be confused about is when you have a table whose
primary key consists of multiple columns that are combinatorially
unique. In these cases I think the solution is to add an integer
primary key and use it for the ID column, and then make a unique
index across the fields that you wish to be combinatorially unique.

Raphael Bauduin wrote:

I just tested it and it seems really easy to link Classes to tables.
However, I encountered
a blocking problem: it seems the column that's the primary key has to
be named id. From the code
in the docs
(http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000057\) it
seems this is hardcoded.
Is it planned to have the name of the "id column" extracted from the
database, or at least to let the user specify a name. I have none of
my columns name id.... :slight_smile:

Attached is a patch that adds a primary key class method, and alters the
code to use it:

class User < ActiveRecord::Base
  def self.primary_key
    "userid"
  end
end

User.find("jdoe")

It removes support for automatically calling to_i on key parameters, and
instead passes them to the database as strings, so it may interpret as
it sees fit. Also added is a little extra debugging for the internal
calls to eval.

Ari

ar-pkey.patch (15.3 KB)

Peter C. Verhage wrote:

Couldn't the table meta data be used to find out which field(s) is / are part of the primary key?

That's what I meant by 'having the name of the "id column" extracted from the database'. That would be the best solution IMHO, but do all database drivers support that?

If it's possible, it would really be in the Active Record way of working (from what I've seen): extract the most information possible directly from the database.

Raph

···

Regards,

Peter

Attached is a patch that adds a primary key class method, and alters the
code to use it:

Great patch, Ari! Experimentors beware, though. This patch has a few bugs, but all the known ones have been ironed out in the CVS version of Active Record. We're adding some more tests to make sure that everything works as it should. All of this stuff will be in 0.8.3 as well.

···

--
David Heinemeier Hansson,
http://www.instiki.org/ -- A No-Step-Three Wiki in Ruby
http://www.basecamphq.com/ -- Web-based Project Management
http://www.loudthinking.com/ -- Broadcasting Brain
http://www.nextangle.com/ -- Development & Consulting Services

Most of them support it. That's how Kansas works.

Kirk Haines

···

On Sat, 5 Jun 2004 21:38:37 +0900, Bauduin Raphael wrote

That's what I meant by 'having the name of the "id column" extracted
from the database'. That would be the best solution IMHO, but do all
database drivers support that?

If it's possible, it would really be in the Active Record way of
working
(from what I've seen): extract the most information possible
directly from the database.

Yeah, sorry for the low quality of the patch -- it's from my development
copy, with the irrelevant changes half picked out by hand.
Cherry-picking from patches could be a lot easier than it is with CVS...

Ari

···

On Sat, 2004-06-05 at 07:25 +0900, David Heinemeier Hansson wrote:

> Attached is a patch that adds a primary key class method, and alters
> the
> code to use it:

Great patch, Ari! Experimentors beware, though. This patch has a few
bugs, but all the known ones have been ironed out in the CVS version of
Active Record. We're adding some more tests to make sure that
everything works as it should. All of this stuff will be in 0.8.3 as
well.