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

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.

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

Getting "TypeError: no implicit conversion of Regexp into Integer" when trying to extract number from string

I'm using Rails 5. I'm having an issue extracting the numeric portion of a field and inserting it into my model attribute. I have
puts "age:#{age}"
self.age = age[/\d+/].present? ? age[/\d+/].to_i : nil
But I'm getting the output
age:41
TypeError: no implicit conversion of Regexp into Integer
from /Users/davea/Documents/workspace/myproject/app/models/my_object_time.rb:129:in `[]'
from /Users/davea/Documents/workspace/myproject/app/models/my_object_time.rb:129:in `set_age'
from /Users/davea/Documents/workspace/myproject/app/models/my_object_time.rb:51:in `block in <class:RaceTime>'
Is there a better way to write the above to extract the number (if ther eis one) but also avoid the error I'm seeing?
The error message is telling you that the [] you are calling expects an Integer as an argument, and thus it tries to convert the Regexp to an Integer (by calling to_int) but fails because Regexps don't respond to to_int.
Since String#[] does take a Regexp as an argument, it's obvious that the object referenced by age cannot be a String, it must be something else. Unfortunately, you don't tell us what object it is (you really should include such information in your question!) but we can make an educated guess: there are only three [] methods in Ruby I can think of, which would conceivably raise this exact error: Array#[], MatchData#[], and Integer#[]. So, my best guess at this point is that your age object is actually not a String, but rather a MatchData object, an Array, or an Integer.
But, we can do better: you included the result of to_s in your question. Array#to_s returns something like '[1, 2, 3]', but your question shows that age.to_s returns something like '41'. So, it can't be an Array.
Ergo, age is either a MatchData object or an Integer, but you are expecting it to be a String. You need to figure out where age is coming from, and why it isn't what you expect it to be, then fix that.
Your age variable is probably a number. I get the same error when I try to use the [/regex/] method with a number:
[5] pry(main)> 1[//]
TypeError: no implicit conversion of Regexp into Integer
You could say age = age.to_s first
I think you should better write that like:
str = "sometext 123 sometext"
self.age = /(?<age>\d+)/ =~ str.to_s ? age.to_i : nil

How to avoid "undefined method `[]' for nil:NilClass" when working with nested Ruby hashes?

I'm working with the Steam Storefront API - http://store.steampowered.com/api/appdetails/?appids=240
I've parsed the JSON into a hash.
When I try to select any hash value nested inside of "data" I receive an "undefined method `[]' for nil:NilClass" error.
I can puts the whole lot with res["240"]["data"] which shows me all of the keys and values. All of which seem to look fine.
However when I try to go one branch further it throws nil.
res["240"]["data"]["type"]
Using .key also throws up an error.
res["240"]["data"].key
My quest to find an answer has mainly found suggestions of searching for the key & values, however I know the direct route to the data so I'd like to go this route if possible.
Thanks.
If you're on ruby 2.3, you may use dig, as suggested by #sawa.
http://docs.ruby-lang.org/en/2.3.0/Hash.html#method-i-dig
However, if you are not on ruby 2.3, then things get a bit trickier.
The simplest approach is to implement your own version of dig:
class Hash
def dig(*path)
path.inject(self) do |h, k|
h.respond_to?(:keys) ? h[k] : nil
end
end
end
Then you can just res.dig("240", "data", "type")
You can use dig in combination with the safe navigation operator, like:
res&.dig("240", "data", "type")
If res is nil (or more accurately isn't "digable") then it will return nil instead of raising.
Use dig.
res.dig("240", "data", "type")

How to handle a select that comes up empty?

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.

Table column names to array in ruby

In Rails I would simply use:
MyModel.column_names
to access the tables column names, but I am enduring my first fumblings with Sinatra (in order to better learn Ruby) and need to get the column names of a table into an array.
Previously, I have iterated over the params hash to extract key, value pairs:
params.each do |key, value|
#manipulate keys and values
end
and know that using:
#mm = MyModel.new
#mm.each do ....
leads to an undefined method 'each' error.
logger.info #mm.inspect
logs something like:
#<MyModel #id=nil #start=nil #end=nil #yada=nil #created_at=nil #updated_at=nil #foreign_id=nil>
So, how do I get those blasted column names I can see so clearly into an array?
Thanks...
UPDATE
Thanks to Doon for pointing me in the right direction with Datamapper properties.
I ended up making a private method column_names:
def self.column_names
self.properties.collect {|column| column.name.to_s }
end
which works a treat
Are you looking for properties ?
http://rubydoc.info/gems/dm-core/1.1.0/DataMapper/Model/Property
For any Ruby object you have the methods instance_variables that returns the array of that object’s attributes. Is that too generic for you? I’m not familiar with DataMapper, there may be a specific method to do the equivalent of column_names in ActiveRecord.

Resources