Транзакция направляющих: сохраните данные в многоуровневых моделях

мои модели

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.

12
задан maček 18 March 2010 в 17:53
поделиться

3 ответа

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

ActiveRecord :: Transactions :: ClassMethods

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

Настоящая конвенция!

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 {...} . Хотя это не стало для меня полным решением, это определенно помогло мне в правильном направлении.

11
ответ дан 2 December 2019 в 20:16
поделиться

Вероятно, вы могли бы переопределить 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 . Я думаю, вам также нужно добавить own_to: аукцион к AuctionBid , чтобы иметь возможность ссылаться на объект аукциона.

4
ответ дан 2 December 2019 в 20:16
поделиться

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

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
1
ответ дан 2 December 2019 в 20:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: