[ANN] Active Record 0.8.3: Modules, mapping, and transactions

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

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

    Active Records in modules are now treated nicely by both the mapping
    and association logic. The mapping can be more finely controlled with
    options for different primary keys than “id” and for non-integer keys.
    The dependent option on associations are now treated as a transaction.
    And many other changes:


    Transactions
    ------------

    * Added transactional protection for destroy
    (important for the new :dependent option) [Suggested by Carl
    Youngblood]

    * Fixed so transactions are ignored on MyISAM tables for MySQL
    (use InnoDB to get transactions)

    * Changed transactions so only exceptions will cause a rollback, not
    returned false.


    Mapping
    -------

    * Added support for non-integer primary keys [Aredridel/earlier work by
    Michael Neumann]

    User.find "jdoe"
    Product.find "PDKEY-INT-12"

    * Added option to specify naming method for primary key column.
    ActiveRecord::Base.primary_key_prefix_type can either be set to nil,
    :table_name, or
    :table_name_with_underscore. :table_name will assume that Product
    class has a primary key
    of "productid" and :table_name_with_underscore will assume
    "product_id".
    The default nil will just give "id".

    * Added an overwriteable primary_key method that'll instruct AR to the
    name of the
    id column [Aredridele/earlier work by Guan Yang]

    class Project < ActiveRecord::Base
    def self.primary_key() "project_id" end
    end

    * Fixed that Active Records can safely associate inside and out of
    modules.

    class MyApplication::Account < ActiveRecord::Base
    has_many :clients # will look for MyApplication::Client
    has_many :interests, :class_name => "Business::Interest" # will
    look for Business::Interest
    end

    * Fixed that Active Records can safely live inside modules [Aredridel]

    class MyApplication::Account < ActiveRecord::Base
    end


    Misc
    ----

    * Added freeze call to value object assignments to ensure they remain
    immutable
    [Spotted by Gavin Sinclair]

    * Changed interface for specifying observed class in observers. Was
    OBSERVED_CLASS constant,
    now is observed_class() class method. This is more consistant with
    things like
    self.table_name(). Works like this:

    class AuditObserver < ActiveRecord::Observer
    def self.observed_class() Account end
    def after_update(account)
    AuditTrail.new(account, "UPDATED")
    end
    end

    [Suggested by Gavin Sinclair]

    * Create new Active Record objects by setting the attributes through a
    block. Like this:

    person = Person.new do |p|
    p.name = 'Freddy'
    p.age = 19
    end

    [Suggested by Gavin Sinclair]

    Get the new version on http://activerecord.rubyonrails.org or, even
    better, type "gem -Ri activerecord"
    when the 0.8.3 version appears on the "gem -Rl" list (it should very
    soon).


    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("", 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, Jun 9, 2004
    #1
    1. Advertising

  2. David Heinemeier Hansson wrote:
    > What's new in Active Record 0.8.3?
    > ==================================
    >
    > Active Records in modules are now treated nicely by both the mapping
    > and association logic. The mapping can be more finely controlled with
    > options for different primary keys than “id” and for non-integer keys.
    > The dependent option on associations are now treated as a transaction.
    > And many other changes:
    >
    >
    > Transactions
    > ------------
    >
    > * Added transactional protection for destroy
    > (important for the new :dependent option) [Suggested by Carl Youngblood]
    >
    > * Fixed so transactions are ignored on MyISAM tables for MySQL
    > (use InnoDB to get transactions)
    >
    > * Changed transactions so only exceptions will cause a rollback, not
    > returned false.
    >
    >
    > Mapping
    > -------
    >
    > * Added support for non-integer primary keys [Aredridel/earlier work by
    > Michael Neumann]
    >
    > User.find "jdoe"
    > Product.find "PDKEY-INT-12"
    >
    > * Added option to specify naming method for primary key column.
    > ActiveRecord::Base.primary_key_prefix_type can either be set to nil,
    > :table_name, or
    > :table_name_with_underscore. :table_name will assume that Product
    > class has a primary key
    > of "productid" and :table_name_with_underscore will assume "product_id".
    > The default nil will just give "id".
    >



    Hi,

    Just tested it. It works find when you want only on object return, but fails when you do this:

    ua = User.find(8,9,10)

    The cause, from the output I got, is that the field used to order the returned rows is still name 'id'

    Raph
    Raphael Bauduin, Jun 9, 2004
    #2
    1. Advertising

  3. David Heinemeier Hansson

    Kirk Haines Guest

    On Wed, 9 Jun 2004 21:15:37 +0900, David Heinemeier Hansson wrote

    > * Fixed so transactions are ignored on MyISAM tables for MySQL
    > (use InnoDB to get transactions)


    David, I'm curious about this design decision. I went the exact opposite
    direction with transactions in Kansas. Regardless of whether transactions
    are implemented at the DBD layer or by the database, transactions are always
    implemented at the object level. I find it interesting that you went the
    other way, and am curious about the reasoning behind it?


    Thanks. Active Record looks like it is shaping up nicely!


    Kirk Haines
    Kirk Haines, Jun 9, 2004
    #3
  4. Kirk Haines wrote:

    >On Wed, 9 Jun 2004 21:15:37 +0900, David Heinemeier Hansson wrote
    >
    >>* Fixed so transactions are ignored on MyISAM tables for MySQL
    >> (use InnoDB to get transactions)
    >>
    >>

    >
    >David, I'm curious about this design decision. I went the exact opposite
    >direction with transactions in Kansas. Regardless of whether transactions
    >are implemented at the DBD layer or by the database, transactions are always
    >implemented at the object level. I find it interesting that you went the
    >other way, and am curious about the reasoning behind it?
    >

    Another option worth considering is using LOCK TABLES for MyISAM
    tables. MySQL's argument on this is that the MyISAM engine is about 3
    times faster than the transactional engines and for those times when you
    need more than one query to accomplish your goals, locking the tables
    doesn't cause you to incur a large performance penalty. It still
    doesn't protect you from a server outage or something but otherwise it
    works pretty well.

    I can imagine some scenarios where you might end up with inconsistent
    data if you don't lock tables before performing a series of queries.

    Carl
    Carl Youngblood, Jun 9, 2004
    #4
  5. > David, I'm curious about this design decision. I went the exact
    > opposite
    > direction with transactions in Kansas. Regardless of whether
    > transactions
    > are implemented at the DBD layer or by the database, transactions are
    > always
    > implemented at the object level. I find it interesting that you went
    > the
    > other way, and am curious about the reasoning behind it?


    Oh, this was referring to database transactions. Before, if you
    attempted to create a database transaction block for MyISAM tables, it
    would bork. Now it'll just ignore MySQL's complaints.

    The object transactions are separate from this and require that you
    name the objects going into that with the transaction call.

    > Thanks. Active Record looks like it is shaping up nicely!


    Thank you..
    --
    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 9, 2004
    #5
  6. > Just tested it. It works find when you want only on object return, but
    > fails when you do this:
    >
    > ua = User.find(8,9,10)
    >
    > The cause, from the output I got, is that the field used to order the
    > returned rows is still name 'id'


    Good spotting! This fix is in the CVS and will be part of 0.8.4.
    --
    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 9, 2004
    #6
  7. David Heinemeier Hansson

    Kirk Haines Guest

    On Thu, 10 Jun 2004 04:30:27 +0900, David Heinemeier Hansson wrote

    > Oh, this was referring to database transactions. Before, if you
    > attempted to create a database transaction block for MyISAM tables,
    > it would bork. Now it'll just ignore MySQL's complaints.
    >
    > The object transactions are separate from this and require that you
    > name the objects going into that with the transaction call.


    Ah! I follow you.

    Was the decision to do it that way mainly to make it easy to just use
    Transaction::Simple?

    To my mind, if one wants to perform a transaction, it is safer to just have
    everything that could be under the transaction under it. I look at it as a
    parallel structure sort of thing. At the database level, a transaction is a
    transaction for everything. So, at the object level, when a transaction is
    entered, all of the objects are part of that transaction. There is also a
    database transaction that is running below that at the same time. That way,
    if one wants to directly write some SQL and bypass the normal object methods
    of doing things, that SQL still executes within the context of the
    transaction.

    Is there a benefit to being able to pick and choose which objects are part
    of the transaction and which are not that I am being too dense to see right
    now? What are your thoughts?


    Thanks,

    Kirk
    Kirk Haines, Jun 9, 2004
    #7
  8. > Is there a benefit to being able to pick and choose which objects are
    > part
    > of the transaction and which are not that I am being too dense to see
    > right
    > now? What are your thoughts?


    It could very well be that you're interested in the data that cause the
    invalidation. So let's imagine that name = "David" will cause the
    transaction to fail. I'd really to be able to access that value
    afterwards. This is how the validations stuff in AR works as well. So
    attempt to validate the data, if it fails, you can present the wrong
    data for correction.

    That and, personally, I don't really need the object transactions most
    of the time. So it's nice to be able to turn it off by not specifically
    requiring it. Transactions are not free of performance penalties (as
    Austin mentioned).
    --
    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 9, 2004
    #8
  9. David Heinemeier Hansson

    Kirk Haines Guest

    On Thu, 10 Jun 2004 04:52:00 +0900, David Heinemeier Hansson wrote

    > It could very well be that you're interested in the data that cause
    > the invalidation. So let's imagine that name = "David" will cause
    > the transaction to fail. I'd really to be able to access that value
    > afterwards. This is how the validations stuff in AR works as well.
    > So attempt to validate the data, if it fails, you can present the
    > wrong data for correction.


    If a transaction fails because of an exception, the exception can be caught
    before the rollback, though. And if the rollback is an explicit one -- the
    code decides that something is wrong and calls a rollback before any
    exception is encountered -- then one can choose to do something with the
    incorrect value before the rollback.

    > That and, personally, I don't really need the object transactions
    > most of the time. So it's nice to be able to turn it off by not
    > specifically requiring it. Transactions are not free of performance
    > penalties (as Austin mentioned).


    I'm finding that object transactions are handy for performance and/or for
    validation. If one is making a lot of changes to an ORM object, but it's
    only the final value that is really of long term interest, wrapping
    everything in a transaction means that only the final value makes its way to
    the db. Or, if the values in the object are subject to user input and
    manipulation, you probably don't want to write that data out to the database
    until it has been validated. So while I wasn't sure when I implemented
    them, I'm finding myself using them all over the place.

    Performance...Hmmm. There is an overhead to transactions. That's one of
    the things I have been working on minimizing, myself. Can't really counter-
    argue you there. :)

    Here's another question -- is there any way to do transactions over
    object/row insertions with ActiveRecord? i.e. start a transaction; do
    something that creates a new object/row; incur and exception or decide that
    there is a problem and rollback; table/objects are unchanged. No? Maybe?
    Yes? My gut sense is that you can't because you do have to specify the
    objects to have transactions applied to them when entering the transaction
    block, and if the object doesn't exist then, you can't do that. However, am
    I wrong?


    Thanks again. This is interesting stuff.


    Kirk Haines
    Kirk Haines, Jun 9, 2004
    #9
  10. David Heinemeier Hansson

    John W. Long Guest

    David Heinemeier Hansson wrote:
    > * Create new Active Record objects by setting the attributes through a
    > block. Like this:
    >
    > person = Person.new do |p|
    > p.name = 'Freddy'
    > p.age = 19
    > end


    Why is the better than:

    person = Person.new(
    'name' => 'David',
    'age' => 12
    )

    --
    John
    John W. Long, Jun 10, 2004
    #10
  11. David Heinemeier Hansson

    Jim Freeze Guest

    On Thursday, 10 June 2004 at 12:46:09 +0900, John W. Long wrote:
    > David Heinemeier Hansson wrote:
    > >* Create new Active Record objects by setting the attributes through a
    > >block. Like this:
    > >
    > > person = Person.new do |p|
    > > p.name = 'Freddy'
    > > p.age = 19
    > > end

    >
    > Why is the better than:
    >
    > person = Person.new(
    > 'name' => 'David',
    > 'age' => 12
    > )
    >


    Maybe because you have more freedom to do complex operations like this:

    > > person = Person.new do |p|
    > > p.name = 'Freddy'

    ... code here to compute age
    > > p.age = age
    > > end

    >


    --
    Jim Freeze
    ... A solemn, unsmiling, sanctimonious old iceberg who looked like he
    was waiting for a vacancy in the Trinity.
    -- Mark Twain
    Jim Freeze, Jun 10, 2004
    #11
  12. John W. Long wrote:
    >> * Create new Active Record objects by setting the attributes through a
    >> block. Like this:
    >>
    >> person = Person.new do |p|
    >> p.name = 'Freddy'
    >> p.age = 19
    >> end

    >
    > Why is the better than:
    >
    > person = Person.new(
    > 'name' => 'David',
    > 'age' => 12
    > )


    It's easier to type for a start. Although the aesthetics of it are
    subjective, it has some general advantages as an idiom:

    * #initialize doesn't have to do any hash processing to support it

    * since you're calling methods, the behaviour of 'name=' is respected

    * it's self-documenting; you look at the list of attributes of the class
    and know what you can specify in the constructor

    As it happens, none of these advantages apply to ActiveRecord: the method
    has to do hash processing anyway, there is no method name= (it's a shallow
    method_missing trick), and no attributes are documented.

    But it's IMO a good idiom and I'm happy to see it apply to ActiveRecord.
    Note that it takes a single line of code to support:

    yielf self if block_given?

    Gavin
    Gavin Sinclair, Jun 10, 2004
    #12
    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. David C. Barber
    Replies:
    3
    Views:
    549
  2. David Heinemeier Hansson
    Replies:
    2
    Views:
    172
    Michael Neumann
    May 31, 2004
  3. David Heinemeier Hansson

    [ANN] Active Record 0.8.0: Transaction that!

    David Heinemeier Hansson, Jun 2, 2004, in forum: Ruby
    Replies:
    5
    Views:
    107
    David Heinemeier Hansson
    Jun 4, 2004
  4. David Heinemeier Hansson
    Replies:
    2
    Views:
    177
    Kevin Bullock
    Jun 3, 2004
  5. David Heinemeier Hansson
    Replies:
    3
    Views:
    123
    julian
    Mar 28, 2006
Loading...

Share This Page