Транзакция Rails: сохранение данных в нескольких моделях - PullRequest
12 голосов
/ 18 марта 2010

моих моделей

class Auction
  belongs_to :item
  belongs_to :user, :foreign_key => :current_winner_id
  has_many :auction_bids

end

class User
  has_many :auction_bids

end

class AuctionBid
  belongs_to :auction
  belongs_to :user

end

текущее использование

На странице отображается аукцион, пользователь вводит сумму и нажимает ставку. Код контроллера может выглядеть примерно так:

class MyController
  def bid
    @ab = AuctionBid.new(params[:auction_bid])
    @ab.user = current_user
    if @ab.save
      render :json => {:response => 'YAY!'}
    else
      render :json => {:response => 'FAIL!'}
    end
  end 
end

желаемая функциональность

Пока это прекрасно работает! Тем не менее, я должен обеспечить пару других вещей.

  1. @ab.auction.bid_count необходимо увеличить на единицу.
  2. @ab.user.bid_count нужно увеличить на единицу
  3. @ab.auction.current_winner_id необходимо установить на @ab.user_id

То есть User и Auction, связанные с AuctionBid, также требуют обновления значений, чтобы AuctionBid#save вернул true.

Ответы [ 3 ]

11 голосов
/ 21 марта 2010

Сохранение и уничтожение автоматически включаются в транзакцию

ActiveRecord :: Сделки :: * 1004 методы класса *

Обе Base # save и Base # destroy заключены в транзакцию , которая гарантирует, что все, что вы делаете в проверках или обратных вызовах, будет происходить под защищенным покрытием транзакции. Таким образом, вы можете использовать проверки для проверки значений, от которых зависит транзакция, или вы можете вызывать исключения в обратных вызовах для отката, в том числе после обратных вызовов * 1014

Настоящее соглашение!

class AuctionBid < ActiveRecord::Base

  belongs_to :auction, :counter_cache => true
  belongs_to :user

  validate              :auction_bidable?
  validate              :user_can_bid?
  validates_presence_of :auction_id
  validates_presence_of :user_id

  # the real magic!
  after_save  :update_auction, :update_user

  def auction_bidable?
    errors.add_to_base("You cannot bid on this auction!") unless auction.bidable?
  end

  def user_can_bid?
    errors.add_to_base("You cannot bid on this auction!") unless user.can_bid?
  end

  protected

  def update_auction
    auction.place_bid(user)
    auction.save!
  end

  def update_user
    user.place_bid
    user.save!
  end

end

похвальная грамота

Франсуа Босолей +1. Спасибо за рекомендацию :foreign_key, но столбцы current_winner_* должны быть кэшированы в БД для оптимизации запроса.

Алекс +1. Спасибо, что начали меня с Model.transaction { ... }. Хотя это и не стало для меня полным решением, оно определенно поможет мне направиться в правильном направлении.

4 голосов
/ 18 марта 2010

Возможно, вы можете переопределить AuctionBid.save, примерно так:

def save
  AuctionBid.transaction {
    auction.bid_count += 1
    user.bid_count += 1
    auction.current_winner_id = user_id
    auction.save!
    user.save!
    return super
  }
end

Возможно, вам также понадобится перехватить исключения, возникшие в блоке транзакции, и вернуть false. Я думаю, вам также нужно добавить belongs_to :auction к AuctionBid, чтобы иметь возможность ссылаться на объект аукциона.

1 голос
/ 18 марта 2010

Вы хотите включить кэширование счетчика , добавив: counter_cache к принадлежащим_в ассоциации.

class Auction
  belongs_to :item
  belongs_to :user, :foreign_key => :current_winner_id
  has_many :auction_bids
end

class User
  has_many :auction_bids
end

class AuctionBid
  belongs_to :auction, :counter_cache => true
  belongs_to :user, :counter_cache => true
end

Не забудьте добавить столбцы через миграцию. Чтобы создать ставку аукциона и установить пользователя, я бы предложил использовать следующий код:

class MyController
  def bid
    @ab = current_user.auction_bids.build(params[:auction_bid])
    if @ab.save
      render :json => {:response => 'YAY!'}
    else
      render :json => {:response => 'FAIL!'}
    end
  end 
end

Сохраняет шаг и гарантирует, что вы никогда не забудете назначить пользователя.

Последнее требование - найти текущего победителя. На самом деле это ассоциация has_one на аукционе. Вам не нужен столбец для этого:

class Auction
  # has_one is essentially has_many with an enforced :limit => 1 added
  has_one :winning_bid, :class_name => "AuctionBid", :order => "bid_amount DESC"
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...