ruby - refactoring if else statement - ruby-on-rails

I've tried reading some tutorials on refactoring and I am struggling with conditionals. I don't want to use a ternary operator but maybe this should be extracted in a method? Or is there a smart way to use map?
detail.stated = if value[:stated].blank?
nil
elsif value[:stated] == "Incomplete"
nil
elsif value[:is_ratio] == "true"
value[:stated] == "true"
else
apply_currency_increment_for_save(value[:stated])
end

If you move this logic into a method, it can be made a lot cleaner thanks to early return (and keyword arguments):
def stated?(stated:, is_ratio: nil, **)
return if stated.blank? || stated == "Incomplete"
return stated == "true" if is_ratio == "true"
apply_currency_increment_for_save(stated)
end
Then...
detail.stated = stated?(value)

stated = value[:stated]
detail.stated = case
when stated.blank? || stated == "Incomplete"
nil
when value[:is_ratio] == "true"
value[:stated] == "true"
else
apply_currency_increment_for_save stated
end
What's happening: when case is used without an expression, it becomes the civilized equivalent of an if ... elsif ... else ... fi.
You can use its result, too, just like with if...end.

Move the code into apply_currency_increment_for_save
and do:
def apply_currency_increment_for_save(value)
return if value.nil? || value == "Incomplete"
return "true" if value == "true"
# rest of the code. Or move into another function if its too complex
end
The logic is encapsulated and it takes 2 lines only

I like #Jordan's suggestion. However, it seems the call is incomplete -- the 'is_ratio' parameter is also selected from value but not supplied.
Just for the sake of argument I'll suggest that you could go one step further and provide a class that is very narrowly focused on evaluating a "stated" value. This might seem extreme but it fits with the notion of single responsibility (the responsibility is evaluating "value" for stated -- while the 'detail' object might be focused on something else and merely makes use of the evaluation).
It'd look something like this:
class StatedEvaluator
attr_reader :value, :is_ratio
def initialize(value = {})
#value = ActiveSupport::StringInquirer.new(value.fetch(:stated, ''))
#is_ratio = ActiveSupport::StringInquirer.new(value.fetch(:is_ratio, ''))
end
def stated
return nil if value.blank? || value.Incomplete?
return value.true? if is_ratio.true?
apply_currency_increment_for_save(value)
end
end
detail.stated = StatedEvaluator.new(value).stated
Note that this makes use of Rails' StringInquirer class.

Related

Ruby array reject elements based on condition

I am trying to reject array items based on multiple conditions.
The code is as follows
def fetch_items
items= line_items.reject(&driving?)
if new_order_history_enabled?
items = items.reject{ |li| li.expenses == 0 }
end
items
end
def driving?
proc { |line_item| LineItemType.new(line_item, segment).drive? }
end
Is there a one liner or a more cleaner way to write this?
Something like
items= line_items.reject { |li| li.driving? && ( new_order_history_enabled? && li.expenses == 0)}
items= line_items.reject { |li| li.driving? || (new_order_history_enabled? && li.expenses == 0)}
Since you want both to apply here, I think you should use || instead of &&
That way, you are actually doing what you describe in your method. (and you only iterate once over the array, which is cool :) )
Although, and this is stylistic preference. I would prefer to do:
items = line_items.reject do |li|
li.driving? ||
(new_order_history_enabled? && li.expenses == 0)
end
since it might be clearer at a glance what we are doing
Personally I don't think a one-liner is always cleaner, especially when it's a long one-liner. The style that (to me) is cleaner, is to write:
def fetch_items
items= line_items.reject(&:driving?)
items= items.reject(&:zero_expenses?) if new_order_history_enabled?
end
def driving?
proc { |line_item| LineItemType.new(line_item, segment).drive? }
end
# in the LineItem class, define the zero_expenses? method:
def zero_expenses?
expenses.zero?
end

Check if not nil and not empty in Rails shortcut?

I have a show page for my Users and each attribute should only be visible on that page, if it is not nil and not an empty string. Below I have my controller and it is quite annoying having to write the same line of code #user.city != nil && #user.city != "" for every variable. I am not too familiar with creating my own methods, but can I somehow create a shortcut to do something like this: #city = check_attr(#user.city)? Or is there a better way to shorten this procedure?
users_controller.rb
def show
#city = #user.city != nil && #user.city != ""
#state = #user.state != nil && #user.state != ""
#bio = #user.bio != nil && #user.bio != ""
#contact = #user.contact != nil && #user.contact != ""
#twitter = #user.twitter != nil && #user.twitter != ""
#mail = #user.mail != nil && #user.mail != ""
end
There's a method that does this for you:
def show
#city = #user.city.present?
end
The present? method tests for not-nil plus has content. Empty strings, strings consisting of spaces or tabs, are considered not present.
Since this pattern is so common there's even a shortcut in ActiveRecord:
def show
#city = #user.city?
end
This is roughly equivalent.
As a note, testing vs nil is almost always redundant. There are only two logically false values in Ruby: nil and false. Unless it's possible for a variable to be literal false, this would be sufficient:
if (variable)
# ...
end
This is preferable to the usual if (!variable.nil?) or if (variable != nil) stuff that shows up occasionally. Ruby tends to wards a more reductionist type of expression.
One reason you'd want to compare vs. nil is if you have a tri-state variable that can be true, false or nil and you need to distinguish between the last two states.
You can use .present? which comes included with ActiveSupport.
#city = #user.city.present?
# etc ...
You could even write it like this
def show
%w(city state bio contact twitter mail).each do |attr|
instance_variable_set "##{attr}", #user[attr].present?
end
end
It's worth noting that if you want to test if something is blank, you can use .blank? (this is the opposite of .present?)
Also, don't use foo == nil. Use foo.nil? instead.

def and initializing. what is "obj"?

I don't understand the second line of the code below because of "obj = nil" in the first line.Given that, the second line seems to me that "obj" always becomes nil, return false and params[:id].to_i would be put into id_num. Could you tell me why it is written like this?
☆application_controller
def me? obj = nil
id_num = obj !=nil ? obj.member_id : params[:id].to_i
if session[:user_id] == id_num then
return true
else
return false
end
end
Declaring a method that has a parameter set to nil means that the parameter is optional.
def output_object_or_say_duck(obj=nil)
if obj
puts obj
else
puts 'Duck'
end
end
A good example of optional parameters as a design pattern is when you want default behavior that can be customized if necessary. A web request is a good example.
def make_web_request(website, parameters={}) # parameters OR empty hash
Net::HTTP.get("#{website}?#{ parameters.to_query }")
end
This line of code:
id_num = obj !=nil ? obj.member_id : params[:id].to_i
is a ternary operator which says if the object exists, assign id_num to the member_id attribute of obj, otherwise use param[:id].to_i (.to_i converts to an integer).
The obj = nil in the first line simply indicates that the default value of the obj parameter is nil. Meaning that if you don't call the method with any arguments, obj will be set to nil. So the me? method can take 0 or 1 arguments.

strange nil object result

I have a module:
module Voteable
def has_up_vote_of user
return ! self.votes.select{|v| v.user.id == user.id && v.value == 1}.empty?
end
def has_down_vote_of user
return ! self.votes.select{|v| v.user.id == user.id && v.value == -1}.empty?
end
end
Which is mixed into a model:
class Comment < ActiveRecord::Base
include Voteable
end
In a controller code, there is a check:
has_up_vote = #voteable.has_up_vote_of #user
has_down_vote = #voteable.has_down_vote_of #user
#voteable and #user are existing model items, found in a DB.
Suppose, voteable item has up-vote of user. After executing the code, has_up_vote will be equal to true, and has_down_vote will be nil.
Why nil, instead of false ?
I have used several variations of methods, but the problem is the same. Even this gives me the same effect:
def has_up_vote_of user
has = self.votes.select{|v| v.user.id == user.id && v.value == 1}.empty?
return !has.nil? && has
end
Posssible, i'm misunderstanding something, but this behavior is strange
Update
I've noticed very strange behaviour.
When i change methods to trivial:
def has_up_vote_of user
return false
end
def has_down_vote_of user
return false
end
They both returns nil, when i debug the app.
But, from console, they returns false.
It's more stange, because i cannot do anything with these results. These code is not working:
has_up_vote = false if has_up_vote.nil?
has_down_vote = false if has_down_vote.nil?
I think that the debugging environment you're running in is interfering with the actual value of has_down_votes. The select method should never return nil as defined.
Instead of !{}.empty? you could use {}.present?
Its more readable and the output will always be true/false only
I know this doesn't get to the root cause of your strange problem, but it should give you the results you want. Instead of
return ! self.votes.select{|v| v.user.id == user.id && v.value == -1}.empty?
try
return !!self.votes.select{|v| v.user.id == user.id && v.value == -1}.any?
The double exclamation point is intentional -- it will cause nil to become false. (!arr.empty? is equivalent to arr.any? which is equivalent to !!arr.any? -- except the last one converts the nil to false)

How to return a boolean value from a regex

I can't quite figure out what I'm doing wrong here..
if #calc.docket_num =~ /DC-000044-10/ || #calc.docket_num =~ /DC-67-09/
#calc.lda = true
else
#calc.lda = false
end
But it seems that #calc.docket_num can be any string whatsoever and it always returns as true.
Am I not doing this right?
This is a one-liner:
#calc.lda = !!(#calc.docket_num =~ /DC-000044-10|DC-67-09/)
The !! forces the response to true/false, then you can assign your boolean variable directly.
Alternatively you could use the triple equals (===) operator for the Regexp class which is used for determining equality when using case syntax.
#calc.lda = /DC-000044-10|DC-67-09/ === #calc.docket_num
#calc.lda
=> true
BEWARE
/Regexp/ === String is totally different than String === /Regexp/!!!! The method is not commutative. Each class implements === differently. For the question above, the regular expression has to be to the left of ===.
For the Regexp implementation, you can see more documentation on this (as of Ruby 2.2.1) here.
I think the issue is somewhere else in your implementation. Use this code to check it:
k = 'random information'
if k =~ /DC-000044-10/ || k =~ /DC-67-09/
puts 'success'
else
puts 'failure'
end
=> failure

Resources