Need advice: transaction block for saving?

I am building an app to allow clients to create a project and fill out
a survey. There are also contractors and hiring_managers associated
with the project. Below are the models:

class User < ActiveRecord::Base
has_many :projects_as_client, :class_name=>
‘Project’, :foreign_key => ‘client_id’
has_many :projects_as_contractor, :class_name=>
‘Project’, :foreign_key => ‘contractor_id’
has_many :projects_as_hiring_manager, :class_name=>
‘Project’, :foreign_key => ‘hiring_manager_id’
end

class Project < ActiveRecord::Base
belongs_to :client, :class_name => ‘User’, :foreign_key =>
‘client_id’
belongs_to :contractor, :class_name => ‘User’, :foreign_key =>
‘contractor_id’
belongs_to :hiring_manager, :class_name => ‘User’, :foreign_key
=> ‘hiring_manager_id’

When I create the project, I may also be creating a contractor and
hiring_manager, etc. What is the best way to handle this? I read about
using something like this:

@project=Project.new(params[:user])
@project.contractor = @project.contractor.build(params[:user])

Unfortunately, that also gives me an Unknown Method error. A friend
suggested using a transaction block so that if there is an error, all
saves/updates would be rolled back.

Thanks in advance from a newbie!

Dcjgoose wrote:

belongs_to :client, :class_name => ‘User’, :foreign_key =>

@project=Project.new(params[:user])
@project.contractor = @project.contractor.build(params[:user])

Unfortunately, that also gives me an Unknown Method error. A friend
suggested using a transaction block so that if there is an error, all
saves/updates would be rolled back.

It’s best not to rely on rollback, both for efficiency and in case
the DB doesn’t support transactions or suitable transaction isolation.

Correct way to write the above line is

@project.contractor = @project.build_contractor(params[:user])

Just ensure everything is validated before anything is saved.


We develop, watch us RoR, in numbers too big to ignore.

On 8/17/07, Mark Reginald J. [email protected] wrote:

Dcjgoose wrote:

[…] A friend
suggested using a transaction block so that if there is an error, all
saves/updates would be rolled back.

It’s best not to rely on rollback, both for efficiency and in case
the DB doesn’t support transactions or suitable transaction isolation.

I’m sorry, but this is shitty advice.

Transactions are the only way to avoid partial updates. They also make
your program flow a lot simpler than trying to without them (which
doesn’t really work anyway).

Any db worth using supports transactions, and performance tends to be
worse in auto-commit mode, when every single statement is run as a
separate transaction, instead of bulking all writes into a single
commit.

Granted, the most popular ‘database’, mysql, takes a bit of know-how
and reconfiguration to support transactions andact somewhat
well-behaved.
I recommend PostgreSQL instead; not only is it superior in every
respect, but also easier to get started with…!

Isak

Isak H. wrote:

Transactions are the only way to avoid partial updates. They also make
your program flow a lot simpler than trying to without them (which
doesn’t really work anyway).

A transaction block should always be used when an action saves
multiple objects so that save errors unrelated to validation do not
result in partial inserts/updates on DBs that support transactions.
Indeed Rails automatically wraps saves of associated objects in
transactions.

But it’s not that hard to validate everything before saving anything,
avoiding the latency of all those useless database calls, reducing
server load and returning error messages to the user much faster.


We develop, watch us RoR, in numbers too big to ignore.

Just to reiterate the point I wrote a lot of code for MySQL before it
hadd InnoDB support that looked like the following psuedo code:

exec_sql "Insert into table1 values (‘a’, ‘b’, ‘c’)
my_id = exec_sql “select last_id from db”

exec_sql "Insert into table2 values (my_id, 1, 2, 3)
next_id = exec_sql “select last_id from db”

if $previous_query_error
exec_sql(“Delete from table1 where id = $my_id”)
else
exec_sql insert into table3 values (next_id, ‘done’)
if $previous_query_error
exec_sql(“Delete from table2 where id = $next_id”)
exec_sql(“Delete from table1 where id = $my_id”)
end
end

This is what you are forced to do without transactions. That kind of
code adds no value to your application. And it works assuming that
there isn’t a problem executing the clean-up code and orphaned child
records are left around. In addition transactions can improve
performance. If you are inserting/updating multiple records the
writing to the database is essentially (at the 30,000 foot level)
postponed until the end of the transaction. That’s why tools like
Oracle data loader allow you to specify transaction batch sizes, to
optimize the speed of the load against the space/memory available for
transactions.

MySQL
PostgreSQL
SQL Server
Oracle
Sybase

All support transactions, and they cover the vast majority of the DB
market.

The difficulty comes when you’re using something that doesn’t support
rollback, like a LDAP server. In which case you are still required
to write clean-up code.