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/ 阅读全文,尤其是关于记忆的部分。
回答您评论中的问题:
- 为什么在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
实例变量
的值
- 您在 rails 应用中的什么地方使用 'memoization'?
嗯......如果不写一个很长的答案并解释很多上下文等,我不确定我能否给出一个好的答案。但简短的故事是:只要有适合的方法以下全部:
- 每个实例可能被调用多次
- 做一些耗时的事情(比如查询数据库什么的)
- 可以安全地缓存该方法的结果,因为它不太可能在调用该方法的不同时间之间发生变化(基于每个实例)
一个很好的比喻可能是:如果有人问你时间,你看表(即耗时的动作)。如果他们在 1 秒后再次问你时间,你不需要再看表——你基本上会说 "I already just checked, it's 9:00am"。 (这有点像你在记住时间 - 省去你看手表的时间,因为自上次你询问以来结果不会改变)。
我正在阅读一篇文章并 运行 进入第一个示例代码。在模型中设置实例变量以避免不必要的查询。我也在其中一个 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/ 阅读全文,尤其是关于记忆的部分。
回答您评论中的问题:
- 为什么在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
实例变量
- 您在 rails 应用中的什么地方使用 'memoization'?
嗯......如果不写一个很长的答案并解释很多上下文等,我不确定我能否给出一个好的答案。但简短的故事是:只要有适合的方法以下全部:
- 每个实例可能被调用多次
- 做一些耗时的事情(比如查询数据库什么的)
- 可以安全地缓存该方法的结果,因为它不太可能在调用该方法的不同时间之间发生变化(基于每个实例)
一个很好的比喻可能是:如果有人问你时间,你看表(即耗时的动作)。如果他们在 1 秒后再次问你时间,你不需要再看表——你基本上会说 "I already just checked, it's 9:00am"。 (这有点像你在记住时间 - 省去你看手表的时间,因为自上次你询问以来结果不会改变)。