Combining two methods into one - ruby-on-rails

I have two methods.
def response_code_description(code)
#response_code_description ||= current_account.one_call_center.response_codes_repository_class.new.to_api_collection
#response_code_description.find {|k| k['code'] == code}.try(:[], 'description')
end
def ticket_response_code_with_description(ticket_response)
#ticket_response_code_with_description ||= ticket_response.ticket.one_call_center.response_codes_repository_class.new.to_api_collection
#ticket_response_code_with_description.find { |k| k['code'] == ticket_response.code }.try(:[], 'description')
end
I think I can combine them.
So.
def response_code_with_description(one_call_center, code)
#ticket_response_code_with_description ||= one_call_center.response_codes_repository_class.new.to_api_collection
#ticket_response_code_with_description.find { |k| k['code'] == code }.try(:[], 'description')
end
and call this method so
response_code_with_description(current_account.one_call_center, ticket_response.code)
response_code_with_description(ticket_response.ticket.one_call_center, code)
what do you think?

The primary difference between these two methods seems to be this one part:
k['code'] == code
k['code'] == ticket_response.code
So in other words you either compare to the argument directly, or the code method called on the argument. Address that problem by making the argument adaptive:
def to_description(code)
code = code.code if (code.respond_to?(:code))
# ... Rest of code presuming `code` is the thing to compare against.
end
This eliminates the difference between the two.
I'd strongly encourage you to revisit the names used in your code here s they are unreasonably verbose.

Related

Refactoring a large method with many conditions - Ruby

I have this method:
method:
def unassigned_workers?(users)
assigned_users = []
unassigned_users = []
users.each do |user|
if user.designated_to_assignment?(self)
assigned_users << user
else
unassigned_users << user
end
end
if unassigned_users.count > 0
true
else
false
end
end
It's in my Assignment model. The assignment model has many Users, and basically what this method is trying to do is check if the user is designated to the assignment based on another relationship I have setup. It checks if the user is assigned and pushes it on the correct array. Does anybody know how I can refactor this to be smaller and more readable?
How about using any?
assigned_users not necessarily required.
def unassigned_workers?(users)
users.any? { |user| !user.designated_to_assignment?(self) }
end
not sure why you have assigned_users at all
try:
def unassigned_workers?(users)
users.reject { |user| user.designated_to_assignment?(self) }.count > 0
end
reject removes elements from a collection that match a predicate.
Moreover passing a self in a model as an argument is a code smell, maybe the dependencies are reversed

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.

Interpolating an attribute's key before save

I'm using Rails 4 and have an Article model that has answer, side_effects, and benefits as attributes.
I am trying to create a before_save method that automatically looks at the side effects and benefits and creates links corresponding to another article on the site.
Instead of writing two virtually identical methods, one for side effects and one for benefits, I would like to use the same method and check to assure the attribute does not equal answer.
So far I have something like this:
before_save :link_to_article
private
def link_to_article
self.attributes.each do |key, value|
unless key == "answer"
linked_attrs = []
self.key.split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.key = linked_attrs.join('; ')
end
end
end
but chaining on the key like that gives me an undefined method 'key'.
How can I go about interpolating in the attribute?
in this bit: self.key you are asking for it to literally call a method called key, but what you want, is to call the method-name that is stored in the variable key.
you can use: self.send(key) instead, but it can be a little dangerous.
If somebody hacks up a new form on their browser to send you the attribute called delete! you don't want it accidentally called using send, so it might be better to use read_attribute and write_attribute.
Example below:
def link_to_article
self.attributes.each do |key, value|
unless key == "answer"
linked_attrs = []
self.read_attribute(key).split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.write_attribute(key, linked_attrs.join('; '))
end
end
end
I'd also recommend using strong attributes in the controller to make sure you're only permitting the allowed set of attributes.
OLD (before I knew this was to be used on all attributes)
That said... why do you go through every single attribute and only do something if the attribute is called answer? why not just not bother with going through the attributes and look directly at answer?
eg:
def link_to_article
linked_attrs = []
self.answer.split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.answer = linked_attrs.join('; ')
end

Conditional code in the define_method block

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.

Ruby on Rails: how do I set a variable where the variable being changed can change?

i want to do
current_user.allow_????? = true
where ????? could be whatever I wanted it to be
I've seen it done before.. just don't remember where, or what the thing is called.
foo = "bar"
current_user.send("allow_#{foo}=", true)
EDIT:
what you're asking for in the comment is another thing. If you want to grab a constant, you should use for instance
role = "admin"
User.const_get(role)
That's a "magic method" and you implement the method_missing on your current_user object. Example from Design Patterns
#example method passed into computer builder class
builder.add_dvd_and_harddisk
#or
builder.add_turbo_and_dvd_dvd_and_harddisk
def method_missing(name, *args)
words = name.to_s.split("_")
return super(name, *args) unless words.shift == 'add'
words.each do |word|
#next is same as continue in for loop in C#
next if word == 'and'
#each of the following method calls are a part of the builder class
add_cd if word == 'cd'
add_dvd if word == 'dvd'
add_hard_disk(100000) if word == 'harddisk'
turbo if word == 'turbo'
end
end

Resources