2

在我的 Rails 应用程序中,我users可以拥有许多payments.

class User < ActiveRecord::Base

  has_many :invoices
  has_many :payments

  def year_ranges
    ...
  end

  def quarter_ranges
    ...
  end

  def month_ranges
    ...
  end

  def revenue_between(range, kind)
    payments.sum_within_range(range, kind)
  end

end

class Invoice < ActiveRecord::Base

  belongs_to :user
  has_many :items
  has_many :payments

  ...

end

class Payment < ActiveRecord::Base

  belongs_to :user
  belongs_to :invoice

  def net_amount
    invoice.subtotal * percent_of_invoice_total / 100
  end  

  def taxable_amount
    invoice.total_tax * percent_of_invoice_total / 100
  end

  def gross_amount
    invoice.total * percent_of_invoice_total / 100
  end

  def self.chart_data(ranges, unit)
    ranges.map do |r| { 
      :range            => range_label(r, unit),
      :gross_revenue    => sum_within_range(r, :gross),
      :taxable_revenue  => sum_within_range(r, :taxable),
      :net_revenue      => sum_within_range(r, :net) }
    end
  end

  def self.sum_within_range(range, kind)
    @sum ||= includes(:invoice => :items)
    @sum.select { |x| range.cover? x.date }.sum(&:"#{kind}_amount")
  end

end

在我dashboard看来,我列出了ranges取决于用户选择的 GET 参数的总付款。用户可以选择yearsquartersmonths

class DashboardController < ApplicationController

  def show  
    if %w[year quarter month].include?(params[:by])   
      @unit = params[:by]
    else
      @unit = 'year'
    end
    @ranges = @user.send("#{@unit}_ranges")
    @paginated_ranges = @ranges.paginate(:page => params[:page], :per_page => 10)
    @title = "All your payments"
  end

end

使用实例变量@sum

然而,问题是,当用户创建、删除或更改其中一个时payments,这不会反映在@sum实例变量中。那么我该如何重置呢?或者有更好的解决方案吗?

谢谢你的帮助。

4

4 回答 4

3

这是您的问题的附带问题,但不要#select与块一起使用。

您正在做的是选择所有付款,然后将关系过滤为数组。使用 Arel 来克服这个问题:

scope :within_range, ->(range){ where date: range }

这将构建一个 SQL BETWEEN 语句。在结果关系上使用#sum将构建一个 SQL SUM() 语句,这可能比加载所有记录更有效。

于 2013-10-11T09:21:32.387 回答
3

与其将关联存储为 Class 的实例变量,Payment不如将其存储为 a 的实例变量user(我知道这听起来令人困惑,我试图在下面解释)

class User < ActiveRecord::Base

  has_many :payments

  def revenue_between(range)
    @payments_with_invoices ||= payments.includes(:invoice => :items).all
    # @payments_with_invoices is an array now so cannot use Payment's class method on it
    @payments_with_invoices.select { |x| range.cover? x.date }.sum(&:total)
  end

end

当您@sum在类方法中定义时(类方法用 表示self.),它成为 Class 的实例变量Payment。这意味着您可以潜在地以Payment.sum. 因此,这与特定用户及其付款无关。@sum现在是类的一个属性,PaymentRails 会像缓存类的方法定义一样缓存它。

一旦@sum初始化,它就会保持不变,正如您所注意到的,即使在用户创建新的付款之后或者如果其他用户为此登录!当应用程序重新启动时它会改变。

但是,如果您@payments_with_invoices像我上面显示的那样定义,它就会成为特定实例的属性,User或者换句话说,实例级实例变量。这意味着您可以潜在地以some_user.payments_with_invoices. 由于一个应用程序可以有许多用户,因此这些用户不会跨请求保留在 Rails 内存中。因此,每当用户实例更改时,它的属性就会再次加载。

因此,如果用户创建更多付款,则@payments_with_invoices变量将被刷新,因为用户实例被重新初始化。

于 2013-10-11T09:18:36.970 回答
0

也许你可以和观察者一起做:

# payment.rb

def self.cached_sum(force=false)
  if @sum.blank? || force
    @sum = includes(:invoice => :items)
  end
  @sum
end

def self.sum_within_range(range)
  @sum = cached_sum
  @sum.select { |x| range.cover? x.date }.sum(&total)
end

#payment_observer.rb

class PaymentObserver < ActiveRecord::Observer
  # force @sum updating

  def after_save(comment)
    Payment.cached_sum(true)
  end

  def after_destroy(comment)
    Payment.cached_sum(true)
  end

end

您可以在http://apidock.com/rails/v3.2.13/ActiveRecord/Observer找到更多关于观察者的信息

于 2013-10-11T09:11:28.520 回答
0

那么你@sum基本上是你需要的值的缓存。像任何缓存一样,如果涉及的值发生问题,您需要使其无效。

您可以使用after_saveafter_create过滤器来调用设置@sum = nil. 保存您的缓存覆盖的范围并在新付款或更改付款的日期之前确定失效也可能很有用。

class Payment < ActiveRecord::Base

  belongs_to :user

  after_save :invalidate_cache

  def self.sum_within_range(range)
    @cached_range = range
    @sum ||= includes(:invoice => :items)
    @sum.select { |x| range.cover? x.date }.sum(&total)
  end

  def self.invalidate_cache
    @sum = nil if @cached_range.includes?(payment_date)
end
于 2013-10-11T09:16:47.873 回答