在回调Rails 3中更新属性

继这个问题之后 ,我花了一天时间尝试将累计运行销售总额添加到我的销售表中。 这对我来说有点棘手(因为我想要),因为我希望isbn_id相同的销售总额,并且在该集合中,记录channel_id相同的位置 – 按invoice_date排名。 这就是我可以计算特定销售单位范围内的特许权使用费。

这是我的非工作回调代码,在Sale模型中:

before_save :runningtotal private def runningtotal @sale = Sale.order("invoice_date ASC") @lastbal = @sale.find_all_by_isbn_id(@isbn).group_by(&:channel) #that sucessfully gets all sales ranked by date ascending, then groups them by channel, just for the current isbn. @lastbal.each do |channel, sale| sale.each_with_index do |sale, i| previous_sale = sale[i-1] unless i==0 next unless previous_sale @total_quantity = previous_sale.quantity + :quantity write_attribute(:total_quantity,@total_quantity) end end end 

这大致是应该如何编写回调 – 只是在模型中? 是否只是在新的销售之前疯狂地运行?

我的核心问题是:我如何将属性“total_quantity”更新为当前记录的“数量”之和,以及在发现的约束条件下,在before_save回调中按日期的前一记录的“total_quantity”。 isbn_id和channel_id?

这是查找的输出:

 ruby-1.9.2-p180 :025 > @lastbal = @sale.find_all_by_isbn_id(@isbn).group_by(&:channel) => {#=>[#], #=>[#, #, #, #, #, #, #, #], #=>[#, #, #, #, #]} 

这是我的Sale模型中的列:

 # id :integer not null, primary key # isbn_id :integer # quantity :integer # value :integer # currency :string(255) # total_quantity :integer # created_at :datetime # updated_at :datetime # customer :string(255) # retail_price :integer # discount :decimal(, ) # channel_id :integer # invoice_date :date # rule_id :integer 

非常感谢提前。

更新:最终解决方案。

真的不确定这算是“回馈社区”,因为它可笑的冗长,而不是DRY,充满了我用来弄清楚所有错误的看跌期权,并且格式化得很糟糕,但是哎呀,我是一个菜鸟至少我可以回到这里,在我知道自己在做什么的几年里嘲笑自己。 所以,这是我的最终解决方案,在Sale.rb. 可怜的过度模型。 有一天我会重构这个。

 before_save :runningtotal after_commit :refresh private def runningtotal # get the latest sale that matches the new sale's isbn and channel id, then rank by invoice date descending, and get the first record: lastsale = Sale.where(:isbn_id => self.isbn_id).where(:channel_id => self.channel_id).order("invoice_date DESC").first allsales = Sale.where(:isbn_id => self.isbn_id).where(:channel_id => self.channel_id).order("invoice_date DESC") # set the total_quantity field in the new sales record to its quantity + the last sale's total. if allsales.maximum(:invoice_date).nil? puts "runningtotal thinks the max of invoice date in the allsales relation is nil" puts "and runningtotal is setting total_quantity on the new sale to be #{self.quantity + (lastsale.try(:total_quantity) || 0)}" self.total_quantity = self.quantity + (lastsale.try(:total_quantity) || 0) else if self.invoice_date  self.isbn_id).where(:channel_id => self.channel_id).order("invoice_date ASC") #if the runningtotal callback hasn't run, the total quantity will be nil, and nil triggers this after_commit callback if total_quantity.nil? puts "running refresh callback" puts "here's a sample parameter pass: id: #{id} quantity: #{quantity} date: #{invoice_date} " puts "allsales class is #{allsales.class}" # if the new sale that's being saved has a date that's before any previous sale... puts "before the if, refresh thinks that the earliest invoice date is #{allsales.minimum(:invoice_date)} and that invoice date is #{invoice_date}" if invoice_date <= allsales.minimum(:invoice_date) puts "date earlier than existing sales dates" puts "refresh thinks that the earliest invoice date is #{allsales.minimum(:invoice_date)} and that invoice date is #{invoice_date}" #... then set its total_quantity to the sale quantity... update_attribute(:total_quantity, quantity) puts "total_qty updated with qty" # ... and update all subsequent records' total_quantity (skipping the before_save callback which would trigger an infinite loop). allsales.each_with_index do |sale, i| previous_sale = allsales[i-1] unless i==0 next unless previous_sale puts "getting qty out of arel when date earlier than others: #{previous_sale.quantity}" puts "this is adding #{quantity} to #{previous_sale.quantity } which is #{quantity + previous_sale.total_quantity }" Sale.skip_callback(:save, :before, :runningtotal ) sale.update_attribute(:total_quantity, (sale.quantity + previous_sale.total_quantity )) Sale.set_callback(:save, :before, :runningtotal) end else # if the invoice date is within the min and max range of the previous sales... # ... update all previous and subsequent records' total_quantity (skipping the before_save callback which would trigger an infinite loop). allsales.each_with_index do |sale, i| previous_sale = allsales[i-1] unless i==0 next unless previous_sale puts "getting qty out of arel within existing date range: #{previous_sale.quantity}" puts "this is adding #{quantity} to #{previous_sale.quantity } which is #{quantity + previous_sale.total_quantity }" Sale.skip_callback(:save, :before, :runningtotal ) sale.update_attribute(:total_quantity, (sale.quantity + previous_sale.total_quantity )) Sale.set_callback(:save, :before, :runningtotal ) end end end end 

是的,在模型中使用before_save将在每次保存时运行,无论是新的还是更新的。 因此,您需要在计算中注意期望当前(新)记录尚不存在。 ;)您可能希望使用before_save, :on => :create将其限制为创建操作。

但是,如果我理解您对该问题的英语陈述,那么您的代码相当复杂。 我甚至不知道@isbn的位置,这可能是危险的……

这是否需要更新其他对象上的isbn和channel? 通常,根据需要简单地计算,而不是尝试在每条记录中缓存总数,这是更好的forms。

在回调中, self是当前(新的?)记录,因此使用它来引用新值。

   @sale = Sale.order(“invoice_date ASC”)
   @lastbal = @ sale.find_all_by_isbn_id(@isbn).group_by(&:channel)

可以用这个代替,我想:

  @lastbal = Sale.order(“invoice_date ASC”)。where(:isbn_id => self.isbn_id).group_by(&:channel)

我假设@isbn实际上是新纪录的isbn。

从那里,我不确定你是否只打算更新新记录或旧记录…如果你想更新当前记录,只需设置属性并退出回调,它将在保存时保存保存rest: self.total_quantity = previous_sale.quantity + self.quantity

如果您打算更新其他对象,那么我们必须更新这些对象并保存它们。 我在你的代码中看不到这种情况。

你的代码经历了几次循环,可能多次击中write_attribute ……这没有意义。

如果你的意思是你想要找到与当前isbn和channel相匹配的最后一条记录来更新新记录,这就是我要做的:

 def runtotal
   lastsale = Sale.where(:isbn_id => self.isbn_id)。
                   where(:channel_id => self.channel_id)。
                  订单(“invoice_date DESC”)。首先 
              #应该是最新的匹配销售 
              #当前的isbn和频道
   self.total_quantity = self.quantity +(lastsale.try(:total_quantity)|| 0) 
       #如果没有先前的记录,请注意nil ^
结束  

`