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:

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.

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.

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

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.

Isak Hansen 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.