How to handle a select that comes up empty? - ruby

How would I handle returning 0 if select comes up empty when searching an array of hashes?
I have the following following line that looks for the created key within an array of hashes:
array.select{|key| key[:created] == Date.yesterday }.first[:amount]
Then, it returns the amount key when it finds the record.
The problem is, if it can't find a record where key[:created] == Date.yesterday then I get this error:
undefined method '[]' for nil:NilClass
Here's what the array of hashes looks like:
[
{:created=>"2014-01-20", :amount=>123},
{:created=>"2014-01-21", :amount=>456},
{:created=>"2014-01-22", :amount=>789}
]

You could use find instead of select, find returns the first object in the enumerable that satisfies the block:
found = array.find { |hash| hash[:created] == Date.yesterday }
amount = found ? found[:amount] : DEFAULT_VALUE
If you are using Rails you could use the try method:
array.find { |hash| ... }.try(:[], :amount)
Update: note that the comparison between a String and a Date is doomed to fail, you have to convert the date to a properly formatted string or vice versa. In my example I've assumed that hash[:created] is a Date object.

The idiom I use is
res = array.select{|key| key[:created] == Date.yesterday }
res.blank? ? nil : res.first[:amount]
Sure, it's not a pretty one liner, but it defends against nil. blank? is I think part of activesupport, but you could roll your own easily.

Related

Generic ruby method implementation for fetching data from a hash of Nosql data

How do get a specific value from a hash of hash using a custom ruby implementation
I have a nosql data that comes out in this particular format:
{:bookname=>"The Fight for Guadalcanal",
:sourceSystemId=>"d4ba4799-atil45-4a",
:checkouttimestamp=>"2018-12-12T04:38:34.476796700Z",:firedevents=>[{:operation=>"GET", :entity=>"warbooks", :keys=>[{:name=>"book_guid", :value=>{:FieldString=>"e33almmatter-syslibrary"}}],
:attributes=>[{:libLocation=>"a44364", :value=>{:FieldInteger=>3994}}, {:name=>"big_response", :value=>{:FieldString=>"The Battle for Enderson Field, also"}}],
:customizable=>true}]}
Is there in method in ruby providing the key as a argument parameter?
I know there is fetch method in ruby that gets me the value:
test.fetch(:firedevents,()).fetch(:operation))
this does get me the value GET
but the problem I have the number of arrays or hashes in the data-set that may vary upon each operation so I am in lookout for a method that gives me a value on passing key as argument.
e.g.: ruby_mthod(sourceSystemId.to_sym) should get me d4ba4799-atil45-4a
Fetch will be enough to get sourceSystemId (assuming your data is in h):
> h.fetch(:sourceSystemId)
# => "d4ba4799-atil45-4a"
If you're looking for something more generic, take a look at Hash#dig. It lets you chain hash access in a neat way:
> h.fetch(:firedevents).first.fetch(:keys).first.dig(:value, :FieldString)
# => "e33almmatter-syslibrary"
EDIT:
As #Stefan suggested, #dig can be used for the whole operation:
> h.dig(:firedevents, 0, :keys, 0, :value, :FieldString)
# => "e33almmatter-syslibrary"
You can go recursively through the hash as:
def deep_find(key, object=self, found=nil)
if object.respond_to?(:key?) && object.key?(key)
return object[key]
elsif object.is_a? Enumerable
object.find { |*a| found = deep_find(key, a.last) }
return found
end
end
or you can use deep_find from hashie gem

Array fetch or default function

I need the following logic. If array contains value, return it else return some default value. So far, I've found this can be achieved by using delete:
array.delete(value) || default_value
But, obviously, it modifies the array. Is there a similar function like fetch with default which takes an element instead of an index?
PS. I know this can be rewritten as array.include?(value) ? value : default_value, the question is just for the sake of cleaner code.
Update: There's one way I can think of, but that's harder to read than the ternary:
(array & [value]).first || default_value
You could monkey-patch that into Array if you are so inclined:
class Array
def fetch_value(value, default)
(self & [value]).first || default
end
end
a = *1..3
a.fetch_value(4, "foo")
#=> "foo"
Old answer, before I realized you want to use the value for the lookup, not the index:
Array#fetch takes an optional second argument:
array.fetch(index, default)
Here's an example:
a = *1..3
a.fetch(4, "foo")
#=> "foo"

Ruby array methods for locating based on an object's attribute?

Say I have a Ruby class, Flight. Flight has an attr_accessor :key on it. If there's an array of instances of this class: flights = [flight1, flight2, flight3], I have a "target key", say "2jf345", and I want to find a flight based on it's key, from that array - what sort of code should I use?
This is the code I was going to use:
flights[flights.map { |s| s.key }.index(target_key)]
But it seems like with Ruby, there should be a simpler way. Also, the code above returns an error for me - `[]': no implicit conversion from nil to integer (TypeError). I assume this means that it's not returning an index at all.
Thanks for any help.
You can just use find to get the Flight object, instead of trying to use index to get the index:
flights.find {|s| s.key == target_key }
However your error message suggests that index(target_key) returns nil, which means that you don't actually have a flight with the key you're looking for, which means that find will return nil as well.

Ruby arrays and non-incremental indexes

I have an array in ruby, and I am setting the index to id of object like below.
My first question is:
This code works:
#array = Array.new(#objects.size)
for i in 0...#objects.size
#array[i] = #objects[i].value
end
but when I do:
#array[#objects[i].id] = #objects[i].value
it says:
undefined method [] for nil::NilClass
I tried putting 100 or 1000 instead of i to make sure it's not about "index out of range", but those worked, I tried converting id to int by using to_i even though it should already be an int, but it still doesn't work. I don't get it.
My second question is:
If I get to make the ids work, does saying Array.new(#objects.size) become usless?
I am not using indexes 0 to size but IDs, so what is happening? Is it initializing indexes 0...size to nil or is it just creating a space for up to x objects?
EDIT:
So I've been told it is better to use Hash for this, and I agree, But I still seem to have the same error in the same situation (just changed Array.new(#objects.size)toHash.new)
Thats not how Arrays work in Ruby. You can however use a hash to do this, and look them up using the method you want:
#lookup_hash = Hash.new
for i in 0...#objects.size
#lookup_hash[#objects[i].id] = #objects[i].value
end
Now you can do:
#lookup_hash[#some_object.id]
And it will return that object's value as you have stored it.
Additional Info
You could also rewrite your loop like this, since you dont need the index anymore:
#lookup_hash = Hash.new
#objects.each do |obj|
#lookup_hash[obj.id] = obj.value
end
A little bit more readable in my opinion.
Your're trying to use an array like a hash. Try this:
Hash[#objects.map{|o| [o.id, o.value] }]
Take a look at the Array and Hash documentations.
#array = #objects.map { |obj| obj.value }
You can, but you don't need to specify the size when creating an array. Anyway, try to use the functional capabilities of Ruby (map, select, inject) instead of C-like imperative loops.
You could use map to do this in a rubyish way:
#array = #objects.map { |o| o.value }

How to convert a ruby integer into a symbol

I have a Ruby array like this
q_id = [1,2,3,4,5,...,100]
I want to iterate through the array and convert into a hash like this
{
:1 => { #some hash} ,
:2 => { #another hash},
...
:100 => {#yet another hash}
}
What is the shortest and most elegant way to accomplish this?
[EDIT : the to_s.to_sym while being handy is not how I want it. Apologies for not mentioning it earlier.]
For creating a symbol, either of these work:
42.to_s.to_sym
:"#{42}"
The #inspect representation of these shows :"42" only because :42 is not a valid Symbol literal. Rest assured that the double-quotes are not part of the symbol itself.
To create a hash, there is no reason to convert the keys to symbols, however. You should simply do this:
q_id = (1..100).to_a
my_hash_indexed_by_value = {}
q_id.each{ |val| my_hash_indexed_by_value[val] = {} }
Or this:
my_hash = Hash[ *q_id.map{ |v| [v,{}] }.flatten ]
Or this:
# Every time a previously-absent key is indexed, assign and return a new hash
my_hash = Hash.new{ |h,val| h[val] = {} }
With all of these you can then index your hash directly with an integer and get a unique hash back, e.g.
my_hash[42][:foo] = "bar"
Unlike JavaScript, where every key to an object must be a string, Hashes in Ruby accept any object as the key.
To translate an integer into a symbol, use to_s.to_sym .. e.g.,:
1.to_s.to_sym
Note that a symbol is more related to a string than an integer. It may not be as useful for things like sorting anymore.
Actually "symbol numbers" aren't a thing in Ruby (try to call the to_sym method on a number). The benefit of using symbols in a hash is about performance, since they always have the same object_id (try to call object_id on strings, booleans, numbers, and symbols).
Numbers are immediate value and, like Symbol objects, they always have the same object_id.
Anyway, using the new hash syntax implies using symbols as keys, but you can always use the old good "hash rocket" syntax
awesome_hash = { 1 => "hello", 2 => "my friend" }
Read about immediate values here:
https://books.google.de/books?id=jcUbTcr5XWwC&pg=PA73&lpg=PA73&dq=immediate+values+singleton+method&source=bl&ots=fIFlAe8xjy&sig=j7WgTA1Cft0WrHwq40YdTA50wk0&hl=en&sa=X&ei=0kHSUKCVB-bW0gHRxoHQAg&redir_esc=y#v=onepage&q&f=false
If you are creating a hard-coded constant numeric symbol, there's a simpler way:
:'99'
This produces the same results as the more complex methods in other answers:
irb(main):001:0> :'99'
=> :"99"
irb(main):002:0> :"#{99}"
=> :"99"
irb(main):003:0> 99.to_s.to_sym
=> :"99"
Of course, this will not work if you're dynamically creating a symbol from a variable, in which case one of the other two approaches is required.
As already stated, :1 is not a valid symbol. Here's one way to do what you're wanting, but with the keys as strings:
Hash[a.collect{|n| [n.to_s, {}] }]
An array of the objects you want in your hash would be so much easier to use, wouldn't it? Even a hash of integers would work pretty well, wouldn't it?
u can use
1.to_s.to_sym
but this will make symbols like :"1"
You can make symbolic keys with Hash[]:
a = Hash[(1..100).map{ |x| ["#{x}".to_sym, {}] }]
Check type of hash keys:
puts a.keys.map(&:class)
=>
Symbol
...
Symbol
Symbol

Resources