[ANN] Active Record 0.8.0: Transaction that!

Discussion in 'Ruby' started by David Heinemeier Hansson, Jun 2, 2004.

  1. What's new in Active Record 0.8.0?
    ==================================

    Transactions are here! This was the last major feature destined for
    inclusion before version 1.0 is released. So I was happy to see that it
    could be done in just a handful of lines. Transactions are guarded by
    blocks and work as such:

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

    This example will only take money from David and give to Mary if
    neither withdrawal nor deposit raises an exception. Exceptions (and
    returning false in the last statement of the transaction block) will
    force a ROLLBACK that returns the database to the state before the
    transaction was begun.

    Be aware, though, that the objects will not have their instance data
    returned to their state before the transaction started. You‘ll have to
    deal with that yourself (just as in the case of Validations). Also have
    in mind that exceptions thrown within a transaction block will be
    propagated (after triggering the ROLLBACK), so you should be ready to
    catch those in your application code.

    Also in 0.8.0:

    * Changed Base.find to also accept either a list (1, 5, 6) or an array
    of ids ([5, 7])
    as parameter and then return an array of objects instead of just an
    object

    * Fixed method has_collection? for has_and_belongs_to_many macro to
    behave as a
    collection, not an association

    * Fixed SQLite adapter so empty or nil values in columns of datetime,
    date, or time type
    aren't treated as current time [Spotted by Gavin Sinclair]

    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("", comment)
    end
    end


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


    * Transaction support
    Account.transaction 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 for three different engines through simple
    adapters
    ActiveRecord::Base.mysql_connection(host, username, pass, database)
    ActiveRecord::Base.postgresql_connection(host, table, username,
    pass, database)
    ActiveRecord::Base.sqlite_connection(dbfile)


    * Logging support for Log4r and Logger


    == 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, Jun 2, 2004
    #1
    1. Advertising

  2. David Heinemeier Hansson

    Kirk Haines Guest

    On Thu, 3 Jun 2004 00:09:23 +0900, David Heinemeier Hansson wrote
    > What's new in Active Record 0.8.0?
    > ==================================
    >
    > Transactions are here! This was the last major feature destined for
    > inclusion before version 1.0 is released. So I was happy to see that
    > it could be done in just a handful of lines. Transactions are
    > guarded by blocks and work as such:
    >
    > Account.transaction do
    > david.withdrawal(100)
    > mary.deposit(100)
    > end
    >
    > This example will only take money from David and give to Mary if
    > neither withdrawal nor deposit raises an exception. Exceptions (and
    > returning false in the last statement of the transaction block) will
    > force a ROLLBACK that returns the database to the state before the
    > transaction was begun.


    Hey, cool. I'll have to compare how you did transactions to how I did them
    in Kansas. Do your transactions rollback the objects as well as the
    database?

    row.savings = 100
    Account.transaction do
    row.savings += income_for_the_day
    ### Oops. Something bad throws an exception.
    end

    will row.savings still have 100 in it after that?


    Kirk Haines
     
    Kirk Haines, Jun 2, 2004
    #2
    1. Advertising

  3. David Heinemeier Hansson

    Kirk Haines Guest

    On Thu, 3 Jun 2004 00:34:10 +0900, Carl Youngblood wrote
    > David Heinemeier Hansson wrote:
    >
    > > Be aware, though, that the objects will not have their instance data

    [WINDOWS-1252?]> > returned to their state before the transaction started.
    You‘ll have
    > > to deal with that yourself (just as in the case of Validations). Also
    > > have in mind that exceptions thrown within a transaction block will
    > > be propagated (after triggering the ROLLBACK), so you should be ready
    > > to catch those in your application code.

    >
    > Is this for performance reasons? It seems like it wouldn't be too
    > hard to undo the changes to instance data if an exception were thrown.


    Oh, duh. Read better, Kirk. Hopefully you disregarded the last question.

    In Kansas I also rollback the objects, but it does introduce some
    interesting wrinkles. I'm playing with the rollback code a bit for the
    upcoming next release. I don't think it'd be very hard to do something
    similar to what I am doing in ActiveRecord, though.


    Kirk Haines
     
    Kirk Haines, Jun 2, 2004
    #3
  4. On Jun 2, 2004, at 9:34 AM, Carl Youngblood wrote:
    > David Heinemeier Hansson wrote:
    >> Be aware, though, that the objects will not have their instance data
    >> returned to their state before the transaction started. You‘ll have
    >> to deal with that yourself (just as in the case of Validations).
    >> Also have in mind that exceptions thrown within a transaction block
    >> will be propagated (after triggering the ROLLBACK), so you should be
    >> ready to catch those in your application code.

    >
    > Is this for performance reasons? It seems like it wouldn't be too
    > hard to undo the changes to instance data if an exception were thrown.


    I could actually see an usefulness to the current
    (non-instance-rollback) mechanism:

    If the transaction fails, your code can look at various instances and
    see which ones worked (the instance is the same as what you attempted
    to set it to) and thus at which point exactly it failed (the instance
    never got set).

    But that's not something I've needed to do, just making up a use case
    to fit the feature. :) And possibly the same information can be
    determined from the exception.


    I love Active Record, and I have even started using it yet :)
     
    Gavin Kistner, Jun 4, 2004
    #4
  5. Gavin Kistner wrote:

    > On Jun 2, 2004, at 9:34 AM, Carl Youngblood wrote:
    >
    >> David Heinemeier Hansson wrote:
    >>
    >>> Be aware, though, that the objects will not have their instance
    >>> data returned to their state before the transaction started. You‘ll
    >>> have to deal with that yourself (just as in the case of
    >>> Validations). Also have in mind that exceptions thrown within a
    >>> transaction block will be propagated (after triggering the
    >>> ROLLBACK), so you should be ready to catch those in your
    >>> application code.

    >>
    >>
    >> Is this for performance reasons? It seems like it wouldn't be too
    >> hard to undo the changes to instance data if an exception were thrown.

    >
    >
    > I could actually see an usefulness to the current
    > (non-instance-rollback) mechanism:
    >
    > If the transaction fails, your code can look at various instances and
    > see which ones worked (the instance is the same as what you attempted
    > to set it to) and thus at which point exactly it failed (the instance
    > never got set).


    Didn't David say that you can still do it the old way by just not
    passing parameters? So your problem is already solved.

    Carl
     
    Carl Youngblood, Jun 4, 2004
    #5
  6. >> I could actually see an usefulness to the current
    >> (non-instance-rollback) mechanism:
    >>
    >> If the transaction fails, your code can look at various instances and
    >> see which ones worked (the instance is the same as what you attempted
    >> to set it to) and thus at which point exactly it failed (the instance
    >> never got set).

    >
    > Didn't David say that you can still do it the old way by just not
    > passing parameters? So your problem is already solved.


    Right. If you don't want to bother with the objects transactions, just
    don't pass any paramenters to the transaction method. This can also be
    used to be selective on what objects you want to play transactions on.
    It's not an all or nothing choice.
    --
    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
     
    David Heinemeier Hansson, Jun 4, 2004
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Michael Post
    Replies:
    5
    Views:
    1,126
    Arne Vajhøj
    Aug 22, 2009
  2. Vencz Istv?n
    Replies:
    2
    Views:
    293
  3. David Heinemeier Hansson
    Replies:
    2
    Views:
    175
    Michael Neumann
    May 31, 2004
  4. David Heinemeier Hansson
    Replies:
    2
    Views:
    178
    Kevin Bullock
    Jun 3, 2004
  5. David Heinemeier Hansson

    [ANN] Active Record 0.8.2: Inheritable callback ques

    David Heinemeier Hansson, Jun 4, 2004, in forum: Ruby
    Replies:
    12
    Views:
    212
    Kirk Haines
    Jun 5, 2004
Loading...

Share This Page