Here I want to show you a demo code:
if ENV["PRODUCTION"]
user.apply_discount!
product.update!
else
VCR.use_cassette(vcr_cassette) do
user.apply_discount!
product.update!
end
end
So basically two times I have there the same code:
user.apply_discount!
product.update!
How can I prevent this duplication of code? How would you do it?
I was thinking of putting the code inside a Block and then either call it directly or in the block. Here's an example:
actions = Proc.new do
user.apply_discount!
product.update!
end
if ENV["PRODUCTION"]
actions.call
else
VCR.use_cassette(vcr_cassette) do
actions.call
end
end
Do you have another idea? Better solution? Thanks
Your version is explicit and readable.
The only thing I'd do is moving it to a general method:
def do_env_specific_stuff(stuff)
ENV('PRODUCTION') ? stuff.call : VCR.use_cassette(vcr_cassette) { stuff.call }
end
Then:
stuff = proc do
user.apply_discount!
product.update!
end
do_env_specific_stuff(stuff)
Andrey's answer is excellent and should be accepted.
But just want to point out that you can convert a proc to a block instead of calling a proc in a block...
VCR.use_cassette(vcr_cassette, &actions)
I think the explicit call is better, but just wanted to point out an alternative technique.
Related
I have approx 11 functions that look like this:
def pending_acceptance(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
pending_acceptance?; collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
def pending_start(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
pending_start?; collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
The iteration is always the same, but next unless conditions are different. In case you wonder: it's next unless and ; in it because RuboCop was complaining about it. Is there a solution to implement it better? I hate this spaghetti code. Something like passing the condition into "iterate_it" function or so...
edit: Cannot just pass another parameter because the conditions are double sometimes:
def picked_up(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless
order_fulfillment.handed_over_late? && order_fulfillment.
fulfillment_time_calculator.pending_handover?
collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
edit2: One question yet: how could I slice a symbol, to get a user role from a status? Something like:
:deliverer_started => :deliverer or 'deliverer'?
You can pass another parameter when you use that parameter to decide what condition to check. Just store all possible conditions as lambdas in a hash:
FULFILLMENT_ACTIONS = {
pending_acceptance: lambda { |fulfillment| fulfillment.fulfillment_time_calculator.pending_acceptance? },
pending_start: lambda { |fulfillment| fulfillment.fulfillment_time_calculator.pending_acceptance? },
picked_up: lambda { |fulfillment| fulfillment.handed_over_late? && fulfillment.fulfillment_time_calculator.pending_handover? }
}
def process_fulfillments(type, order_fulfillments)
condition = FULFILLMENT_ACTIONS.fetch(type)
order_fulfillments.each do |order_fulfillment|
next unless condition.call(order_fulfillment)
collect_fulfillments(order_fulfillment.status, order_fulfillment)
end
end
To be called like:
process_fulfillments(:pending_acceptance, order_fulfillments)
process_fulfillments(:pending_start, order_fulfillments)
process_fulfillments(:picked_up, order_fulfillments)
you can make array of strings
arr = ['acceptance','start', ...]
in next step:
arr.each do |method|
define_method ( 'pending_#{method}'.to_sym ) do |order_fulfillments|
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
send('pending_#{method}?'); collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
end
for more information about define_method
While next is handy it comes late(r) in the code and is thus a bit more difficult to grasp. I would first select on the list, then do the action. (Note that this is only possible if your 'check' does not have side effects like in order_fullfillment.send_email_and_return_false_if_fails).
So if tests can be complex I would start the refactoring by expressing the selection criteria and then pulling out the processing of these items (wich also matches more the method names you have given), somewhere in the middle it might look like this:
def pending_acceptance(order_fulfillments)
order_fulfillments.select do |o|
o.fulfillment_time_calculator.pending_acceptance?
end
end
def picked_up(order_fulfillments)
order_fulfillments.select do |order_fulfillment|
order_fulfillment.handed_over_late? && order_fulfillment.
fulfillment_time_calculator.pending_handover?
end
end
def calling_code
# order_fulfillments = OrderFulFillments.get_from_somewhere
# Now, filter
collect_fulfillments(pending_start order_fulfillments)
collect_fulfillments(picked_up order_fulfillments)
end
def collect_fullfillments order_fulfillments
order_fulfillments.each {|of| collect_fullfillment(of) }
end
You'll still have 11 (+1) methods, but imho you express more what you are up to - and your colleagues will grok what happens fast, too. Given your example and question I think you should aim for a simple, expressive solution. If you are more "hardcore", use the more functional lambda approach given in the other solutions. Also, note that these approaches could be combined (by passing an iterator).
You could use something like method_missing.
At the bottom of your class, put something like this:
def order_fulfillment_check(method, order_fulfillment)
case method
when "picked_up" then return order_fulfillment.handed_over_late? && order_fulfillment.fulfillment_time_calculator.pending_handover?
...
... [more case statements] ...
...
else return order_fulfillment.fulfillment_time_calculator.send(method + "?")
end
end
def method_missing(method_name, args*, &block)
args[0].each do |order_fulfillment|
next unless order_fulfillment_check(method_name, order_fulfillment);
collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
Depending on your requirements, you could check if the method_name starts with "pending_".
Please note, this code is untested, but it should be somewhere along the line.
Also, as a sidenote, order_fulfillment.fulfillment_time_calculator.some_random_method is actually a violation of the law of demeter. You might want to adress this.
I have a User model in a ROR application that has multiple methods like this
#getClient() returns an object that knows how to find certain info for a date
#processHeaders() is a function that processes output and updates some values in the database
#refreshToken() is function that is called when an error occurs when requesting data from the object returned by getClient()
def transactions_on_date(date)
if blocked?
# do something
else
begin
output = getClient().transactions(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().transactions(date)
process_fitbit_rate_headers(output)
return output
end
end
end
def events_on_date(date)
if blocked?
# do something
else
begin
output = getClient().events(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().events(date)
processHeaders(output)
return output
end
end
end
I have several functions in my User class that look exactly the same. The only difference among these functions is the line output = getClient().something(date). Is there a way that I can make this code look cleaner so that I do not have a repetitive list of functions.
The answer is usually passing in a block and doing it functional style:
def handle_blocking(date)
if blocked?
# do something
else
begin
output = yield(date)
processHeaders(output)
output
rescue UnauthorizedError => ex
refresh_token
output = yield(date)
process_fitbit_rate_headers(output)
output
end
end
end
Then you call it this way:
handle_blocking(date) do |date|
getClient.something(date)
end
That allows a lot of customization. The yield call executes the block of code you've supplied and passes in the date argument to it.
The process of DRYing up your code often involves looking for patterns and boiling them down to useful methods like this. Using a functional approach can keep things clean.
Yes, you can use Object#send: getClient().send(:method_name, date).
BTW, getClient is not a proper Ruby method name. It should be get_client.
How about a combination of both answers:
class User
def method_missing sym, *args
m_name = sym.to_s
if m_name.end_with? '_on_date'
prop = m_name.split('_').first.to_sym
handle_blocking(args.first) { getClient().send(prop, args.first) }
else
super(sym, *args)
end
end
def respond_to? sym, private=false
m_name.end_with?('_on_date') || super(sym, private)
end
def handle_blocking date
# see other answer
end
end
Then you can call "transaction_on_date", "events_on_date", "foo_on_date" and it would work.
A user has multiple libraries, and each library has multiple books. I want to know if a user has a book in one of his libraries. I'm calling this method with: current_user.has_book?(book):
def has_book?(book)
retval = false
libraries.each do |l|
retval = true if l.books.include?(book)
end
return retval
end
Can my method be refactored?
def has_book?(book)
libraries.any?{|lib| lib.books.include?book}
end
Pretty much the simplest i can imagine.
Not Nil safe though.
def has_book?(book)
libraries.map { |l| l.books.include?(book) }.any?
end
map turns collection of library objects into collection of bools, depending do they include the book, and any? returns true if any of the elements in the array is true.
this has fewer lines, but your original solution could be more efficient if you return true as soon as you find a library that contains the book.
def has_book?(book)
libraries.each do |l|
return true if l.books.include?(book)
end
return false
end
btw. both php and ruby appeared about the same time.
This looks to be the most minified version, I can't see any way to minify it more:
def has_book?(a)c=false;libraries.each{|b|c=true if b.books.include?a};c end
I don't know why #Зелёный removed his answer but this was a good one IMO so I paste it here:
def has_book?(book)
libraries.includes(:books)
.where(books: {id: book.id})
.any?
end
Consider the following
users.collect do |user|
user.favorite_song.presence
end.compact.first
In this case, I want the first favorite song I encounter among my users.
Can this be written in a nicer way?
I tried
users.find do |user|
user.favorite_song.presence
end
But it returns the first user with a favorite song, rather than the favorite song itself.
If the users array isn't too big, your first solution is fine, and can be rewritten like this:
users.map(&:favorite_song).compact.first
You can also modify your second approach as follows:
users.find { |user| user.favorite_song.present? }.favorite_song
Both of these solutions assume that there exists a favorite_song in some user and will raise an exception if there isn't. You can elegantly avoid this with try (Rails only):
users.find { |user| user.favorite_song.present? }.try(:favorite_song)
What about:
users.each do |user|
break user.favorite_song if user.favorite_song.present?
end
Will return user.favorite_song if condition is true otherwise will return users
favorite_song = nil
users.map do |user|
favorite_song = user.favorite_song
break if favorite_song
end
Please have a try with
users.map{|user| user.favorite_song}.compact.first
As of Ruby 2.0 you can use Enumerator::Lazy#lazy to solve this more cleanly and efficiently:
users.lazy.map(&:favorite_song).find(&:present?)
This is more efficient because it will only call favorite_song on users until it finds one that's present. It's cleaner because it's more concise and lazy is self documenting of the intention.
I am generating some methods on the fly. The method body varies based on a certain criteria.
I was relying on class_eval to generate conditional code.
%Q{
def #{name}
#{
(name != "password") ? "attributes[:#{name}]" :
"encrypt(attributes[:#{name}])"
}
end
}
Recently I have started using define_method. How do I generate conditional code blocks while using define_method?
Edit 1
Here are the possible approaches that I have considered:
1) Checking the name on during run time:
define_method(name) do
if name == password
decrypt(attributes[name])
else
attributes[name]
end
end
This is not a preferred method as the check is done during run time.
2) Conditionally defining the entire method.
if (name == "password")
define_method(name) do
decrypt(attributes[name])
end
else
define_method(name) do
attributes[name]
end
end
This approach has the disadvantage of having to repeat the code block just change a small part (as my actual method has several lines of code).
I think because of closures you can do something like this:
define_method name do
if name=='password'
decrypt(attributes[name])
else
attributes[name]
end
end
But the issue there is that the if will be evaluated on each call to the method.
If you wanted to avoid that you'd need to pass different blocks to define_method for different behavior. e.g.
if name=='password'
define_method(name) { decrypt(attributes[name]) }
else
define_method(name) { attributes[name] }
end
alternately you could pass a lambda chosen by the if statement.
define_method(name, name=='password' ? lambda { decrypt(attributes[name]) } : lambda { attributes[name] }
One thing to think about, define_method can be slower than using eval.