How do I refactor these conditionals in my ruby class? - ruby

Here is my method. It checks if the file is usable. How do I dry this out?
##filepath = nil
def self.file_usable?
return false unless ##filepath
return false unless File.exists?(##filepath)
return false unless File.readable?(##filepath)
return false unless File.writable?(##filepath)
return true
end
Should I be using some kind of loop?

def self.file_usable?
##filepath and File.exists?(##filepath) and File.readable?(##filepath) and File.writable?(##filepath)
end

I wouldn't do it this way, but since you asked "to just refactor all these methods action on the same variable"...
def self.file_usable?
##filepath && [:exists?, :readable?, :writable?].all? { |m| File.send(m, ##filepath) }
end
This may be useful if you programatically need to decide which methods must be checked. If that's an isolated function, I'd write:
def self.file_usable?
f = ##filepath
f && File.exists?(f) && File.readable?(f) && File.writable?(f)
end

You can use File#stat and check mode value.
s = File.stat("testfile")
other_can_rwx = s.mode && 0007

I often use this technique when readability is my main concern:
def self.file_usable?
[##filepath,
File.exists?(##filepath),
File.readable?(##filepath),
File.writable?(##filepath)].all?
end
Do note however that there is a big difference in this approach, in that all expressions are evaluated.
The following works because nil.some_method is never called:
nil and nil.some_method
This however will throw an exception, because everything is always evaluated:
[nil, nil.some_method].all?

It's probably not a good idea but you could theoretically do something like:
def self.file_usable?
File.writable? ##filepath rescue nil
end

Another variation:
CHECK_METHODS = [:exists?, :readable?, :writable?] \
.map{ |m| File.method(m) } \
.unshift(lambda{ |x| x })
def self.file_usable?
CHECK_METHODS.all? { |m| m[##filepath] }
end

Related

Ruby Enumerable#find returning mapped value

Does Ruby's Enumerable offer a better way to do the following?
output = things
.find { |thing| thing.expensive_transform.meets_condition? }
.expensive_transform
Enumerable#find is great for finding an element in an enumerable, but returns the original element, not the return value of the block, so any work done is lost.
Of course there are ugly ways of accomplishing this...
Side effects
def constly_find(things)
output = nil
things.each do |thing|
expensive_thing = thing.expensive_transform
if expensive_thing.meets_condition?
output = expensive_thing
break
end
end
output
end
Returning from a block
This is the alternative I'm trying to refactor
def costly_find(things)
things.each do |thing|
expensive_thing = thing.expensive_transform
return expensive_thing if expensive_thing.meets_condition?
end
nil
end
each.lazy.map.find
def costly_find(things)
things
.each
.lazy
.map(&:expensive_transform)
.find(&:meets_condition?)
end
Is there something better?
Of course there are ugly ways of accomplishing this...
If you had a cheap operation, you'd just use:
collection.map(&:operation).find(&:condition?)
To make Ruby call operation only "on a as-needed basis" (as the documentation says), you can simply prepend lazy:
collection.lazy.map(&:operation).find(&:condition?)
I don't think this is ugly at all—quite the contrary— it looks elegant to me.
Applied to your code:
def costly_find(things)
things.lazy.map(&:expensive_transform).find(&:meets_condition?)
end
I would be inclined to create an enumerator that generates values thing.expensive_transform and then make that the receiver for find with meets_condition? in find's block. For one, I like the way that reads.
Code
def costly_find(things)
Enumerator.new { |y| things.each { |thing| y << thing.expensive_transform } }.
find(&:meets_condition?)
end
Example
class Thing
attr_reader :value
def initialize(value)
#value = value
end
def expensive_transform
self.class.new(value*2)
end
def meets_condition?
value == 12
end
end
things = [1,3,6,4].map { |n| Thing.new(n) }
#=> [#<Thing:0x00000001e90b78 #value=1>, #<Thing:0x00000001e90b28 #value=3>,
# #<Thing:0x00000001e90ad8 #value=6>, #<Thing:0x00000001e90ab0 #value=4>]
costly_find(things)
#=> #<Thing:0x00000001e8a3b8 #value=12>
In the example I have assumed that expensive_things and things are instances of the same class, but if that is not the case the code would need to be modified in the obvious way.
I don't think there is a "obvious best general solution" for your problem, which is also simple to use. You have two procedures involved (expensive_transform and meets_condition?), and you also would need - if this were a library method to use - as a third parameter the value to return, if no transformed element meets the condition. You return nil in this case, but in a general solution, expensive_transform might also yield nil, and only the caller knows what unique value would indicate that the condition as not been met.
Hence, a possible solution within Enumerable would have the signature
class Enumerable
def find_transformed(default_return_value, transform_proc, condition_proc)
...
end
end
or something similar, so this is not particularily elegant either.
You could do it with a single block, if you agree to merge the semantics of the two procedures into one: You have only one procedure, which calculates the transformed value and tests it. If the test succeeds, it returns the transformed value, and if it fails, it returns the default value:
class Enumerable
def find_by(default_value, &block)
result = default_value
each do |element|
result = block.call(element)
break if result != default_value
end
end
result
end
You would use it in your case like this:
my_collection.find_by(nil) do |el|
transformed_value = expensive_transform(el)
meets_condition?(transformed_value) ? transformed_value : nil
end
I'm not sure whether this is really intuitive to use...

ruby do map and compact together

I'm new to ruby and this looks wrong but works fine
def get_internal_deps
self.internal_dependencies = self.sources.map do |f|
s = File.open(File.join(self.dir, f)).grep(/\d{8}-\w{5}/)
if s.length > 0
{:file => f, :line => s}
end
end.compact
#how crazy does that look?
end
So how do I do this without having an end.compact?
Some notes
Your method is called get_internal_deps, but it looks like it actually sets an instance variable.
You could define internal_dependencies and use caching.
In this case, you'd need to remove any attr_reader/writer/accessor for #internal_dependencies.
File.open(f) isn't really clean.
You don't need self in self.dir or self.sources
:line is an Array. Shouldn't it be called :lines?
2 separate, short methods might be better than a bigger one.
Refactored code
def internal_dependencies
#internal_dependencies ||= sources.map{|s| parse_dependency(s) }
.reject{|h| h[:line].empty? }
end
private
def parse_dependency(source)
{
file: source,
line: File.readlines(File.join(dir, source)).grep(/\d{8}-\w{5}/)
}
end
To avoid compact one might use reduce (Enumerable#each_with_object in this particular case) instead of map:
def get_internal_deps
self.internal_dependencies = sources.each_with_object do |f, acc|
s = File.open(File.join(self.dir, f)).grep(/\d{8}-\w{5}/)
acc << {:file => f, :line => s} if s.length > 0
end
end
Also, note that an explicit self receiver might make sense in a case of assignment, but it is completely redundant in RHO (sources in this snippet.)

Ruby: Combine two similar methods into one?

I have two extremely similar methods in my Ruby object for a Rails app. I know they can be combined, I just don't know how. (Extra points if you can find a more beautiful way to handle possible nils besides return unless, not using #try.)
def is_portal_admin?(resource)
return unless resource.user && resource.user.clinic_memberships.any?
memberships = resource.user.clinic_memberships.collect { |membership| membership.portal_admin? }
memberships.include?(true)
end
def is_staff_admin?(resource)
return unless resource.user && resource.user.clinic_memberships.any?
memberships = resource.user.clinic_memberships.collect { |membership| membership.staff_admin? }
memberships.include?(true)
end
How about:
def is_admin_of_type?(type, resource)
return unless resource.user && resource.user.clinic_memberships.any? && type
memberships = resource.user.clinic_memberships.collect { |membership| membership.send("#{type}_admin?") }
memberships.include?(true)
end
If someone gives a nonexistent type, it'l throw NoMethodError. Also, it's forward compatible if you add more admin types.
Instead of your collect and include? mechanism, you can simply use any?. If clinic_memberships always return an array (which it does if it is e.g. a has_many association), you don't even need to check that.
def has_membership?(resource, &block)
return unless resource.user
resource.user.clinic_memberships.any?(&block)
end
This can then be called like
has_membership?(resource, &:portal_admin?)
which is equivalent to
has_memberhsip?(resource){|m| m.portal_admin?}
def is_admin?(resource, kind)
if resource.user && resource.user.clinic_memberships.any?
!!resource.user.clinic_memberships.detect { |membership| membership.send("#{kind}_admin?") }
end
end
If you don't enter the if branch, nil is returned, so doing your conditional like above will yield the same result as an explicit return unless...
Add a second parameter and pass :staff or :portal (or "staff" or "portal"). Using "send" will eval at run time into "staff_admin?" or "portal_admin?"
Using detect instead of collect + include? will return an object if at least one found and the !! double negates to turn it into a true/false result.
I'd personally simply do it this way since that resource.user.clinic_memberships.any? is moot in grand scheme of things:
def is_admin?(resource, kind)
!!resource.user.clinic_memberships.detect { |membership| membership.send("#{kind}_admin?") } if resource.user
end
If you're actually trying to guard against clinic_memberships being nil, then you do need the 2nd half of the conditional, but drop the ".any?", otherwise you'll get an error testing any? against nil.
def is_portal_admin?(resource)
is_admin_of_type?(resource, :portal)
end
def is_staff_admin?(resource)
is_admin_of_type?(resource, :staff)
end
def is_admin_of_type?(resource, type)
if (user = resource.user)
user.clinic_memberships.any? { |ms| ms.send("#{type}_admin?") }
end
end
It's redundant to check if there are memberships.
You can add || false after the conditinal so you make sure your method? returns a boolean.
You can make is_admin_of_type? private.

Ruby - elegantly call a method on the elements of two arrays

I am comparing arrays of objects and I have a method that determines whether or not two elements are equivalent. I want to to call this method on each pair of elements from both arrays, is there an elegant way of doing this to find a truth value (i.e true if all elements in each array were equivalent, false otherwise)
this is what I have currently:
c = false
self.children.zip(other.children).each do |s,o|
c = s.equiv o # I need a good way to store this result
break if not c
end
I was hoping I could do something like this:
c = self.children.zip(other.children).each{|s,o| s.equiv o}
Any help would be appreciated.
Well you have Enumerable#all?
c = self.children.zip(other.children).all? {|s,o| s.equiv o}
How about using all?
c = self.children.zip(other.children).all?{|s,o| s.equiv o}
A better solution is to just define == on your objects. Then you can use Array#== to do your work because it already does an all-pairs comparison.
Here's a simple example:
class Widget
attr_reader :name
def initialize(name)
#name = name
end
def ==(other)
#name == other.name
end
end
if $0 == __FILE__
require 'minitest/autorun'
describe 'widget arrays' do
let(:some_widgets) { %w(foo bar baz).map { |name| Widget.new(name) } }
let(:diff_widgets) { %w(bar baz spam).map { |name| Widget.new(name) } }
it 'returns true if the widget arrays are the same' do
(some_widgets == some_widgets).must_equal true
end
it 'returns false if the widget arrays are different' do
(some_widgets == diff_widgets).must_equal false
end
end
end
You just call some_widgets == my_other_widgets to compare each element.
I think if you replace each by map, c will be the array you're looking for. I can't be 100% sure, because I can't test right away, and I know there is some issues with zip.

Refactoring respond_to? call in if-elsif-else condition

I have the following method and want to make it more readable:
def value_format(value)
if value.respond_to? :to_actor
value.to_actor
elsif value.respond_to? :to_subject
value.to_subject
elsif value.respond_to? :to_json
value.to_json
elsif value.respond_to? :to_hash
value.to_hash
else
value.inspect
end
end
This is my solution. What do you think?
def value_format(value)
methods = [:to_actor, :to_subject, :to_json, :to_hash, :inspect]
value.send(methods.find_all { |m| m if value.respond_to? m }.first)
end
Your solution looks fine, but you might as well use find instead of find_all:
METHODS = [:to_actor, :to_subject, :to_json, :to_hash, :inspect]
def value_format(value)
value.send(METHODS.find { |m| value.respond_to? m })
end
Using a constant has the advantage of not creating a new array every time value_format is ran.
Seems there's a pretty simple optimization to your solution:
def value_format(value)
methods = [:to_actor, :to_subject, :to_json, :to_hash]
value.send(methods.find(:inspect) { |m| value.respond_to? m })
end
The facets gem provides an elegant solution (I think) to this problem. It combines the two steps of checking if an object responds to a method and actually calling that method into a single step.
So your example could be rewritten as this:
require 'facets/kernel/respond'
def value_format(v)
v.respond.to_actor || v.respond.to_subject || v.respond.to_json || v.respond.to_hash || v.respond.inspect
end
Note that this method only works if it is safe to assume that none of these methods are going to return nil or false (because respond returns nil if the object doesn't respond, that is what allows us to chain it together with a bunch of ors).
Since all of the methods you listed should return strings, I believe this approach would work fine in your example.
Documentation:
# Like #respond_to? but returns the result of the call
# if it does indeed respond.
#
# class RespondExample
# def f; "f"; end
# end
#
# x = RespondExample.new
# x.respond(:f) #=> "f"
# x.respond(:g) #=> nil
#
# or
#
# x.respond.f #=> "f"
# x.respond.g #=> nil

Resources