Ruby on rails 如何更新Rails模型中的实例变量?
在我的Rails应用程序中,我有Ruby on rails 如何更新Rails模型中的实例变量?,ruby-on-rails,ruby,ruby-on-rails-3,instance-variables,Ruby On Rails,Ruby,Ruby On Rails 3,Instance Variables,在我的Rails应用程序中,我有用户,他们可以进行许多支付 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)
用户
,他们可以进行许多支付
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
实例变量(@sum
)的使用大大减少了这里的SQL查询数量,因为相同的查询不会一次又一次地命中数据库
但是,问题是,当用户创建、删除或更改其付款之一时,@sum
实例变量中没有反映这一点。那么我如何重置它呢?还是有更好的解决办法
谢谢您的帮助。也许您可以通过观察员来完成:
# 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
#payment.rb
def self.cached_sum(force=false)
如果@sum.blank?|力
@金额=包括(:发票=>:项目)
结束
@总数
结束
定义范围内的自和(范围)
@sum=缓存的\u sum
@sum.select{| x | range.cover?x.date}.sum(&total)
结束
#支付(u observer.rb)
类PaymentObserver
你可以在上找到更多关于观察者的信息。你的@sum
基本上就是你所需要的值的缓存。与任何缓存一样,如果所涉及的值出现问题,则需要使其无效
您可以在保存后使用或创建后使用过滤器调用设置@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
classpayment:项目)
@sum.select{| x | range.cover?x.date}.sum(&total)
结束
def self.invalidate_缓存
@如果@cached\u range.includes?(付款日期),则总和=零
结束
与其将关联存储为类支付的实例变量,不如将其存储为用户的实例变量(我知道这听起来很困惑,我已尝试在下面解释)
class用户:项目)。全部
#@payments\u with\u invoices现在是一个数组,因此不能对其使用Payment的class方法
@带发票的付款。选择{| x | range.cover?x.date}。总和(&:总计)
结束
结束
当您在类方法中定义@sum
(类方法由self.
表示)时,它成为类支付的实例变量。这意味着您可以通过Payment.sum
访问它。因此,这与特定用户及其付款无关@sum
现在是类Payment
的一个属性,Rails缓存它的方式与缓存类的方法定义的方式相同
一旦@sum
被初始化,它将保持不变,正如您所注意到的,即使在用户创建了新的支付之后,或者如果其他用户为此登录,它也将保持不变!当应用程序重新启动时,它将更改
但是,如果您使用发票定义@payments\u,如我上面所示,它将成为用户
的特定实例的属性,或者换句话说,是实例级实例变量。这意味着您可以作为某些用户访问它。使用发票进行支付。由于一个应用程序可以有许多用户,因此这些用户不会跨请求持久保存在Rails内存中。因此,每当用户实例更改时,都会再次加载其属性
因此,如果用户创建了更多的付款,@payments\u with\u invoices
变量将在用户实例重新初始化后刷新。这是您的问题附带的,但不要使用带有块的\select
您要做的是选择所有付款,然后将关系过滤为数组。使用Arel解决这一问题:
scope :within_range, ->(range){ where date: range }
这将在语句之间构建SQL。对结果关系使用#sum
将构建一个SQL sum()语句,这可能比加载所有记录更有效。选择是一种ActiveRecord查询方法,而不是数组。它只是scope.nope上的另一个链,而不是与块一起使用时。请自己尝试:它将加载所有记录。证据:。“使用块,就像数组一样工作#选择”参见源代码…如果在单个请求中为几个范围调用该方法,这是一个更好的解决方案。+1,很好的解决方法。虽然我会让sum_在_范围内
使用SQL(见我的答案)进行所有计算@m_x ya通常会更好,但由于前面的问题,我有一些bkgrnd。这个函数在相同的重叠范围请求中被多次调用,所以我认为最好在Rails中进行过滤。但如果情况不再是这样,你的解决方案就更有意义了:)谢谢。当我复制您的确切代码时,仍然会得到大约80个SQL查询。因此,我将includes(:invoice=>:items)
移动到Payment
类中select方法的开头,现在我每页只得到大约10个SQL查询(与以前相同,因此很好),但是总呈现时间增加了很多,从大约300毫秒到3000毫秒。这肯定太多了,我想知道为什么。试试@m_x的建议,它可能会更快。我也更新了我的解决方案以保持一致性,但不认为它会加快速度
# 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
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
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
scope :within_range, ->(range){ where date: range }