Ruby Mongo::ObjectID comparison - ruby

SO,
I have two Mongo::ObjectID objects that are equal according to both == and eql? (they both return true). However, if one is a key in a Hash and the other is in a document stored in an array, this fails:
myhash[array_of_docs[0]['_id']] # => nil
myhash.fetch(array_of_docs[0]['_id']) # => KeyError: key not found
My db has 2 collections, "bookmarks", with mainly a title and url, and "tags", with a 'bkm_id' key pointing to a bookmark doc's _id and a 'name' key. With the following query, I map each bookmark's _id to the corresponding comma-separated list of tags:
bkms_tags_array = tags_collection.group(['bkm_id'], nil, { tags: Array.new }, "function(tag, agg){ agg.tags.push(tag.name) }", true)
bkms_tags = {}
bkms_tags_array.each do |bt|
bkms_tags.merge! Hash[bt.values[0], bt.values[1].join(", ")]
end
bkms_tags # => {4d60b29603e5665f82000001=>"socialnw, blablabla", 4d60b44703e5665fff000001=>"mail, app, google", 4d61812f03e5661ad8000001=>"socialnw, comms, web"}
Given that bks is the result of 'bookmarks_collection.find.to_a', this is my problem:
bkms_tags[bks[0]['_id']] # => nil
bkms_tags.include? bks[0]['_id'] # => false ; however:
bkms_tags.keys.include? bks[0]['_id'] # => true
How come 'hash.include?' be false and 'hash.keys.include?' be true? Is there a difference between ObjectIDs returned by different queries?
Like I said, both == and eql? return true:
bkms_tags.each { |k,v| puts k == bks[0]['_id'] } # => true false false
bkms_tags.keys.each { |k| puts k == bks[0]['_id'] } # => true false false
bkms_tags.each { |k,v| puts k.eql? bks[0]['_id'] } # => true false false
bkms_tags.keys.each { |k| puts k.eql? bks[0]['_id'] } # => true false false
So, by any comparison possible, 'bks[0]['_id']' is a key of bkms_tags, but when I try to retrieve it's value, Ruby gets confused somehow.
Any ideas? Thanks!
Extra info:
Some sample documents:
Bookmarks
{ "_id" : ObjectId("4d60b29603e5665f82000001"), "url" : "http://www.facebook.com/", "title" : "Facebook", "host" : "facebook.com", "saved_at" : "Sun Feb 20 2011 01:20:06 GMT-0500 (PET)" }
{ "_id" : ObjectId("4d60b44703e5665fff000001"), "url" : "http://mail.google.com/", "title" : "gmail", "host" : "mail.google.com", "saved_at" : "Sun Feb 20 2011 01:27:19 GMT-0500 (PET)" }
{ "_id" : ObjectId("4d61812f03e5661ad8000001"), "url" : "http://twitter.com/", "title" : "twitter", "host" : "twitter.com", "saved_at" : "Sun Feb 20 2011 16:01:35 GMT-0500 (PET)" }
Tags
{ "_id" : ObjectId("4d60b44703e5665fff000002"), "bkm_id" : ObjectId("4d60b44703e5665fff000001"), "name" : "mail" }
{ "_id" : ObjectId("4d60b44703e5665fff000003"), "bkm_id" : ObjectId("4d60b44703e5665fff000001"), "name" : "app" }
{ "_id" : ObjectId("4d60b29603e5665f82000003"), "bkm_id" : ObjectId("4d60b29603e5665f82000001"), "name" : "socialnw" }
{ "_id" : ObjectId("4d60b29603e5665f82000004"), "bkm_id" : ObjectId("4d60b29603e5665f82000001"), "name" : "blablabla" }
{ "_id" : ObjectId("4d61812f03e5661ad8000003"), "bkm_id" : ObjectId("4d61812f03e5661ad8000001"), "name" : "comms" }
{ "_id" : ObjectId("4d61812f03e5661ad8000004"), "bkm_id" : ObjectId("4d61812f03e5661ad8000001"), "name" : "web" }
EDIT
Testing some more, I came up with more irregularities:
bkms_ids # => [4d60b29603e5665f82000001, 4d61812f03e5661ad8000001, 4d61ba9103e5667dbe000001, 4d61ba9103e5667dbe000001]
bkms_ids[2] == bkms_ids[3] # => true
bkms_ids[2].eql? bkms_ids[3] # => true
bkms_ids.uniq # => nothing changes: [4d60b29603e5665f82000001, 4d61812f03e5661ad8000001, 4d61ba9103e5667dbe000001, 4d61ba9103e5667dbe000001]
EDIT 2
As requested, my bson version:
irb> BSON::VERSION # NameError: uninitialized constant BSON::VERSION
$ gem list bson
*** LOCAL GEMS ***
bson (1.2.2)
bson_ext (1.2.2)
and inspect and class of my array of docs:
array_of_docs = bookmarks_collection.find.to_a
array_of_docs[0].inspect # => "{\"_id\"=>4d60b29603e5665f82000001, \"url\"=>\"http://www.facebook.com/\", \"title\"=>\"Facebook\", \"host\"=>\"facebook.com\", \"saved_at\"=>2011-02-20 06:20:06 UTC}"
array_of_docs[0].class # => OrderedHash
array_of_docs[0]['_id'].class # => Mongo::ObjectID

Change uniq to uniq! :
bkms_ids
bkms_ids[2] == bkms_ids[3]
bkms_ids[2].eql? bkms_ids[3]
bkms_ids.uniq!

Related

How to extract a value from a hash

I have a parsed JSON file that contains a Hash:
{
"user1" : {
"about_you" : "jjhj",
"age" : 18,
"email" : 18
},
"user2" : {
"about_you" : "jjhj",
"age" : 18,
"email" : 18
},
"user3" : {
"about_you" : "jjhj",
"age" : 18,
"email" : 18
}
}
I'm trying to loop and get all the email values and write them to a CSV file.
At the moment I'm trying to read the email, and tried a few variations but this is the closest I got, but it doesn't read the value, it just shows blank.
data_hash = JSON.parse(File.read('user.json'))
data_hash.keys.each do |user|
puts user['email']
end
The keys method returns an array of the key names; it doesn't return the values.
Given these inputs:
json = '{"user1":{"about_you":"jjhj","age":18,"email":18},"user2":{"about_you":"jjhj","age":18,"email":18},"user3":{"about_you":"jjhj","age":18,"email":18}}'
data_hash = JSON.parse(json)
Try just iterating over the hash's keys and values:
data_hash.each { |k,v| puts v['email'] }
Or if you prefer:
data_hash.each do |k,v|
puts v['email']
end
Each returns:
18
18
18
If you only need the email data then you can just use map:
data_hash = JSON.parse(File.read('user.json'))
data_hash.values.map{|x| x[:email]}

Ruby Mongoid DateTime.gte/lte query not working

I have a mongoid class User with a DateTime field called last_notification_check
not all the User objects have this field, because its only added after the first time a user logs in.
I need to query for the following:
User.where(:last_notification_check.lte => (Time.now - 60).utc)
this should return a response of 1 user object, but it continuously returns nil
my mongoid object that it should be able to pick up:
{
"_id" : ObjectId("590ad06f78cee4ffcd612d5e"),
"active" : true,
"first_name" : "Test",
"last_name" : "Person",
"suffix" : null,
"email" : "test#email.com",
"encrypted_password" : "s7FThz4a68nBKx6aGJ19Bw==",
"admin" : true,
"updated_at" : ISODate("2017-05-28T21:29:30.486+0000"),
"created_at" : ISODate("2017-05-04T06:55:42.990+0000"),
"title" : "Test User",
"last_notificaton_check" : ISODate("2017-05-28T21:29:30.485+0000")
}
I get the following results.. not sure what to do...
User.where(:last_notification_check.gte => Time.now)
# nil
User.where(:last_notification_check.lte => Time.now)
# nil
User.where(:first_name => 'Test').each do |user|
puts user.last_name
end
# "Person"

Ruby - Elegantly replace hash values with nested value (description)

The hash I'm working with has a hash for it's values which always contains an ID, name, and description. I am not interested in keeping the ID or name and just want to replace every hash value with its corresponding description.
Code
hsh['nested']['entries']['addr'] = hsh['nested']['entries']['addr']['description']
hsh['nested']['entries']['port'] = hsh['nested']['entries']['port']['description']
hsh['nested']['entries']['protocol'] = hsh['nested']['entries']['protocol']['description']
hsh['nested']['entries']['type'] = hsh['nested']['entries']['type']['description']
... (many more)
This works fine, but it is not very elegant--in reality, I have 20 entries/lines of code to get the job done.
Structure of the hash value (for hsh['nested']['entries']['addr'])
{ "id" => "27", "name" => "Instance", "description" => "**This is what I need.**" }
Taking the first line of code above as a sample, the end result would be the value of hsh['nested']['entries']['addr'] becomes **This is what I need.**
What is an elegant way to achieve this?
hsh = { 'nested'=>
{ 'entries'=>
{
'addr'=>{ "id" => "1", "description"=>"addr" },
'port'=>{ "id" => "2", "description"=>"port" },
'cats'=>{ "id" => "3", "description"=>"dogs" },
'type'=>{ "id" => "4", "description"=>"type" }
}
}
}
keys_to_replace = ["addr", "port", "type"]
hsh['nested']['entries'].tap { |h| keys_to_replace.each { |k| h[k]=h[k]["description"] }
#=> { "addr"=>"addr",
# "port"=>"port",
# "cats"=>{"id"=>"3", "description"=>"dogs"},
# "type"=>"type"
# }
hsh
#=> {"nested"=>
# { "entries"=>
# { "addr"=>"addr",
# "port"=>"port",
# "cats"=>{"id"=>"3", "description"=>"dogs"},
# "type"=>"type"
# }
# }
# }
sub_hash = hsh['nested']['entries']
categories = %w{addr port protocol type}
categories.each do |category|
sub_hash[category] = sub_hash[category]['description']
end

Map keys with the same name

A GET to an API endpoint I'm working with returns json with an inconsistent order of contacts, either
{"contacts"=>[
{"id"=>$UUID_0, "name"=>nil, "email"=>$EMAIL_0, "phone"=>$PHONE_0, "type"=>"foo"},
{"id"=>$UUID_1, "name"=>nil, "email"=>$EMAIL_1, "phone"=>$PHONE_1, "type"=>"bar"}
]}
or
{"contacts"=>[
{"id"=>$UUID_1, "name"=>nil, "email"=>$EMAIL_1, "phone"=>$PHONE_1, "type"=>"bar"},
{"id"=>$UUID_0, "name"=>nil, "email"=>$EMAIL_0, "phone"=>$PHONE_0, "type"=>"foo"}
]}
The "type" values are the only static objects in these responses, so I'd like to map this so that the contact types are keys containing the other pairs:
{
"foo"=>{"id"=>$UUID_0, "name"=>$NAME_0, "email"=>$EMAIL_0, "phone"=>$PHONE_0},
"bar"=>{"id"=>$UUID_1, "name"=>$NAME_1, "email"=>$EMAIL_1, "phone"=>$PHONE_1}
}
A solution is not obvious to me.
If you use Ruby on Rails, or at least ActiveSupport, you can try index_by instead of group_by: it won't put the values into arrays.
hash['contacts'].index_by {|r| r['type']}
=>
{
"bar" => {
"id" => "asdf",
"name" => nil,
"email" => "EMAIL_1",
"phone" => "PHONE_1",
"type" => "bar"
},
"foo" => {
"id" => "asdf",
"name" => nil,
"email" => "EMAIL_0",
"phone" => "PHONE_0",
"type" => "foo"
}
}
Hash[data['contacts'].map { |c| [c['type'], c] }]
This can be done with Enumerable#reduce:
hash['contacts'].reduce({}) {|m,c| m[c['type']] = c;m}
How it works:
An empty hash is the starting point.
The block is called once for each element in the contacts list. The block receives the hash that we're building as m and the current contact as c.
In the block, assign c to the hash based on its type and return the hash so far.
Final result is the last return value of the block.

Compare three arrays of hashes and get the result without duplicates in ruby?

I m using the fql gem to retrieve the data from facebook. The original array of hashes is like this. Here. When i compare these three arrays of hashes then i want to get the final result in this way:
{
"photo" => [
[0] {
"owner" : "1105762436",
"src_big" : "https://fbcdn-sphotos-b-a.akamaihd.net/hphotos-ak-xap1/t31.0-8/q71/s720x720/10273283_10203050474118531_5420466436365792507_o.jpg",
"caption" : "Rings...!!\n\nView Full Screen.",
"created" : 1398953040,
"modified" : 1398953354,
"like_info" : {
"can_like" : true,
"like_count" : 22,
"user_likes" : true
},
"comment_info" : {
"can_comment" : true,
"comment_count" : 2,
"comment_order" : "chronological"
},
"object_id" : "10203050474118531",
"pid" : "4749213500839034982"
}
],
"comment" => [
[0] {
"text" : "Wow",
"text_tags" : [],
"time" : 1398972853,
"likes" : 1,
"fromid" : "100001012753267",
"object_id" : "10203050474118531"
},
[1] {
"text" : "Woww..",
"text_tags" : [],
"time" : 1399059923,
"likes" : 0,
"fromid" : "100003167704574",
"object_id" : "10203050474118531"
}
],
"users" =>[
[0] {
"id": "1105762436",
"name": "Nilanjan Joshi",
"username": "NilaNJan219"
},
[1] {
"id": "1105762436",
"name": "Ashish Joshi",
"username": "NilaNJan219"
}
]
}
Here is my attempt:
datas = File.read('source2.json')
all_data = JSON.parse(datas)
photos = all_data[0]['fql_result_set'].group_by{|x| x['object_id']}.to_a
comments = all_data[1]['fql_result_set'].group_by{|x| x['object_id']}.to_a
#photos_comments = []
#comments_users = []
#photo_users = []
photos.each do |a|
comments.each do |b|
if a.first == b.first
#photos_comments << {'photo' => a.last, 'comment' => b.last}
else
#comments_users << {'photo' => a.last, 'comment' => ''} unless #photos_comments.include? (a.last)
end
end
end
#photo_users = #photos_comments | #comments_users
#photo_comment_users = {photos_comments: #photo_users }
Here is what i'm getting final result
Still there are duplicates in the final array. I've grouped by the array by object id which is common between the photo and the comment array. But the problem it is only taking those photos which has comments. I'm not getting the way how to find out the photos which don't have the comments.
Also in order to find out the details of the person who has commented, ive users array and the common attribute between comments and users is fromid and id. I'm not able to understand how to get the user details also.
I think this is what you want:
photos = all_data[0]['fql_result_set']
comments = all_data[1]['fql_result_set'].group_by{|x| x['object_id']}
#photo_comment_users = photos.map do |p|
{ 'photo' => p, 'comment' => comments[p['object_id']] || '' }
end
For each photo it takes all the comments with the same object_id, or if none exist - returns ''.
If you want to connect the users too, you can map them by id, and select the relevant ones by the comment:
users = Hash[all_data[2]['fql_result_set'].map {|x| [x['id'], x]}]
#photo_comment_users = photos.map do |p|
{ 'photo' => p, 'comment' => comments[p['object_id']] || '',
'user' => (comments[p['object_id']] || []).map {|c| users[c['formid']]} }
end

Resources