I have some ruby code that gets a json from Jenkins that contains an array of n items. The item I want has a key called "lastBuiltRevision"
I know I can loop through the array like so
actions.each do |action|
if action["lastBuiltRevision"]
lastSuccessfulRev = action["lastBuiltRevision"]["SHA1"]
break
end
end
but that feels very clunky and devoid of the magic that I usually feel when working with ruby.
I have only been tinkering with it for roughly a week now, and I feel that I may be missing something to make this easier/faster.
Is there such a thing? or is manual iteration all I can do?
I am kind of hoping for something like
lastSuccessfulRev = action.match("lastBuildRevision/SHA1")
Using Enumerable#find:
actions = [
{'dummy' => true },
{'dummy' => true },
{'dummy' => true },
{'lastBuiltRevision' => { "SHA1" => "123abc" }},
{'dummy' => true },
]
actions.find { |h|
h.has_key? 'lastBuiltRevision'
}['lastBuiltRevision']['SHA1']
# => "123abc"
UPDATE
Above code will throw NoMethodError if there's no matched item. Use follow code if you don't want get an exception.
rev = actions.find { |h| h.has_key? 'lastBuiltRevision' }
rev = rev['lastBuiltRevision']['SHA1'] if rev
Here's another way to do it, making use of the form of Enumerable#find that takes a parameter ifnone which is called, and its return value returned, if find's block never evaluates true.
I assume the method is to return nil if either key is not found.
Code
def look_for(actions, k1, k2)
actions.find(->{{k1=>{}}}) { |e| e.key?(k1) }[k1][k2]
end
Examples
actions = [{ 'dog'=>'woof' }, { 'lastBuiltRevision'=>{ 'SHA1'=> 2 } }]
look_for(actions, 'lastBuiltRevision', 'SHA1') #=> 2
look_for(actions, 'cat, 'SHA1') #=> nil
look_for(actions, 'lastBuiltRevision', 'cat') #=> nil
Explanation
I've made find's ifnone parameter the lambda:
l = ->{{k1=>{}}}
so that:
k1 = "cats"
h = l.call #=> {"cats"=>{}}
h[k1]['SHA1'] #=> {}['SHA1']
#=> nil
Try:
action.map { |a| a["lastBuiltRevision"] }.compact.map { |lb| lb["SHA1"] }.first
Related
I'd like to calculate the difference for various values inside 2 hashes with the same structure, as concisely as possible. Here's a simplified example of the data I'd like to compare:
hash1 = {"x" => { "y" => 20 } }
hash2 = {"x" => { "y" => 12 } }
I have a very simple method to get the value I want to compare. In reality, the hash can be nested a lot deeper than these examples, so this is mostly to keep the code readable:
def get_y(data)
data["x"]["y"]
end
I want to create a method that will calculate the difference between the 2 values, and can take a method like get_y as an argument, allowing me to re-use the code for any value in the hash. I'd like to be able to call something like this, and I'm not sure how to write the method get_delta:
get_delta(hash1, hash2, get_y) # => 8
The "Ruby way" would be to pass a block:
def get_delta_by(obj1, obj2)
yield(obj1) - yield(obj2)
end
hash1 = {"x" => { "y" => 20 } }
hash2 = {"x" => { "y" => 12 } }
get_delta_by(hash1, hash2) { |h| h["x"]["y"] }
#=> 8
A method could be passed (indirectly) via:
def get_y(data)
data["x"]["y"]
end
get_delta_by(hash1, hash2, &method(:get_y))
#=> 8
Building on Stefan's response, if you want a more flexible get method you can actually return a lambda from the function and pass arguments for what you want to get. This will let you do error handling nicely:
Starting with the basics from above...
def get_delta_by(obj1, obj2)
yield(obj1) - yield(obj2)
end
hash1 = {"x" => { "y" => 20 } }
hash2 = {"x" => { "y" => 12 } }
get_delta_by(hash1, hash2) { |h| h["x"]["y"] }
Then we can define a get_something function which takes a list of arguments for the path of the element to get:
def get_something(*args)
lambda do |data|
args.each do |arg|
begin
data = data.fetch(arg)
rescue KeyError
raise RuntimeError, "KeyError for #{arg} on path #{args.join(',')}"
end
end
return data
end
end
Finally we call the function using the ampersand to pass the lambda as a block:
lambda_getter = get_something("x","y")
get_delta_by(hash1, hash2, &lambda_getter)
That last bit can be a one liner... but wrote it as two for clarity here.
In Ruby 2.3, you can use Hash#dig method, if it meets your needs.
hash1.dig("x", "y") - hash2.dig("x", "y")
#=> 8
Is there a more readable way to test if delivery_status is one of three strings?
if ["partial", "successful", "unsuccessful"].include? delivery_status
Here's what I'd really like, but it doesn't work:
if delivery_status == ("partial" or "successful" or "unsuccessful")
While I would not advise this, you can do it anyway:
def String
def is_one_of?(array)
array.include?(self)
end
end
And then:
if delivery_status.is_one_of?([...])
But there is a much better solution: use case (if possible in your situation):
case delivery_status
when 'partial', 'successful', 'unsuccessful'
#stuff happens here
when ... #other conditions
end
if %w[partial successful unsuccessful].include? delivery_status
It's not intuitive, but using the Regexp engine can speed these tests up:
STATES = ["partial", "successful", "unsuccessful"]
regex = /\b(?:#{ Regexp.union(STATES).source })\b/i
=> /\b(?:partial|successful|unsuccessful)\b/i
delivery_status = 'this is partial'
!!delivery_status[regex]
=> true
delivery_status = 'that was successful'
!!delivery_status[regex]
=> true
delivery_status = 'Yoda says, "unsuccessful that was not."'
!!delivery_status[regex]
=> true
delivery_status = 'foo bar'
!!delivery_status[regex]
=> false
If I'm not searching a string for the word, I'll use a hash for a lookup:
STATES = %w[partial successful unsuccessful].each_with_object({}) { |s, h| h[s] = true }
=> {"partial"=>true, "successful"=>true, "unsuccessful"=>true}
STATES['partial']
=> true
STATES['foo']
=> nil
Or use:
!!STATES['foo']
=> false
If you want a value besides true/nil/false:
STATES = %w[partial successful unsuccessful].each_with_index.with_object({}) { |(s, i), h| h[s] = i }
=> {"partial"=>0, "successful"=>1, "unsuccessful"=>2}
That'll give you 0, 1, 2 or nil.
I ended up doing something similar to #Linuxios's suggestion
class String
def is_one_of(*these)
these.include? self
end
def is_not_one_of(*these)
these.include? self ? false : true
end
end
This allows me to write:
if delivery_status.is_one_of "partial", "successful", "unsuccessful"
Typically, parsing XML or JSON returns a hash, array, or combination of them. Often, parsing through an invalid array leads to all sorts of TypeErrors, NoMethodErrors, unexpected nils, and the like.
For example, I have a response object and want to find the following element:
response['cars'][0]['engine']['5L']
If response is
{ 'foo' => { 'bar' => [1, 2, 3] } }
it will throw a NoMethodError exception, when all I want is to see is nil.
Is there a simple way to look for an element without resorting to lots of nil checks, rescues, or Rails try methods?
Casper was just before me, he used the same idea (don't know where i found it, is a time ago) but i believe my solution is more sturdy
module DeepFetch
def deep_fetch(*keys, &fetch_default)
throw_fetch_default = fetch_default && lambda {|key, coll|
args = [key, coll]
# only provide extra block args if requested
args = args.slice(0, fetch_default.arity) if fetch_default.arity >= 0
# If we need the default, we need to stop processing the loop immediately
throw :df_value, fetch_default.call(*args)
}
catch(:df_value){
keys.inject(self){|value,key|
block = throw_fetch_default && lambda{|*args|
# sneak the current collection in as an extra block arg
args << value
throw_fetch_default.call(*args)
}
value.fetch(key, &block) if value.class.method_defined? :fetch
}
}
end
# Overload [] to work with multiple keys
def [](*keys)
case keys.size
when 1 then super
else deep_fetch(*keys){|key, coll| coll[key]}
end
end
end
response = { 'foo' => { 'bar' => [1, 2, 3] } }
response.extend(DeepFetch)
p response.deep_fetch('cars') { nil } # nil
p response.deep_fetch('cars', 0) { nil } # nil
p response.deep_fetch('foo') { nil } # {"bar"=>[1, 2, 3]}
p response.deep_fetch('foo', 'bar', 0) { nil } # 1
p response.deep_fetch('foo', 'bar', 3) { nil } # nil
p response.deep_fetch('foo', 'bar', 0, 'engine') { nil } # nil
I tried to look through both the Hash documentation and also through Facets, but nothing stood out as far as I could see.
So you might want to implement your own solution. Here's one option:
class Hash
def deep_index(*args)
args.inject(self) { |e,arg|
break nil if e[arg].nil?
e[arg]
}
end
end
h1 = { 'cars' => [{'engine' => {'5L' => 'It worked'}}] }
h2 = { 'foo' => { 'bar' => [1, 2, 3] } }
p h1.deep_index('cars', 0, 'engine', '5L')
p h2.deep_index('cars', 0, 'engine', '5L')
p h2.deep_index('foo', 'bonk')
Output:
"It worked"
nil
nil
If you can live with getting an empty hash instead of nil when there is no key, then you can do it like this:
response.fetch('cars', {}).fetch(0, {}).fetch('engine', {}).fetch('5L', {})
or save some types by defining a method Hash#_:
class Hash; def _ k; fetch(k, {}) end end
response._('cars')._(0)._('engine')._('5L')
or do it at once like this:
["cars", 0, "engine", "5L"].inject(response){|h, k| h.fetch(k, {})}
For the sake of reference, there are several projects i know of that tackle the more general problem of chaining methods in the face of possible nils:
andand
ick
zucker's egonil
methodchain
probably others...
There's also been considerable discussion in the past:
Ruby nil-like object - One of many on SO
Null Objects and Falsiness - Great article by Avdi Grimm
The 28 Bytes of Ruby Joy! - Very interesting discussion following J-_-L's post
More idiomatic way to avoid errors when calling method on variable that may be nil? on ruby-talk
et cetera
Having said that, the answers already provided probably suffice for the more specific problem of chained Hash#[] access.
I would suggest an approach of injecting custom #[] method to instances we are interested in:
def weaken_checks_for_brackets_accessor inst
inst.instance_variable_set(:#original_get_element_method, inst.method(:[])) \
unless inst.instance_variable_get(:#original_get_element_method)
singleton_class = class << inst; self; end
singleton_class.send(:define_method, :[]) do |*keys|
begin
res = (inst.instance_variable_get(:#original_get_element_method).call *keys)
rescue
end
weaken_checks_for_brackets_accessor(res.nil? ? inst.class.new : res)
end
inst
end
Being called on the instance of Hash (Array is OK as all the other classes, having #[] defined), this method stores the original Hash#[] method unless it is already substituted (that’s needed to prevent stack overflow during multiple calls.) Then it injects the custom implementation of #[] method, returning empty class instance instead of nil/exception. To use the safe value retrieval:
a = { 'foo' => { 'bar' => [1, 2, 3] } }
p (weaken_checks_for_brackets_accessor a)['foo']['bar']
p "1 #{a['foo']}"
p "2 #{a['foo']['bar']}"
p "3 #{a['foo']['bar']['ghgh']}"
p "4 #{a['foo']['bar']['ghgh'][0]}"
p "5 #{a['foo']['bar']['ghgh'][0]['olala']}"
Yielding:
#⇒ [1, 2, 3]
#⇒ "1 {\"bar\"=>[1, 2, 3]}"
#⇒ "2 [1, 2, 3]"
#⇒ "3 []"
#⇒ "4 []"
#⇒ "5 []"
Since Ruby 2.3, the answer is dig
I'm working with a Ruby API that takes a series of boolean switches, something along the lines of:
validate({ :can_foo => true, :can_bar => false, :can_baz => true, ... })
I'm writing a series of tests to verify that the API is behaving as it should, so I need to construct a lot of sets of switches. It seemed wasteful to continue to type :foo => true all the time, so I figured I'd write a little Ruby ditty to convert an array to this structure, e.g.
true_vals = %w( these are my true items )
false_vals = %w( these are my false items )
convert = lambda{ |arr, truthiness| arr.inject({}){ |res, key| res.update(key=>truthiness) } }
falsify = lambda{ |arr| convert.call(arr, false) }
truthify = lambda{ |arr| convert.call(arr, true) }
validate( truthify.call(true_vals).merge( falsify.call(false_vals) ) )
Does that seem any better than simply typing out a long list of :sym => [true|false] pairs? Is there a better way to do this?
(I started with true_vals.inject({}){ |res, key| res.update(key=>true) } but that doesn't feel DRY enough; I'd have to copy-paste & s/true/false/ to do the false ones; and I'm doing it many many times so a lambda seems reasonable)
Thanks,
--
Matt
cs = { true => [:y, :yes],
false => [:n, :no] }
Hash[cs.map{ |k, vs| vs.map{ |v| [v, k] } }.flatten(1)]
#=> {:y=>true, :yes=>true, :no=>false, :n=>false}
Here is one solution:
switches={}
true_vals.each do |v|
switches[v]=true
end
false_vals.each do |v|
switches[v]=false
end
I've got a Ruby method like the following:
# Retrieve all fruits from basket that are of the specified kind.
def fruits_of_kind(kind)
basket.select { |f| f.fruit_type == kind.to_s }
end
Right now, you can call this like:
fruits_of_kind(:apple) # => all apples in basket
fruits_of_kind('banana') # => all bananas in basket
and so on.
How do I change the method so that it will correctly handle iterable inputs as well as no inputs and nil inputs? For example, I'd like to be able to support:
fruits_of_kind(nil) # => nil
fruits_of_kind(:apple, :banana) # => all apples and bananas in basket
fruits_of_kind([:apple, 'banana']) # => likewise
Is this possible to do idiomatically? If so, what's the best way to write methods so that they can accept zero, one, or many inputs?
You need to use the Ruby splat operator, which wraps all remaining arguments into an Array and passes them in:
def foo (a, b, *c)
#do stuff
end
foo(1, 2) # a = 1, b = 2, c = []
foo(1, 2, 3, 4, 5) #a = 1, b = 2, c = [3, 4, 5]
In your case, something like this should work:
def fruits_of_kind(*kinds)
kinds.flatten!
basket.select do |fruit|
kinds.each do |kind|
break true if fruit.fruit_type == kind.to_s
end == true #if no match is found, each returns the whole array, so == true returns false
end
end
EDIT
I changed the code to flatten kinds so that you can send in a list. This code will handle entering no kinds at all, but if you want to expressly input nil, add the line kinds = [] if kinds.nil? at the beginning.
Use the VARARGS feature of Ruby.
# Retrieve all fruits from basket that are of the specified kind.
# notice the * prefix used for method parameter
def fruits_of_kind(*kind)
kind.each do |x|
puts x
end
end
fruits_of_kind(:apple, :orange)
fruits_of_kind()
fruits_of_kind(nil)
-sasuke
def fruits_of_kind(kind)
return nil if kind.nil?
result = []
([] << kind).flatten.each{|k| result << basket.select{|f| f.fruit_type == k.to_s }}
result
end
The 'splat' operator is probably the best way to go, but there are two things to watch out for: passing in nil or lists. To modify Pesto's solution for the input/output you'd like, you should do something like this:
def fruits_of_kind(*kinds)
return nil if kinds.compact.empty?
basket.select do |fruit|
kinds.flatten.each do |kind|
break true if fruit.fruit_type == kind.to_s
end == true #if no match is found, each returns the whole array, so == true returns false
end
end
If you pass in nil, the * converts it to [nil]. If you want to return nil instead of an empty list, you have to compact it (remove nulls) to [], then return nil if it's empty.
If you pass in a list, like [:apple, 'banana'], the * converts it to [[:apple, 'banana']]. It's a subtle difference, but it's a one-element list containing another list, so you need to flatten kinds before doing the "each" loop. Flattening will convert it to [:apple, 'banana'], like you expect, and give you the results you're looking for.
EDIT: Even better, thanks to Greg Campbell:
def fruits_of_kind(basket, kind)
return nil if kind.nil?
kind_list = ([] << kind).flatten.map{|kind| kind.to_s}
basket.select{|fruit| kind_list.include?(fruit) }
end
OR (using splat)
def fruits_of_kind(*kinds)
return nil if kinds.compact.empty?
kind_list = kinds.flatten.map{|kind| kind.to_s}
basket.select{|fruit| kind_list.include?(fruit.fruit_type) }
end
There's a nicely expressive use of splat as an argument to array creation that handles your last example:
def foo(may_or_may_not_be_enumerable_arg)
arrayified = [*may_or_may_not_be_enumerable_arg]
arrayified.each do |item|
puts item
end
end
obj = "one thing"
objs = ["multiple", "things", 1, 2, 3]
foo(obj)
# one thing
# => ["one thing"]
foo(objs)
# multiple
# things
# 1
# 2
# 3
# => ["multiple", "things", 1, 2, 3]