MongoDB + Ruby. How to access document properties? - ruby

I want to try Mongo with Ruby. I connected, selected collection and I can query data from MongoDB.
irb(main):049:0> coll.find_one({:x=>4})
=> #<BSON::OrderedHash:0x3fdb33fdd59c {"_id"=>BSON::ObjectId('4f8ae4d7c0111ba6383cbe1b'), "x"=>4.0, "j"=>1.0}>
irb(main):048:0> coll.find_one({:x=>4}).to_a
=> [["_id", BSON::ObjectId('4f8ae4d7c0111ba6383cbe1b')], ["x", 4.0], ["j", 1.0]]
But how to access propeties, when I retrieve BSON hash? I need something like this:
data.x
=> 4
to_hash method gives me the same BSON::OrderedHash... :(

When you say coll.find_one({:x=>4}), you get a BSON::OrderedHash back that you access like a normal Hash:
h = coll.find_one(:x => 4)
puts h['x']
# 4 comes out unless you didn't find anything.
If you use a full find instead of find_one, you get a MongoDB::Cursor which is an Enumerable so you can iterate it like any other collection; the cursor will return BSON::OrderedHash instances as you iterate so you can do things like this:
cursor = coll.find(:thing => /stuff/)
cursor.each { |h| puts h['thing'] }
things = cursor.map { |h| h['thing'] }
If you wanted objects instead of Hashes then you'd have to wrap the MongoDB::Cursor and BSON::OrderedHash instances with object yourself (possibly via Struct).

Mongodb find_one method returns hash object, find method returns cursor object.
Cursor object can be iterated and then is possible to extract the answer in a normal hash.
require 'rubygems'
require 'mongo'
include Mongo
client = MongoClient.new('localhost', 27017)
db = client.db("mydb")
coll = db.collection("testCollection")
coll.insert({"name"=>"John","lastname"=>"Smith","phone"=>"12345678"})
coll.insert({"name"=>"Jane","lastname"=>"Fonda","phone"=>"87654321"})
cursor = coll.find({"phone"=>"87654321"})
answer = {}
cursor.map { |h| answer = h }
puts answer["name"]

Related

Create a Ruby Hash out of an xml string with the 'ox' gem

I am currently trying to create a hash out of an xml documen, with the help of the ox gem
Input xml:
<?xml version="1.0"?>
<expense>
<payee>starbucks</payee>
<amount>5.75</amount>
<date>2017-06-10</date>
</expense>
with the following ruby/ox code:
doc = Ox.parse(xml)
plist = doc.root.nodes
I get the following output:
=> [#<Ox::Element:0x00007f80d985a668 #value="payee", #attributes={}, #nodes=["starbucks"]>, #<Ox::Element:0x00007f80d9839198 #value="amount", #attributes={}, #nodes=["5.75"]>, #<Ox::Element:0x00007f80d9028788 #value="date", #attributes={}, #nodes=["2017-06-10"]>]
The output I want is a hash in the format:
{'payee' => 'Starbucks',
'amount' => 5.75,
'date' => '2017-06-10'}
to save in my sqllite database. How can I transform the objects array into a hash like above.
Any help is highly appreciated.
The docs suggest you can use the following:
require 'ox'
xml = %{
<top name="sample">
<middle name="second">
<bottom name="third">Rock bottom</bottom>
</middle>
</top>
}
puts Ox.load(xml, mode: :hash)
puts Ox.load(xml, mode: :hash_no_attrs)
#{:top=>[{:name=>"sample"}, {:middle=>[{:name=>"second"}, {:bottom=>[{:name=>"third"}, "Rock bottom"]}]}]}
#{:top=>{:middle=>{:bottom=>"Rock bottom"}}}
I'm not sure that's exactly what you're looking for though.
Otherwise, it really depends on the methods available on the Ox::Element instances in the array.
From the docs, it looks like there are two handy methods here: you can use [] and text.
Therefore, I'd use reduce to coerce the array into the hash format you're looking for, using something like the following:
ox_nodes = [#<Ox::Element:0x00007f80d985a668 #value="payee", #attributes={}, #nodes=["starbucks"]>, #<Ox::Element:0x00007f80d9839198 #value="amount", #attributes={}, #nodes=["5.75"]>, #<Ox::Element:0x00007f80d9028788 #value="date", #attributes={}, #nodes=["2017-06-10"]>]
ox_nodes.reduce({}) do |hash, node|
hash[node['#value']] = node.text
hash
end
I'm not sure whether node['#value'] will work, so you might need to experiment with that - otherwise perhaps node.instance_variable_get('#value') would do it.
node.text does the following, which sounds about right:
Returns the first String in the elements nodes array or nil if there is no String node.
N.B. I prefer to tidy the reduce block a little using tap, something like the following:
ox_nodes.reduce({}) do |hash, node|
hash.tap { |h| h[node['#value']] = node.text }
end
Hope that helps - let me know how you get on!
I found the answer to the question in my last comment by myself:
def create_xml(expense)
Ox.default_options=({:with_xml => false})
doc = Ox::Document.new(:version => '1.0')
expense.each do |key, value|
e = Ox::Element.new(key)
e << value
doc << e
end
Ox.dump(doc)
end
The next question would be how can i transform the value of the amount key from a string to an integer befopre saving it to the database

Getting typed results from ActiveRecord raw SQL

In Sequel, I can do:
irb(main):003:0> DB["select false"].get
=> false
Which returns a false boolean. I'd like to be able to do something similar in ActiveRecord:
irb(main):007:0> ActiveRecord::Base.connection.select_value "select false"
=> "f"
As you can see, it returns the string "f". Is there a way to get a false boolean with ActiveRecord? (Similarly, I might be calling a function that returns a timestamptz, an array, etc -- I'd want the returned value to have the correct type)
My use case: I'm calling a database function, want to get back a typed result instead of a string.
While I have no doubt that Björn Nilsson's answer worked when he posted it, it is failing for me with Postgres 9.4 and PG gem version 0.18.2. I have found the following to work after looking through the PG gem documentation:
pg = ActiveRecord::Base.connection
#type_map ||= PG::BasicTypeMapForResults.new(pg.raw_connection)
res = pg.execute("SELECT 'abc'::TEXT AS a, 123::INTEGER AS b, 1.23::FLOAT;")
res.type_map = #type_map
res[0]
# => {"a"=>"abc", "b"=>123, "float8"=>1.23}
Pretty ugly but does what you are asking for:
res = ActiveRecord::Base.connection.
select_all("select 1 as aa, false as aas, 123::varchar, Array[1,2] as xx")
# Breaks unless returned values have unique column names
res.map{|row|row.map{|col,val|res.column_types[col].type_cast val}}
# Don't care about no column names
res.map{|row|
row.values.map.with_index{|val,idx|
res.column_types.values[idx].type_cast val
}
}
gives:
[[1, false, "123", [1, 2]]]
How it works:
res.column_types
returns a hash of columns names and Postgresql column types
Here is a pointer to how it works:
https://github.com/rails/docrails/blob/fb8ac4f7b8487e4bb5c241dc0ba74da30f21ce9f/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb
Don't have enough reputation points to respond, but Bjorn's answer and associated replies are broken in Rails 5. This works:
res = ActiveRecord::Base.connection.select_all(sql)
res.to_a.map{|o| o.each{|k, v| o[k] = res.column_types[k].cast v}}
I don't know if it is the way, but you can create activerecord model without table with sort of fake column:
class FunctionValue < ActiveRecord::Base
def self.columns
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s,
default,
sql_type.to_s,
null
)
end
column :value, :boolean
end
And then you can run this:
function_value = FunctionValue.find_by_sql('select false as value').first
function_value.value
This works for me in rails 5
results = ActiveRecord::Base.connection.select_all(sql)
results.rows.map{ |row| Hash[results.columns.zip(row)] }
Gives nice results
[{"person1"=>563, "person2"=>564, "count"=>1},
{"person1"=>563, "person2"=>566, "count"=>5},
{"person1"=>565, "person2"=>566, "count"=>1}]
In Rails 6, Person.connection.select_all(sql_query).to_a
...will return an array of hashes whose values are type-casted. Example:
[{"id"=>12, "name"=>"John Doe", "vip_client"=>false, "foo"=> nil, "created_at"=>2018-01-24 23:55:58 UTC}]
If you prefer an OpenStruct, use Mike's suggestion:
Person.connection.select_all(sql_query).to_a.map {|r| OpenStruct.new(r) }
If you prefer symbols as keys, call map(&:symbolize_keys) after to_a.

Simple JSON not working in Ruby

Code:
#!/usr/bin/ruby
require 'rubygems'
require 'open-uri'
require 'json'
def getData
file = open("http://goo.gl/BI6h7a")
#json = JSON.parse(file.read)
end
getData
cveIds = #json['cve_id']
puts cveIds
You can see the JSON response here: http://goo.gl/BI6h7a
Console:
./cve.rb:13:in `[]': can't convert String into Integer (TypeError) from ./cve.rb:13:in `<main>'
I don't know why this is happening. "Convert String into Integer"? WHAT?
The #json gets the content fine, but the cveIds doesn't.
The top element in the json that you're reading is actually an Array, each of its elements is actually a hash, it's like this:
[
{
"cve_id": "CVE-2014-3976"
// other key/value pairs
}
{
"cve_id": "CVE-2014-3975"
// other key/value pairs
}
{
"cve_id": "CVE-2014-3974"
// other key/value pairs
}
// .... more hashes
]
so #json is an array. And if you want to access any of its elements you have to access it with a numeric integer index like, so:
#json[0] # => { "cve_id": "CVE-2014-3976", // other key/value pairs }
I think you are trying to collect the cve_id fields of all these hashes, this can be done as follows:
cveIds = #json.collect { |h| h["cve_id"] }
# The result:
=> ["CVE-2014-3976", "CVE-2014-3975", "CVE-2014-3974", "CVE-2014-3962", "CVE-2014-3961",
"CVE-2014-3878", "CVE-2014-3871", "CVE-2014-3842", "CVE-2014-3806", "CVE-2014-3792",
"CVE-2014-3791", "CVE-2014-3443", "CVE-2014-3247", "CVE-2014-3246", "CVE-2014-3225",
"CVE-2014-3216", "CVE-2014-3139", "CVE-2014-3138", "CVE-2014-3008", "CVE-2014-2996",
"CVE-2014-2994", "CVE-2014-2976", "CVE-2014-2850", "CVE-2014-2847", "CVE-2014-2671",
"CVE-2014-2668", "CVE-2014-2588", "CVE-2014-2587","CVE-2014-2586", "CVE-2014-2579"]
I'm not a ruby developer but what you have there is a list if dictionaries.
My guess in order for you to read cve_id you need to create some kind of a for loop.
for example in python I would write it like this:
for line in my_data:
print line['cve_id']
I guess in ruby it would look like this:
for i in #json do
cveIds = i['cve_id']
puts cveIds
end
cveIds = #json['cve_id']
What are you doing here is equivalent to:
arr = [1, 2, 3, 4]
puts arr["hello"] # using a string here on an indexed based array!
Hence your error message about Ruby trying to convert a String to an int.
Try the following instead
cveIds = #json.first['cve_id'] # equivalent to #json[0]['cve_id']
puts cveIds
In the above code sample, we are getting the first element from the array, which is a hash we can then access cve_id from.

How to use valid? method on an array in rails 3

I am working on an app in rails 3.
I have several records that i want to save to my database. I am trying to make sure that all the objects in an array (the records are stored in an array) are valid before saving. The Owner model validates the presence of name and email. In the rails console, I have tried the following:
#owner = Array.new
=> []
#owner[0] = Owner.new (name:"peter", email:"peter#gmail.com")
=> returns object
#owner[1] = Owner.new (name:"fred", email:"fred#gmail.com")
=> returns object
#owner[2] = Owner.new (name:"", email:"")
=> returns object
#owner[0].valid?
=> true
#owner[1].valid?
=> true
#owner[2].valid?
=> false
#owner.each { |t| t.valid? }
=> returns an array like this: [object1, object2, object3]. I would expect something like this instead: [true,true,false]
I dont understand why the .valid? method works fine if I individually check the elements of the array using #owner[i], but doesnt work correctly if I'm using .each to iterate through the array. Anybody know what might be the problem?
What I am trying to do is achieve something like this:
(#owner.each { |t| t.valid? }).all?
To make sure that each record is valid, then I can proceed to save them.
Thanks
Each does not return an array of valid? values. You probably want either:
(#owner.collect { |t| t.valid? }).all?
or
(#owner.all? { |t| t.valid? })
The examples can also be written as:
#owner.collect(&:valid?).all?
or
#owner.all?(&:valid?)

How to construct the 2d structure in a dynamic fashion

I iterate through all cars and its supported attributes (many attributes per car) to create a structure like this, how do I do this in a dynamic fashion.
cars = {
"honda" => {'color' => 'blue', 'type' => 'sedan'}.
"nissan" => {'color' => 'yellow', 'type' => 'sports'}.
...
}
cars.each do |car|
car_attrs = ...
car_attrs.each do |attr|
??? How to construct the above structure
end
end
Your question is not very clear... But i guess this is what you want:
cars = {}
options = {}
options['color'] = 'blue'
...
cars['honda'] = options
Is that what you were looking for?
It sounds like you may be asking for a way to create a 2-dimensional hash without having to explicitly create each child hash. One way to accomplish that is by specifying the default object created for a hash key.
# When we create the cars hash, we tell it to create a new Hash
# for undefined keys
cars = Hash.new { |hash, key| hash[key] = Hash.new }
# We can then assign values two-levels deep as follows
cars["honda"]["color"] = "blue"
cars["honda"]["type"] = "sedan"
cars["nissan"]["color"] = "yellow"
cars["nissan"]["type"] = "sports"
# But be careful not to check for nil using the [] operator
# because a default hash is now created when using it
puts "Found a Toyota" if cars["toyota"]
# The correct way to check would be
puts "Really found a Toyota" if cars.has_key? "toyota"
Many client libraries assume that the [] operator returns a nil default, so make sure other code doesn't depend on that behavior before using this solution. Good luck!
Assuming you are using something similar to ActiveRecord (but easy to modify if you are not):
cars_info = Hash[cars.map { |car| [car.name, car.attributes] }

Resources