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

Ben Teese wrote:

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

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.

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>   <name>blah</name>   <description>blah</description>   <cost>     <amount>10</amount>   </cost> </cost_item>

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

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