[ANN] Active Record 0.8.0: Transaction that!

  • Thread starter David Heinemeier Hansson
  • Start date
D

David Heinemeier Hansson

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("(e-mail address removed)", 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
 
K

Kirk Haines

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
 
K

Kirk Haines

Be aware, though, that the objects will not have their instance data
[/QUOTE]
[WINDOWS-1252?]> > returned to their state before the transaction started.
You‘ll have
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
 
G

Gavin Kistner

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 :)
 
C

Carl Youngblood

Gavin said:
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
 
D

David Heinemeier Hansson

I could actually see an usefulness to the current
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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top