问题 Rails事务:将数据保存在多个模型中


我的模特

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 需要增加1。
  2. @ab.user.bid_count 需要增加1
  3. @ab.auction.current_winner_id 需要设置为 @ab.user_id

那就是 User 和 Auction 与...相关联 AuctionBid 需要更新值以便更新 AuctionBid#save 返回真实。


1042
2018-03-18 08:35


起源



答案:


保存和销毁会自动包含在事务中

ActiveRecord的::交易:: ClassMethods

基料#保存 和 基料#销毁 来包裹着 交易 确保您在验证或回调中所做的任何事情都将在交易的受保护保护下进行。因此,您可以使用验证来检查事务所依赖的值,或者可以在回调中引发异常以进行回滚,包括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

荣誉奖

FrançoisBeausoleil+1。谢谢你 :foreign_key 推荐,但是 current_winner_* 列需要缓存在db中以优化查询。

亚历克斯+1。谢谢你让我开始 Model.transaction { ... }。虽然这对我来说并不是一个完整的解决方案,但它肯定有助于我指出正确的方向。


11
2018-03-21 00:54





您可以覆盖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 能够参考拍卖对象。


4
2018-03-18 10:40



如果引发异常,则中止事务。保存!应该提出例外。 - Alex Korban


答案:


保存和销毁会自动包含在事务中

ActiveRecord的::交易:: ClassMethods

基料#保存 和 基料#销毁 来包裹着 交易 确保您在验证或回调中所做的任何事情都将在交易的受保护保护下进行。因此,您可以使用验证来检查事务所依赖的值,或者可以在回调中引发异常以进行回滚,包括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

荣誉奖

FrançoisBeausoleil+1。谢谢你 :foreign_key 推荐,但是 current_winner_* 列需要缓存在db中以优化查询。

亚历克斯+1。谢谢你让我开始 Model.transaction { ... }。虽然这对我来说并不是一个完整的解决方案,但它肯定有助于我指出正确的方向。


11
2018-03-21 00:54





您可以覆盖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 能够参考拍卖对象。


4
2018-03-18 10:40



如果引发异常,则中止事务。保存!应该提出例外。 - Alex Korban


你想要启用 反缓存 通过添加:counter_cache到belongs_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
2018-03-18 18:59



这些是有用的提示,但我犯了一个错误,让我的例子有点简单。如果需要更改相关模型的其他部分怎么办?模式将如何变化? - maček
同样的答案:保存回调之前/之后将用于触发其他模型的保存,所有这些都包含在同一个事务中。 - François Beausoleil