Updating parent-child objects in a single transaction

Is there any way to update a parent and child object in a single
transaction without having to explicitly demarcate that transaction?
I’ve got the following two objects:

class CostItem < ActiveRecord::Base
has_one :cost :dependent => :destroy
validates_associated :cost
end

class Cost < ActiveRecord::Base
belongs_to :cost_item
validates_numericality_of :amount
end

In my controller, I’ve got the following:

def create
@cost_item = CostItem.new(params[:cost_item])
@cost_item.build_cost(params[:cost])
if @cost_item.save
…
else
# Handle the error
end
end

def update
@cost_item = Item.find(params[:id])
if @cost_item.cost.update_attributes(params[:cost]) &&
@cost_item.update_attributes(params[:cost_item])
…
else
# Handle the error
end
end

The ‘create’ method works transactionally - if either @cost_item or
@cost is invalid, nothing gets saved. However, ‘update’ is not
transactional: if either @cost_item or @cost is invalid, then the
valid one still gets saved.

The only way I can see around this is to put a transaction around it
and use methods that throw an exception to trigger the rollback:

def update
@cost_item = Item.find(params[:id])
begin
CostItem.transaction do
@cost_item.cost.update_attributes!(params[:cost])
@cost_item.update_attributes!(params[:cost_item])
…
end
rescue ActiveRecord::RecordInvalid
# Handle the error
end
end

However this is a bit more verbose than I hoped. Is there any way to
make the updates of related objects fully transactional - in the same
manner as creation - without having to explicitly demarcate the
transaction?

Thanks,

Ben

I am trying to do something very similar and i’ve got update to work,
however rails is failing on my create hierarchical xml. to use you
example, I am sending something like:

<cost_item>
blah
blah

10

</cost_item>

what xml format do you post to get this to work?

Ben T. wrote:

belongs_to :cost_item
else
# Handle the error
end
end

def update
@cost_item = Item.find(params[:id], :include => :cost)
@cost_item.cost.attributes = params[:cost]
if @cost_item.update_attributes(params[:cost_item])
@cost_item.cost.save!
…
else
# Handle the error
end
end

If they’re always updated together you can move the save
of cost to the model:

class CostItem
after_update ‘cost.save!’
end

You should still wrap the two saves in a transaction in
case there are DB problems, but it’s best to avoid using
transactions to recover from validation failures.

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

On 17 Mar 2008, at 15:58, Mark Reginald J. wrote:

transactions to recover from validation failures.
before_save, the actual save and after_save are wrapped in a
transaction for you.

Fred

I’m just using a HTTP POST with parameters, not XML. I’m not sure how
it’d
work with XML.

Cheers,

Ben

On Mon, May 5, 2008 at 3:39 PM, coconut [email protected] wrote:

> has_one :cost :dependent => :destroy > def create > @cost_item = Item.find(params[:id]) > transactional: if either @cost_item or @cost is invalid, then the > @cost_item.update_attributes!(params[:cost_item]) thetransaction? > > Thanks, > > Ben

–
iZepto: Timesheets Made Human
http://izepto.com