how to iterate through json result in ruby on rails? - ruby-on-rails

I have a function in ruby on rails, where I make a call and get the following json data. now i want to add another “key” : "value" to each item in the json data and return the new or modified json object from the function. what is the best way to do this in ruby?
def get_results
results = getJsonData();
end
[
{ "name" : "Harry Potter", "rating" : 1, },
{ "name" : "Lord of the rings", "rating" : 2, },
{ "name" : "game of thrones", "rating" : 3, },
]

You can use map to transform every element of a collection
def get_results
results = getJsonData();
results.map { |item| item.merge("year" => 2000) }
end
In this case I'm adding a key "year" to your hashes with value 2000, it depends what you want to do

Related

Updating an array field that contains hashes in mongoid

I have a document in XYZ collection as follows.
"_id" : ObjectId("55311e4487216d7063040000"),
"colours" : [
{
"value" : 1,
"colour" : "red"
},
{
"value" : 2,
"colour" : "green"
}
]
I need to update the name of the colour which value is 1. What query should I write?
I am using rails 4.1.2, mongoid 4.0.0.
Please help.
For example you want to change it to "yellow" :
XYZ.where(_id: "55311e4487216d7063040000").elem_match(colours: { value: 1 }).update("$set" => {"colours.$.colour" => "yellow"})

MongoID : match multiple elements in array and update all

I use MongoID and PostgreSQL on Rails 4 and I have a notification system.
Each user has one notification document. I would like to be able to mark all as read in a controller.
Here is my notification schema :
{
"_id" : ObjectId("53e360156d6163513e000000"),
"unread_counter" : 1,
"user_id" : 1,
"rows" : [
{
"_id" : ObjectId("53e369166d61635328000000"),
"readed" : false,
"created_at" : ISODate("2014-08-07T11:55:02.936Z"),
"author" : {
"_id" : ObjectId("53e369166d61635328010000"),
"username" : "edouard",
"user_id" : 6
},
"subject" : {
"_id" : ObjectId("53e369166d61635328020000"),
"type" : "follower"
}
}
]
}
The problem is that elem_match just match one element in rows array.
Notification.where(:user_id => current_user.id)
.elem_match(rows: { readed: false })
.update_all("$set" => {"rows.$.readed" => true })
I have been inspired by this post : mongoid update elements within array
How can I match all "readed" : false row elements and then update them to true ?

Ruby on Rails: Concatenate results of Mongoid criterias and paging

I'm pretty sure that I'm doing something wrong. Consider the following code:
criteria1 = Model.where(...)
criteria2 = Model.where(...)
results = (criteria1.to_a + criteria2.to_a)[offset..(offset + count_per_page - 1)]
This code concatenates results of two different criterias and get a certain number of results with a given offset (paging).
The problem in this code is implicit. The to_a method call actually loads all results of a criteria to the memory as an array.
Now consider a really huge collection... The to_a call slows all things down dramatically.
What I wish to do is something like this:
criteria1 = Model.where(...)
criteria2 = Model.where(...)
# A criteria, which returns results of the first criteria concatenated with results of the second criteria
criteria = criteria1 + criteria2
results = criteria.offset(offset).limit(count_per_page)
The important thing is that results of the second criteria goes after results of the first criteria.
Any clues how is it possible to achieve with Mongoid?
Thanks!
UPDATE
Gergo Erdosi suggested to use merge method. I've tried to use this and it is not what I'm looking for. The problem here is the following:
criteria1 = Model.where(:name => "John", :age => "23")
criteria2 = Model.where(:name => "Bob", :gender => "male")
criteria = criteria1.merge(criteria2)
p criteria.selector
# prints: { "name" => "Bob", :age => 23, :gender => "male" }
So here are two problems:
merge doesn't produce OR, it overrides common keys of the first query with the second;
Even if we use Model.or({ :name => "John" }, { :name => "Bob" }) or Model.in(:name => ["John", "Bob"]) results won't have the right order. I wish results of the first criteria go first and then results of the second criteria go after.
It is possible that I don't understand something and Gergo's answer is right. Do you have any other ideas? Thanks.
UPDATE 2
Thank you Gergo for helping me out here. Let's try a simple example in Mongo shell:
// Fill out test db with some simple documents.
for (var i = 0; i < 10; ++i) { db.users.insert({ name: i % 2 ? "John" : "Bob", age: Math.round(Math.random() * 100) }); }
// These queries give me the same order of documents.
db.users.find({ name: { $in: ["Bob", "John"] } });
db.users.find({ $or: [{ name: "Bob" }, { name: "John" }] });
// Like this:
{ "_id" : ObjectId("53732076b110ab9be7619a8e"), "name" : "Bob", "age" : 69 }
{ "_id" : ObjectId("53732076b110ab9be7619a8f"), "name" : "John", "age" : 63 }
{ "_id" : ObjectId("53732076b110ab9be7619a90"), "name" : "Bob", "age" : 25 }
{ "_id" : ObjectId("53732076b110ab9be7619a91"), "name" : "John", "age" : 72 }
// ...
// But I wish to get concatenated results of these queries:
db.users.find({ name: "Bob" });
db.users.find({ name: "John" });
// Like this (results of the first criteria go first):
{ "_id" : ObjectId("53732076b110ab9be7619a8e"), "name" : "Bob", "age" : 69 }
{ "_id" : ObjectId("53732076b110ab9be7619a90"), "name" : "Bob", "age" : 25 }
// ...
{ "_id" : ObjectId("53732076b110ab9be7619a8f"), "name" : "John", "age" : 63 }
{ "_id" : ObjectId("53732076b110ab9be7619a91"), "name" : "John", "age" : 72 }
// ...
Notice, that I cannot use a simple sorting here, because the data in the real application is more complex. In the real application criterias look like these:
// query variable is a string
exact_match_results = Model.where(:name => query)
inexact_match_results = Model.where(:name => /#{query}/i)
So we cannot just sort alphabetically here.
Use the merge method:
criteria = criteria1.merge(criteria2)
results = criteria.offset(offset).limit(count_per_page)
You can see the details in the method description.
Edit: As pointed out, merge doesn't produce an OR query.
irb(main):010:0> Model.where(name: 'John').merge(Model.where(name: 'Bob'))
=> #<Mongoid::Criteria
selector: {"name"=>"Bob"}
options: {}
class: Model
embedded: false>
Which is not the expected behavior in this case. The reason is that merge uses Hash.merge which behaves this way. The relevant code from Criteria.merge:
selector.merge!(criteria.selector)
This can be illustrated as:
irb(main):011:0> {name: 'John'}.merge({name: 'Bob'})
=> {:name=>"Bob"}
Because of this, it's not easy to give a general advice on how to merge two criteria in a way that the result is an OR query. But with a little change in the criteria, it's possible. For example:
criteria1 = Model.any_of(name: 'John').where(age: '23')
criteria2 = Model.any_of(name: 'Bob').where(gender: 'male')
The result of the merge is an OR query which contains both names:
irb(main):014:0> criteria1.merge(criteria2)
=> #<Mongoid::Criteria
selector: {"$or"=>[{"name"=>"John"}, {"name"=>"Bob"}], "age"=>"23", "gender"=>"male"}
options: {}
class: Model
embedded: false>

Tire gem: How to access Elasticsearch's 'highlight' property?

I have some Rails models that are indexed in Elasticsearch (via Tire gem). I can index new documents and query the existing index.
What I can't seem to do is get ahold of the highlight attached to a record from within my Rails app. I can however see that highlight is returned in the json when I interact with Elasticsearch directly via curl.
When I try to access the highlight property of my record I get: undefined method 'highlight' for #<Report:0x007fe8afa54700>
# app/views/reports/index.html.haml
%h1 Listing reports
...
- #reports.results.each do |report|
%tr
%td= report.title
%td= raw report.highlight.attachment.first.to_s
But if I use curl I can see the highlight is returned to Tire...
$ curl -X GET "http://localhost:9200/testapp_development_reports/report/_search?load=true&pretty=true" -d '{query":{"query_string":{"query":"contains","default_operator":"AND"}},"highlight":{"fields":{"attachment":{}}}}'
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 0.111475274,
"hits" : [ {
"_index" : "testapp_development_reports",
"_type" : "report",
"_id" : "1",
"_score" : 0.111475274, "_source" : {"id":1,"title":"Sample Number One",...,"attachment":"JVBERi0xMJ1Ci... ...UlRU9GCg==\n"},
"highlight" : {
"attachment" : [ "\nThis <em>contains</em> one\n\nodd\n\n\n" ]
}
}, {
"_index" : "testapp_development_reports",
"_type" : "report",
"_id" : "2",
"_score" : 0.111475274, "_source" : {"id":2,"title":"Number two",...,"attachment":"JVBERi0xLKM3OA... ...olJVPRgo=\n"},
"highlight" : {
"attachment" : [ "\nThis <em>contains</em> two\n\neven\n\n\n" ]
}
} ]
}
}
The search method in the model:
...
def self.search(params)
tire.search(load: true) do
query { string params[:query], default_operator: "AND" } if params[:query].present?
highlight :attachment
end
end
...
Method highlight is inaccessible when you are using load: true option. This should be fixed in future versions of Tire.
edit: you can use each_with_hit method to access returned elasticsearch values now
For example:
results = Article.search 'One', :load => true
results.each_with_hit do |result, hit|
puts "#{result.title} (score: #{hit['_score']})"
end
You can find my answer right at this post
Elasticsearch/Lucene highlight
My method works fine for me and wish you can get it work as well.

How to join query in mongodb?

I have user document collection like this:
User {
id:"001"
name:"John",
age:30,
friends:["userId1","userId2","userId3"....]
}
A user has many friends, I have the following query in SQL:
select * from user where in (select friends from user where id=?) order by age
I would like to have something similar in MongoDB.
To have everything with just one query using the $lookup feature of the aggregation framework, try this :
db.User.aggregate(
[
// First step is to extract the "friends" field to work with the values
{
$unwind: "$friends"
},
// Lookup all the linked friends from the User collection
{
$lookup:
{
from: "User",
localField: "friends",
foreignField: "_id",
as: "friendsData"
}
},
// Sort the results by age
{
$sort: { 'friendsData.age': 1 }
},
// Get the results into a single array
{
$unwind: "$friendsData"
},
// Group the friends by user id
{
$group:
{
_id: "$_id",
friends: { $push: "$friends" },
friendsData: { $push: "$friendsData" }
}
}
]
)
Let's say the content of your User collection is the following:
{
"_id" : ObjectId("573b09e6322304d5e7c6256e"),
"name" : "John",
"age" : 30,
"friends" : [
"userId1",
"userId2",
"userId3"
]
}
{ "_id" : "userId1", "name" : "Derek", "age" : 34 }
{ "_id" : "userId2", "name" : "Homer", "age" : 44 }
{ "_id" : "userId3", "name" : "Bobby", "age" : 12 }
The result of the query will be:
{
"_id" : ObjectId("573b09e6322304d5e7c6256e"),
"friends" : [
"userId3",
"userId1",
"userId2"
],
"friendsData" : [
{
"_id" : "userId3",
"name" : "Bobby",
"age" : 12
},
{
"_id" : "userId1",
"name" : "Derek",
"age" : 34
},
{
"_id" : "userId2",
"name" : "Homer",
"age" : 44
}
]
}
Edit: this answer only applies to versions of MongoDb prior to v3.2.
You can't do what you want in just one query. You would have to first retrieve the list of friend user ids, then pass those ids to the second query to retrieve the documents and sort them by age.
var user = db.user.findOne({"id" : "001"}, {"friends": 1})
db.user.find( {"id" : {$in : user.friends }}).sort("age" : 1);
https://docs.mongodb.org/manual/reference/operator/aggregation/lookup/
This is the doc for join query in mongodb , this is new feature from version 3.2.
So this will be helpful.
You can use in Moongoose JS .populate() and { populate : { path : 'field' } }.
Example:
Models:
mongoose.model('users', new Schema({
name:String,
status: true,
friends: [{type: Schema.Types.ObjectId, ref:'users'}],
posts: [{type: Schema.Types.ObjectId, ref:'posts'}],
}));
mongoose.model('posts', new Schema({
description: String,
comments: [{type: Schema.Types.ObjectId, ref:'comments'}],
}));
mongoose.model('comments', new Schema({
comment:String,
status: true
}));
If you want to see your friends' posts, you can use this.
Users.find(). //Collection 1
populate({path:'friends', //Collection 2
populate:{path:'posts' //Collection 3
}})
.exec();
If you want to see your friends' posts and also bring all the comments, you can use this and too, you can indentify the collection if this not find and the query is wrong.
Users.find(). //Collection 1
populate({path:'friends', //Collection 2
populate:{path:'posts', //Collection 3
populate:{path:'commets, model:Collection'//Collection 4 and more
}}})
.exec();
And to finish, if you want get only some fields of some Collection, you can use the propiertie select Example:
Users.find().
populate({path:'friends', select:'name status friends'
populate:{path:'comments'
}})
.exec();
MongoDB doesn't have joins, but in your case you can do:
db.coll.find({friends: userId}).sort({age: -1})
one kind of join a query in mongoDB, is ask at one collection for id that match , put ids in a list (idlist) , and do find using on other (or same) collection with $in : idlist
u = db.friends.find({"friends": ? }).toArray()
idlist= []
u.forEach(function(myDoc) { idlist.push(myDoc.id ); } )
db.friends.find({"id": {$in : idlist} } )
Only populate array friends.
User.findOne({ _id: "userId"})
.populate('friends')
.exec((err, user) => {
//do something
});
Result is same like this:
{
"_id" : "userId",
"name" : "John",
"age" : 30,
"friends" : [
{ "_id" : "userId1", "name" : "Derek", "age" : 34 }
{ "_id" : "userId2", "name" : "Homer", "age" : 44 }
{ "_id" : "userId3", "name" : "Bobby", "age" : 12 }
]
}
Same this: Mongoose - using Populate on an array of ObjectId
You can use playOrm to do what you want in one Query(with S-SQL Scalable SQL).
var p = db.sample1.find().limit(2) ,
h = [];
for (var i = 0; i < p.length(); i++)
{
h.push(p[i]['name']);
}
db.sample2.find( { 'doc_name': { $in : h } } );
it works for me.
You can do it in one go using mongo-join-query. Here is how it would look like:
const joinQuery = require("mongo-join-query");
joinQuery(
mongoose.models.User,
{
find: {},
populate: ["friends"],
sort: { age: 1 },
},
(err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results))
);
The result will have your users ordered by age and all of the friends objects embedded.
How does it work?
Behind the scenes mongo-join-query will use your Mongoose schema to determine which models to join and will create an aggregation pipeline that will perform the join and the query.

Resources