Reputation: 35318
I'm going through a phase of trying to avoid temporary variables and over-use of conditional where I can use a more fluid style of coding. I've taken a great liking to using #tap
in places where I want to get the value I need to return, but do something with it before I return it.
def fluid_method
something_complicated(a, b, c).tap do |obj|
obj.update(:x => y)
end
end
Vs. the procedural:
def non_fluid_method
obj = something_complicated(a, b, c)
obj.update(:x => y)
obj # <= I don't like this, if it's avoidable
end
Obviously the above examples are simple, but this is a pretty common coding style in the ruby community nonetheless. I'll sometimes use #inject
to pass an object through a series of filters too:
things.inject(whatever) do |obj, thing|
thing.filter(obj)
end
Vs. the procedural:
obj = whatever
things.each do |thing|
obj = thing.filter(obj)
end
obj
Now I'm facing repeated use of a condition like the following, and looking for a more fluid approach to handling it:
def not_nice_method
obj = something_complex(a, b, c)
if a_predicate_check?
obj.one_more_method_call
else
obj
end
end
The (slightly) cleaner solution is to avoid the temporary variable at the cost of duplication:
def not_nice_method
if a_predicate_check?
something_complex(a, b, c).one_more_method_call
else
something_complex(a, b, c)
end
end
I can't help but feeling the desire to use something almost like #tap
here though.
What other patterns might I follow here. I realise this is all just nonsensical sugar to some people and that I should just move onto more interesting problems, but I'm trying to learn to write in a more functional style, so I'm just curious what long-term rubyists have determined to be good ways to tackle situations like this. These examples are hugely simplified.
Upvotes: 33
Views: 7193
Reputation: 2311
class Object
def apply_if(pred)
if pred
yield self
else
self
end
end
end
Typical usage:
def rlrs_usage_by_group(group_id, date_range = nil)
views = ContentView.joins(user: [:groups])
.where(groups: { id: group_id })
.where(viewable_type: 'RealStory')
.apply_if(date_range) {
_1.where(viewed_at: date_range)
}
end
Your case:
def nice_method
something_complex(a, b, c)
.apply_if(a_predicate_check?) {
_1.one_more_method_call
}
end
or even
something_complex(a, b, c)
.apply_if(a_predicate_check, &:one_more_method_call)
Upvotes: 0
Reputation: 1259
instance_eval
can be misused for this purpose
"this".instance_eval { |test| test + " works" }
since 2.5 it is possible to use yield_self
"easy".yield_self{ |a| a + " peasy" }
Read more:
https://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-instance_eval
https://ruby-doc.org/core-2.5.0/Object.html#method-i-yield_self
Upvotes: 8
Reputation: 10201
def best_nice_method
something_complex(a, b, c).tap |obj|
break obj.one_more_method_call if a_predicate_check?
end
end
The magic is break
in tap
returns another value.
new
ruby 2.5 has yield_self
which exactly you want.
https://stackoverflow.com/a/47890832/683157
Upvotes: 14
Reputation: 67900
Define Object#as
:
class Object
def as
yield self
end
end
And now you can write:
def not_sure_this_is_nice_enough_method1
something_complex(a, b, c).as do |obj|
a_predicate_check? ? obj.one_more_method_call : obj
end
end
Upvotes: 14
Reputation: 433
I needed to do something like this and I like tokland's answer, but I didn't want to pollute Object for the small script I was writing. Instead, I made use of tap
on an array:
[something_complicated].tap { |s| s[0] = new_cool_thing)}.first
Upvotes: 1
Reputation: 9511
I found a method in the Facets gem that might be what you were looking for: Kernel#ergo
So your original method:
def not_nice_method
obj = something_complex(a, b, c)
if a_predicate_check?
obj.one_more_method_call
else
obj
end
end
might end up looking something like this:
require 'facets/kernel/ergo'
def nice_method
something_complex(a, b, c).ergo do |_|
a_predicate_check? ? _.one_more_method_call : _
end
end
Upvotes: 3