rails 何时使用 ||= 设置实例变量

rails when to set instance variable with ||=

我正在阅读一篇文章并 运行 进入第一个示例代码。在模型中设置实例变量以避免不必要的查询。我也在其中一个 railscasts(第二个例子)中看到了这一点。另一方面,我在更多文章中读到,如果我使用这种模式,那么我的应用程序可能不是线程安全的,因此我无法利用我的 puma 网络服务器。

sby 可以告诉我 when/where 我应该使用这个模式吗?

第一个例子:

def first_order_date
  if first_order
    first_order.created_at.to_date
  else
    NullDate.new 'orders'
  end
end

private

def first_order
  @first_order ||= orders.ascend_by_created_at.first
end

第二个例子

def shipping_price
  if total_weight == 0
    0.00
  elsif total_weight <= 3
    8.00
  elsif total_weight <= 5
    10.00
  else
    12.00
  end
end

def total_weight
  @total_weight ||= line_items.to_a.sum(&:weight)
end

更新问题

第一个例子

正如我所见,这个 'first_order_date' 总是在对象 (https://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object) 上调用,所以我不明白如何避免额外的查询。我确定我错了,但据我所知,它可能只是

def first_order_date
  if orders.ascend_by_created_at.first
    first_order.created_at.to_date
  else
    NullDate.new 'orders'
  end
end

或者也可以在其他地方使用 @first_order

第二个例子

原题中的代码与此不等价?

def shipping_price
  total_weight = line_items.to_a.sum(&:weight)
  if total_weight == 0
    0.00
  elsif total_weight <= 3
    8.00
  elsif total_weight <= 5
    10.00
  else
    12.00
  end
end

我在这里看到了他们通过定义 total_weight 取得的成果,但为什么在我的示例中使用实例变量更好?

在这种情况下,它用于避免在答案相同时重复执行代码。

想象一下这个版本:

def shipping_price
  if line_items.to_a.sum(&:weight) == 0
    0.00
  elsif line_items.to_a.sum(&:weight) <= 3
    8.00
  elsif line_items.to_a.sum(&:weight) <= 5
    10.00
  else
    12.00
  end
end

一件简单的事情需要做很多繁重的工作,不是吗? ||= 模式用于缓存结果。

简而言之:您的代码在 Puma 上应该没问题。

关于 Puma 上下文中的线程安全,您需要担心的是更改可能跨线程共享的内容(这通常意味着 class 级别的内容,而不是实例中的内容level - 我不认为 Puma 会在它的线程之间共享对象的实例) - 而你不会那样做。

您提到的 ||= 技术称为 'memoization'。您应该在 https://bearmetal.eu/theden/how-do-i-know-whether-my-rails-app-is-thread-safe-or-not/ 阅读全文,尤其是关于记忆的部分。

回答您评论中的问题:

  1. 为什么在shipping_price方法的第一行定义total_weight = line_items.to_a.sum(&:weight)不够?正如我所见,它 运行 只查询一次

好的,所以如果 shipping_price 方法只在 class 的每个实例中被调用一次,那么你是对的 - 不需要记忆。但是,如果它被多次调用 per instance,那么每次都必须执行 line_items.to_a.sum(&:weight) 来计算总数。

假设您出于某种原因在同一个实例上连续调用了 shipping_price 3 次。然后没有记忆,它必须执行 line_items.to_a.sum(&:weight) 3 次。但是有了记忆,它只需要执行一次line_items.to_a.sum(&:weight),接下来的两次它只需要检索@total_weight实例变量

的值
  1. 您在 rails 应用中的什么地方使用 'memoization'?

嗯......如果不写一个很长的答案并解释很多上下文等,我不确定我能否给出一个好的答案。但简短的故事是:只要有适合的方法以下全部

  • 每个实例可能被调用多次
  • 做一些耗时的事情(比如查询数据库什么的)
  • 可以安全地缓存该方法的结果,因为它不太可能在调用该方法的不同时间之间发生变化(基于每个实例)

一个很好的比喻可能是:如果有人问你时间,你看表(即耗时的动作)。如果他们在 1 秒后再次问你时间,你不需要再看表——你基本上会说 "I already just checked, it's 9:00am"。 (这有点像你在记住时间 - 省去你看手表的时间,因为自上次你询问以来结果不会改变)。