【问题标题】:Update attribute in callback Rails 3更新回调 Rails 3 中的属性
【发布时间】:2011-05-26 22:36:01
【问题描述】:

this question 开始,我花了一天时间尝试将累积的运行销售总额添加到我的销售表中。这有点棘手(对我来说),因为我想要一个 isbn_id 相同的销售总计,并且在该集合中,channel_id 相同的记录 - 按 invoice_date 排名。这就是我可以计算特定范围内销售单位的版税。

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

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  

这大概是应该如何编写回调 - 只是在模型中?它只是在新销售之前神奇地运行吗?

我的核心问题是:在 before_save 回调中,如何将属性“total_quantity”更新为当前记录的“quantity”和上一条记录的“total_quantity”之和,在isbn_id 和 channel_id 的发现?

这是查找的输出:

ruby-1.9.2-p180 :025 > @lastbal = @sale.find_all_by_isbn_id(@isbn).group_by(&:channel) 
=> {#<Channel id: 4, isbn_id: nil, channel_name: "Gratis", created_at: "2011-05-26 11:08:22", updated_at: "2011-05-26 11:08:22">=>[#<Sale id: 26, isbn_id: 2, quantity: 10000, value: 40000, currency: "", total_quantity: nil, created_at: "2011-05-26 11:11:30", updated_at: "2011-05-26 11:11:30", customer: "6", retail_price: nil, discount: nil, channel_id: 4, invoice_date: "2011-05-18", rule_id: nil, trenche: nil>], #<Channel id: 1, isbn_id: nil, channel_name: "Home", created_at: "2011-05-16 19:47:27", updated_at: "2011-05-16 19:47:27">=>[#<Sale id: 22, isbn_id: 2, quantity: 1000, value: 5193, currency: "", total_quantity: nil, created_at: "2011-05-25 19:46:03", updated_at: "2011-05-25 19:46:03", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2011-05-11", rule_id: nil, trenche: nil>, #<Sale id: 24, isbn_id: 2, quantity: 1000, value: 4394, currency: "", total_quantity: nil, created_at: "2011-05-26 09:48:16", updated_at: "2011-05-26 09:48:16", customer: "g", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2011-05-10", rule_id: nil, trenche: nil>, #<Sale id: 25, isbn_id: 2, quantity: 1000, value: 4394, currency: "", total_quantity: nil, created_at: "2011-05-26 10:02:38", updated_at: "2011-05-26 10:02:38", customer: "g", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2011-05-05", rule_id: nil, trenche: nil>, #<Sale id: 21, isbn_id: 2, quantity: 1000, value: 5193, currency: "", total_quantity: nil, created_at: "2011-05-25 14:12:45", updated_at: "2011-05-25 14:12:45", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2010-10-15", rule_id: nil, trenche: nil>, #<Sale id: 13, isbn_id: 2, quantity: 50, value: 159, currency: "", total_quantity: nil, created_at: "2011-05-25 12:33:09", updated_at: "2011-05-25 12:33:09", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2010-01-01", rule_id: nil, trenche: nil>, #<Sale id: 14, isbn_id: 2, quantity: 25, value: 129, currency: "", total_quantity: nil, created_at: "2011-05-25 12:33:23", updated_at: "2011-05-25 12:33:23", customer: "", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2010-01-01", rule_id: nil, trenche: nil>, #<Sale id: 12, isbn_id: 2, quantity: 100, value: 415, currency: "", total_quantity: nil, created_at: "2011-05-25 12:32:50", updated_at: "2011-05-25 15:13:21", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2001-10-01", rule_id: nil, trenche: nil>, #<Sale id: 11, isbn_id: 2, quantity: 500, value: 2197, currency: "", total_quantity: nil, created_at: "2011-05-25 12:32:24", updated_at: "2011-05-25 15:11:20", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2000-10-01", rule_id: nil, trenche: nil>], #<Channel id: 2, isbn_id: nil, channel_name: "Export", created_at: "2011-05-16 19:47:35", updated_at: "2011-05-16 19:47:35">=>[#<Sale id: 23, isbn_id: 2, quantity: 2000, value: 5000, currency: "", total_quantity: nil, created_at: "2011-05-26 09:16:15", updated_at: "2011-05-26 09:16:15", customer: "v", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2011-05-02", rule_id: nil, trenche: nil>, #<Sale id: 17, isbn_id: 2, quantity: 242, value: 657, currency: "", total_quantity: nil, created_at: "2011-05-25 12:34:24", updated_at: "2011-05-25 12:34:24", customer: "b ", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2010-10-15", rule_id: nil, trenche: nil>, #<Sale id: 18, isbn_id: 2, quantity: 54, value: 194, currency: "", total_quantity: nil, created_at: "2011-05-25 12:34:44", updated_at: "2011-05-25 12:34:44", customer: "b ", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2010-10-15", rule_id: nil, trenche: nil>, #<Sale id: 15, isbn_id: 2, quantity: 135, value: 377, currency: "", total_quantity: nil, created_at: "2011-05-25 12:33:48", updated_at: "2011-05-25 12:33:48", customer: "b ", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2010-09-15", rule_id: nil, trenche: nil>, #<Sale id: 16, isbn_id: 2, quantity: 433, value: 830, currency: "", total_quantity: nil, created_at: "2011-05-25 12:34:06", updated_at: "2011-05-25 12:34:06", customer: "b ", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2010-09-15", rule_id: nil, trenche: nil>]} 

这是我的销售模型中的列:

#  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

提前非常感谢。

更新:最终解决方案。

真的不确定这算不算“回馈社区”,因为它非常冗长,而不是枯燥无味,充满了我用来找出所有错误的 put,并且引导格式错误,但见鬼,我一个菜鸟,至少我可以在几年后回到这里嘲笑自己,当我知道自己在做什么时。所以,这是我在 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 < allsales.maximum(:invoice_date)
            puts "the runningtotal method has been skipped because runningtotal thinks the current invoice date is less than the highest invoice date in the allsales relation"
            else 
              puts "this is a normal entry so runningtotal has set the total quantity to be #{self.quantity + (lastsale.try(:total_quantity) || 0) }"
              self.total_quantity = self.quantity + (lastsale.try(:total_quantity) || 0)     
            end
        end
  end

  def refresh
     allsales = Sale.where(:isbn_id => 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

【问题讨论】:

  • 这有点令人困惑。但我得到的是你需要总结......你见过inject吗? total = @array.inject{|sum, this| sum + this.numeric_value} 用于销售对象数组
  • 我在之前发布的问题中被警告不要注入!不过谢谢,我会保留它。

标签: ruby-on-rails ruby ruby-on-rails-3 callback


【解决方案1】:

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

但是,如果我理解您对问题的英文陈述,您的代码相当复杂。我什至看不到@isbn 的设置位置,这可能很危险......

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

在回调中,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。

从那里开始,我不确定你是只打算更新新记录还是旧记录...如果你想更新当前记录,只需设置属性并退出回调,它将是保存其余部分时保存: self.total_quantity = previous_sale.quantity + self.quantity

如果您也打算更新其他对象,那么我们必须更新这些对象并保存它们。我在您的代码中根本没有看到这种情况发生。

您的代码经历了几个循环,可能多次点击 write_attribute...这没有意义。

如果您的意思是要找到与当前 isbn 和频道匹配的最后一条记录来更新新记录,我会这样做:

def 运行总计 lastsale = Sale.where(:isbn_id => self.isbn_id)。 其中(:channel_id => self.channel_id)。 order("invoice_date DESC").first # 这应该是匹配的最新销售 # 当前 isbn 和频道 self.total_quantity = self.quantity + (lastsale.try(:total_quantity) || 0) # 如果没有以前的记录,请注意 nil ^ 结尾

`

【讨论】:

  • 太棒了 - 非常感谢您提供如此有用的解释。效果很好。
  • 已经用我的最终解决方案编辑了我的问题,fwiw 这并不多,因为它很讨厌!
猜你喜欢
  • 2012-11-14
  • 2014-09-29
  • 1970-01-01
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多