Reputation: 2380
I was reading an article and ran into the 1st example code. In the model the instance variable is set to avoid unnecessary queries. I also saw this in one of the railscasts (2nd example). On the other hand I read in more articles that if I use this pattern then my app may won't be thread safe so I can't take advantages of my puma webserver.
Could sby tell me when/where I should use this pattern?
1st example:
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
2nd example
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
UPDATED questions
1st example
As I see this 'first_order_date' is always called on an object (https://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object), so I don't exactly see how the extra query can be avoided. I'm sure I'm wrong but according to my knowledge it could be just
def first_order_date
if orders.ascend_by_created_at.first
first_order.created_at.to_date
else
NullDate.new 'orders'
end
end
Or the may use the @first_order
somewhere else as well?
2nd example
The code in the original question is not equivalent with this?
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
I see here what they achieve with defining total_weight
, but why is it better to use an instance variable over my example?
Upvotes: 0
Views: 1392
Reputation: 13952
The short story is: your code should be fine on Puma.
With regards to thread safety in the context of Puma, what you have to worry about is changing stuff that might be shared across threads (this usually means stuff at a class level, rather than at an instance level - I don't think Puma will be sharing instances of objects across it's threads) - and you're not doing that.
The ||=
technique you refer to is called 'memoization'. You should read the full article at https://bearmetal.eu/theden/how-do-i-know-whether-my-rails-app-is-thread-safe-or-not/, in particular the section on memoization.
To answer the questions in your comments:
total_weight = line_items.to_a.sum(&:weight)
in the first line of shipping_price method? As I see it would run the query only onceOK, so if the shipping_price
method only gets called once per instance of that class, then you're right - there's no need for memoization. However, if it gets called multiple times per instance, then every time, it has to execute line_items.to_a.sum(&:weight)
to calculate the total.
So let's say you called shipping_price
3 times in a row for some reason, on the same instance. Then without memoization, it would have to execute line_items.to_a.sum(&:weight)
3 times. But with memoization, it'd have to execute line_items.to_a.sum(&:weight)
only once, and the next two times it'd only have to retrieve the value of the @total_weight
instance variable
Hmm... I'm not sure I can give a good answer to that without writing a really long answer and explaining a lot of context etc. But the short story is: whenever there's a method that fits all of the following:
A good analogy might be: if someone asks you the time, you check your watch (ie, the time consuming action). If they ask you the time again, 1 second later, you don't need to check your watch again - you basically say "I already just checked, it's 9:00am". (That's kinda like you're memoizing the time - saving you having to check your watch, because the result won't have changed since last time you asked).
Upvotes: 6
Reputation: 211660
In this case it's used to avoid executing code repeatedly when the answer will be the same.
Imagine this version:
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
That's a lot of heavy lifting for one simple thing, isn't it? The ||=
pattern serves to cache the results.
Upvotes: 2