Can you pass a "next" back to the function that called the current function? - ruby-on-rails

I have a series of nested each loops that iterate through a list of cards. These loops call out to other sub-functions that test if certain conditions are met in order to proceed.
def card_handler
cards.each do |card|
#some non-relevant code is here on my end
already_sent?
end
end
def already_sent?
# allows for checking if different emails have been sent on the same card
if list_action == 147
a_s_helper(p1_label)
elsif list_action == 146
a_s_helper(p2_label)
elsif list_action == 145
a_s_helper(p3_label)
end
end
def a_s_helper(label)
if card::card_labels.include? label
# if the card already has the label, I want to log the error and return all the way to the next card in the iteration
puts '\n Order info: \n id: #{id} \n Email already sent'
next
# doesn't work
else
real_id?
end
end
Like I say in my comment in a_s_helper, if the card already has the label, I want to log the error and return all the way to the next card in the iteration. I get an "Invalid next" error from the current setup.
Is there a way to return a next back to the parent function or loop?

next is only valid in the direct context of a loop. Once you call into a method, you are no longer directly in that loop context. You cannot use next to short-circuit the outer loop like this.
You have a couple of options:
Return statuses from your predicate functions (which is what you should do, from a predicate!) and short-circuit the loop based on those, or
Use Ruby's catch...throw construct (which is NOT its raise/rescue exception handler, but is instead something like a block-scoped GOTO statement)
Option 1: Returning statuses. This is the most appropriate method, IMO. Predicate methods (those ending in ?) should conventionally return a boolean and be idempotent (that is, should have no side effects, such as logging a statement). They are conventionally used to ask a yes/no question. Deciding what to do based on that question should ideally be outside of their scope.
def card_handler
cards.each do |card|
#some non-relevant code is here on my end
if already_sent?
puts '\n Order info: \n id: #{id} \n Email already sent'
next
end
end
end
def already_sent?
case list_action
when 145
a_s_helper(p3_label)
when 145
a_s_helper(p2_label)
when 147
a_s_helper(p1_label)
end
end
def a_s_helper(label)
card::card_labels.include? label
end
This causes your helpers to return a true or false value to your loop, which can decide to log a message and go to the next iteration.
Option 2: catch...throw
def card_handler
cards.each do |card|
# Put all your code that should nomally run inside the catch block. If
# the message :email_sent is thrown, then Ruby will zip up the stack and
# resume execution at the end of the block. This will skip any unexecuted
# code in the block, essentially terminating the execution.
catch :email_sent do
already_sent?
end
end
end
def already_sent?
# ...
end
def a_s_helper(label)
# ...
throw :email_sent if card::card_labels.include? label
# ...
end
You may be tempted to use option 2, since it requires less careful control over method construction, but it is perilously close to exceptions as flow control which are widely considered an antipattern (it's essentially a slightly more fancy GOTO, which is notorious for making code difficult to read and debug). If you can simply return a status from your helpers and decide whether or not to continue the loop based on that, you should do so.

I want to show how I ended up implementing the solution I got from #Chris-heald for future people who see this question. I made it a little more compact. This was the code I ended up using:
def card_handler
cards.each do |card|
real_id?
puts "real_id? : #{real_id?}"
next if !(real_id?)
needs_email?
puts "needs_email? : #{needs_email?}"
next if !(needs_email?)
get_email_info
end
end
def needs_email?
case list_action
when 147
!(card::card_labels.include? p1_label::id)
when 146
!(card::card_labels.include? p2_label::id)
when 145
!(card::card_labels.include? p3_label::id)
else
false
end
end
def real_id?
id != 0 ? true : false
end
def get_email_info
#more stuff
end

Related

Many very similar functions, spaghetti code fix?

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.

How to DRY a list of functions in ruby that are differ only by a single line of code?

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.

Why doesn't calling next within a rescue block within a transaction within a loop work?

I have a loop like this:
# Iterate a list of items
req_wf_list.each do |req_wf|
# Begin a transaction
ReqWf.transaction do # ReqWf is an ActiveRecord model class
# Do some things
# ...
# 1. I want to be able to continue processing with the
# next iteration of the loop if there is an error here
# 2. I also want to rollback the transaction associated with
# this particular iteration if I encounter an error
begin
# Do something that might return an error
rescue
# Do some error processing
puts "Caught such and such error"
# Don't complete transaction (rollback),
# don't "do some more things",
# proceed to next item in req_wf_list
next
end
# Do some more things
# Shouldn't make it here if there is an error but I do indeed make it here
# ...
# End transaction
end
# End loop
end
Now, I would expect that calling "next" within the rescue block would cause the transaction associated with that particular iteration of the loop to rollback and for execution to resume at the top of the next iteration of the loop. Instead, execution appears to resume at the "Do some more things" line. It is as if the "next" statement is completely ignored. What am I missing?
Most likely that in this case next applies to transaction so you are in a nested loop situation.
This is an example of what can be done to solve the issue
req_wf_list.each do |req_wf|
catch :go_here do #:missingyear acts as a label
ReqWf.transaction do
throw :go_here unless something #break out of two loops
end
end #You end up here if :go_here is thrown
end
But in general, it is not a good practice to use next. You should be able to put a global begin .. rescue and have all the conditions inside of it, so that nothing else gets executed once you catch an error.
Update
I did some a small test and the behavior is as you expect it.
loop = [1,2,3]
loop.each do |value|
puts "value => #{value}"
ActiveRecord::Base.transaction do
puts "Start transaction"
begin
raise
rescue
puts "ActiveRecord::StatementInvalid"
next
end
puts "Should not get here!"
end
end
The output is the following:
value => 1
Start transaction
ActiveRecord::StatementInvalid
value => 2
Start transaction
ActiveRecord::StatementInvalid
value => 3
Start transaction
ActiveRecord::StatementInvalid
Is it possible that you had another error in your code before the next was being called ?
In any case, using the next statement is not the best option as I said before.

How to test the number of database calls in Rails

I am creating a REST API in rails. I'm using RSpec. I'd like to minimize the number of database calls, so I would like to add an automatic test that verifies the number of database calls being executed as part of a certain action.
Is there a simple way to add that to my test?
What I'm looking for is some way to monitor/record the calls that are being made to the database as a result of a single API call.
If this can't be done with RSpec but can be done with some other testing tool, that's also great.
The easiest thing in Rails 3 is probably to hook into the notifications api.
This subscriber
class SqlCounter< ActiveSupport::LogSubscriber
def self.count= value
Thread.current['query_count'] = value
end
def self.count
Thread.current['query_count'] || 0
end
def self.reset_count
result, self.count = self.count, 0
result
end
def sql(event)
self.class.count += 1
puts "logged #{event.payload[:sql]}"
end
end
SqlCounter.attach_to :active_record
will print every executed sql statement to the console and count them. You could then write specs such as
expect do
# do stuff
end.to change(SqlCounter, :count).by(2)
You'll probably want to filter out some statements, such as ones starting/committing transactions or the ones active record emits to determine the structures of tables.
You may be interested in using explain. But that won't be automatic. You will need to analyse each action manually. But maybe that is a good thing, since the important thing is not the number of db calls, but their nature. For example: Are they using indexes?
Check this:
http://weblog.rubyonrails.org/2011/12/6/what-s-new-in-edge-rails-explain/
Use the db-query-matchers gem.
expect { subject.make_one_query }.to make_database_queries(count: 1)
Fredrick's answer worked great for me, but in my case, I also wanted to know the number of calls for each ActiveRecord class individually. I made some modifications and ended up with this in case it's useful for others.
class SqlCounter< ActiveSupport::LogSubscriber
# Returns the number of database "Loads" for a given ActiveRecord class.
def self.count(clazz)
name = clazz.name + ' Load'
Thread.current['log'] ||= {}
Thread.current['log'][name] || 0
end
# Returns a list of ActiveRecord classes that were counted.
def self.counted_classes
log = Thread.current['log']
loads = log.keys.select {|key| key =~ /Load$/ }
loads.map { |key| Object.const_get(key.split.first) }
end
def self.reset_count
Thread.current['log'] = {}
end
def sql(event)
name = event.payload[:name]
Thread.current['log'] ||= {}
Thread.current['log'][name] ||= 0
Thread.current['log'][name] += 1
end
end
SqlCounter.attach_to :active_record
expect do
# do stuff
end.to change(SqlCounter, :count).by(2)

How can I output a calculated value using .detect in Ruby on Rails? (or alternative to .detect)

I currently have the following code:
events.detect do |event|
#detect does the block until the statement goes false
self.event_status(event) == "no status"
end
What this does is output the instance of event (where events is a string of different Models that all collectively call Events) when the event_status method outputs a "no status".
I would like the output to also include the value for delay where:
delay = delay + contact.event_delay(event)
event_delay method hasn't been written, but it would be similar (maybe redundant but I'll deal with that later) to event_status in looking at the delay between when an event was done and when it was supposed to be done.
Here is how event_status looks currently for reference:
def event_status target
# check Ticket #78 for source
target_class= target.class.name
target_id = target_class.foreign_key.to_sym
assoc_name = "contact_#{target_class.tableize}"
r = send(assoc_name).send("find_by_#{target_id}", target.id)
return "no status" unless r
"sent (#{r.date_sent.to_s(:long)})"
end
My concept of output should be [event,delay] so that, for example, I can access it as Array[:event] or Array[:delay] to get at the value.
****I was thinking maybe I should use yield on a method, but haven't quite put the pieces together (should the block passed to the method be the delay =+ for example, I think it is).**
I am not wed to the .detect method, it's what I started with and it appears to work, but it isn't allowing me to run the tally alongside it.
It's not entirely clear what you're asking for, but it sounds like you're trying to add up a delay until you reach a certain condition, and return the record that triggered the condition at the same time.
You might approach that using Enumerable#detect like you have, but by keeping a tally on the side:
def next_event_info
next_event = nil
delay = 0
events.detect do |event|
case (self.event_status(event))
when "no status"
true
else
delay += contact.event_delay(event)
false
end
end
[ next_event, delay ]
end
Update for if you want to add up all delays for all events, but also find the first event with the status of "no status":
def next_event_info
next_event = nil
delay = 0.0
events.each do |event|
case (self.event_status(event))
when "no status"
# Only assign to next_event if it has not been previously
# assigned in this method call.
next_event ||= event
end
# Tally up the delays for all events, converting to floating
# point to ensure they're not native DB number types.
delay += contact.event_delay(event).to_f
end
{
:event => next_event,
:delay => delay
}
end
This will give you a Hash in return that you can interrogate as info[:event] or info[:delay]. Keep in mind to not abuse this method, for example:
# Each of these makes a method call, which is somewhat expensive
next_event = next_event_info[:event]
delay_to_event = next_event_info[:delay]
This will make two calls to this method, both of which will iterate over all the records and do the calculations. If you need to use it this way, you might as well make a special purpose function for each operation, or cache the result in a variable and use that:
# Make the method call once, save the results
event_info = next_event_info
# Use these results as required
next_event = event_info[:event]
delay_to_event = event_info[:delay]

Resources