Nesting AND and OR operation with MongoID - sinatra

I'm trying to figure out how to nest an AND and OR operation with MongoID, like so (taken from something that I used to use with MongoMapper):
{
:$and=> [
{
:$or => [
{"name"=> "joe"}, {"name" => "randy" }
{
:$or=> [
{"something" => "else" }, {"another" => "thing" }
]
}
]
}
I'm not too terribly familiar with the way union and intersection works, but the kicker is that each child within the AND is optional/not guaranteed. In other words, each query within AND is programmatic, there could be 2 items to check against, 1 item, etc.
I thought about doing something like this:
Model.or({ :name => "...." }).union.or( :something => "...." })
But, the only problem with that is I'm not sure on the best practice of constructing the query based on user input. I have a sinatra-based application that acts as an API point for my users that is connecting to my MongoID models, and I'd like for users to be able to construct queries like this (maybe not this complicated) over the API.
I'm migrating over to MongoID from MongoMapper for various reasons, but with MongoMapper these queries were a little simpler because everything, such as nested and and or operators, are supported within a where method.

Turns out that MongoID (well, more specifically Origin::Query) supports Mongo selector syntax within many of their DSL functions, like so:
Model.where( { "name" => { "$or" => [ "betsy", "charles" ] } )
So gathering from my above example, you can just do this:
Model.all_of( [
{
"$or" => [
{"name"=> "joe"}, {"name" => "randy" }
},
{
"$or" => [
{"something" => "else" }, {"another" => "thing" }
]
}
]

Related

how to query documents in mongo that have nested objects and the object name is not static [duplicate]

I have the following collection structure:
{
_id : ObjectId('...'),
randomKeysSubdocument : {
'jhaksdf' : 'something',
'jio348akgqug' : 'something else',
'kgowe98akjg' : 'more things',
}
}
where randomKeysSubdocument is a subdocument with random keys generated programmatically (and i don't know their names).
Is there a way how I can query by randomKeysSubdocument values in the same way as I would query them if randomKeysSubdocument were an array:
{
_id : ObjectId('...'),
randomKeysSubdocument : [
'something',
'something else',
'more things',
]
}
Query:
db.my_collection.find({
randomKeysSubdocument : 'something'
})
So it should be clear that you should not do this. Structuring documents with different key names is not a good pattern, and certainly does not work well with MongoDB or general database searching.
Only "data" can be indexed, so a better structure for you would be:
{
randomKeysSubdocument : [
{ "key": 'jhaksdf', "data": 'something' },
{ "key": 'jio348akgqug', "data": 'something else' }
{ "key": 'kgowe98akjg', "data": 'more things' }
]
}
Where searching in the array as you note is already easy:
db.collection.find({ "randomKeysSubdocument.data": "something" })
And of course that can use an index to make things faster.
The way you currently have things you have to rely on JavaScript processing of $where for the match and cannot use an index for this:
db.collection.find(function() {
var doc = this.randomKeysSubDocument;
return Object.keys(doc).some(function(key) {
return doc[key] == "something";
});
})
Which is going to be much slower than a standard query and must test every document in the collection.
For mine, I would change the structure and move away from the pattern of using "data" as the names of keys, but put it in data instead where it belongs and keeps structures consistent for searching.

mongodb findAll with 2 parameters equal to each other

How do I translate this sql query to mongodb
Select * From Users Where type = "S" and registration_token = username;
I've tried this
$users = User::model()->findAll(array(
"type" => "S",
"registration_token" => "username"
));
but no joy...
This is a relational query, so please keep in mind that MongoDB isn't geared towards this kind of operation like SQL is. This type of query will usually be significantly slower than in an SQL database. I'd also consider this type of 'meta-logic' bad design, because the fact that two seemingly independent values match shouldn't mean anything.
That said, you still have two options. You can use the JavaScript-based $where:
db.coll.find({$and : [
{"type" : "S"},
{"$where": function() { return this.registration_token === this.username; } }]})
However, this approach is slow because it needs to fire up JavaScript for each object it finds (i.e. for all those with type == 'S'). It might also have security implications if any of the data in the $where comes from the end user.
Alternatively, you can use the aggregation pipeline:
> db.coll.aggregate([{ "$project": {
"username": "$username",
"type": "$type",
"registration_token" : "$registration_token",
"match": { "$eq": ["$username","$registration_token"]} }},
{ "$match": { "match": true } } ])

MongoDB. Is it possible to create value with string

I'm using mongodb with aggregation framework and I need to somehow pass in my query value with some string.
I need something like this:
{
'$project' => {
'value' => '$value',
'label' => 'Some string'
}
}
And in result I should have the following:
{
value => 'value of $value',
label => 'Some string'
}
I have to use only aggregation framework.
In shell you can use this "solution":
t = "Some text..."
db.towns.aggregate([
{'$project': {"nm" : "$name", "text": {$substr : [t,0, t.length]}}}
])
You only need to adapt for your programming language.
independently of the programming language, and before $literal is there, you can use for example $concat to assign static value to projection (how to define is as parameter depends actually on the PL/driver that you use):
{
'$project' : {
'value' : {$concat: ['$value']} ,
....
}
}

Mongoid query retrieving embedded object using '$in' operator

I'm very beginner of mongoid, so I apology for the basic question.
I'm looking for an mongoid statement which throw the mongoDB query like below:
db.mycollection.find({"status.user.name": {$in:["jack","mary"]}}
There are two documents in mycollection in mongoDB
{ "status": {"user: {:name : "jack"}} }
{ "status": {"user: {:name : "mary"}} }
I tried below queries with mongoid, but it did not fetch any document:
MyCollection.where("status.user.name" => {'$in' => ["jack","mary"]})
MyCollection.in("status.user.name" => ["jack","mary"])
update on 2013/05/04
I'm sorry. I did not grasp the actual situation when I submit the question. But now I understand what happened and solved the issue. I update the question for those struggling the same issue.
The problem came from my misunderstanding of "embedding" and "referencing".
In the above situation, user is embedded in mycollection. Then, the in method can fetch all of two documents.
MyCollection.in("status.user.name" => ["jack", "mary"]).size
=> 2
But, in my actual situation, mycollection just referenced the user collection.
mycollection collection
{ "status": {"user_id": xyzxyz} }
{ "status": {"user_id": abcabc} }
user collection
{ "_id":xyzxyz, "name": "jack" }
{ "_id":abcabc, "name": "mary" }
That's why I couldn't fetch any documents with where or in method. In my understanding, I can't fetch user documents via mycollection with only one query becaus MongoDB isn't a RDBMS and doesn't support such kind of relation query.
MyCollection.in("status.user.name" => ["jack", "mary"]).size
=> 0
How about:
MyCollection.in(status.user.name: [ "jack", "mary" ]).find();

Using stored JavaScript functions in the Aggregation pipeline, MapReduce or runCommand

Is there a way to use a user-defined function saved as db.system.js.save(...) in pipeline or mapreduce?
Any function you save to system.js is available for usage by "JavaScript" processing statements such as the $where operator and mapReduce and can be referenced by the _id value is was asssigned.
db.system.js.save({
"_id": "squareThis",
"value": function(a) { return a*a }
})
And some data inserted to "sample" collection:
{ "_id" : ObjectId("55aafd2bacbed38e06f9eccf"), "a" : 1 }
{ "_id" : ObjectId("55aafea6acbed38e06f9ecd0"), "a" : 2 }
{ "_id" : ObjectId("55aafeabacbed38e06f9ecd1"), "a" : 3 }
Then:
db.sample.mapReduce(
function() {
emit(null, squareThis(this.a));
},
function(key,values) {
return Array.sum(values);
},
{ "out": { "inline": 1 } }
);
Gives:
"results" : [
{
"_id" : null,
"value" : 14
}
],
Or with $where:
db.sample.find(function() { return squareThis(this.a) == 9 })
{ "_id" : ObjectId("55aafeabacbed38e06f9ecd1"), "a" : 3 }
But in "neither" case can you use globals such as the database db reference or other functions. Both $where and mapReduce documentation contain information of the limits of what you can do here. So if you thought you were going to do something like "look up data in another collection", then you can forget it because it is "Not Allowed".
Every MongoDB command action is actually a call to a "runCommand" action "under the hood" anyway. But unless what that command is actually doing is "calling a JavaScript processing engine" then the usage becomes irrelevant. There are only a few commands anyway that do this, being mapReduce, group or eval, and of course the find operations with $where.
The aggregation framework does not use JavaScript in any way at all. You might be mistaking just as others have done a statement like this, which does not do what you think it does:
db.sample.aggregate([
{ "$match": {
"a": { "$in": db.sample.distinct("a") }
}}
])
So that is "not running inside" the aggregation pipeline, but rather the "result" of that .distinct() call is "evaluated" before the pipeline is sent to the server. Much as with an external variable is done anyway:
var items = [1,2,3];
db.sample.aggregate([
{ "$match": {
"a": { "$in": items }
}}
])
Both essentially send to the server in the same way:
db.sample.aggregate([
{ "$match": {
"a": { "$in": [1,2,3] }
}}
])
So it is "not possible" to "call" any JavaScript function in the aggregation pipeline, nor is there really any point is "passing in" results in general from something saved in system.js. The "code" needs to be "loaded to the client" and only a JavaScript engine can actually do anything with it.
With the aggregation framework, all of the "operators" available are actually natively coded functions as opposed to the "free form" JavaScript interpretation provided for mapReduce. So instead of writing "JavaScript", you use the operators themselves:
db.sample.aggregate([
{ "$group": {
"_id": null,
"sqared": { "$sum": {
"$multiply": [ "$a", "$a" ]
}}
}}
])
{ "_id" : null, "sqared" : 14 }
So there are limitations on what you can do with functions saved in system.js, and the chances are that what you want to do is either:
Not allowed, such as accessing data from another collection
Not really required as the logic is generally self contained anyway
Or probably better implemented in client logic or other different form anyway
Just about the only practical use I can really think of is that you have a number of "mapReduce" operations that cannot be done any other way and you have various "shared" functions that you would rather just store on the server than maintain within every mapReduce function call.
But then again, the 90% reason for mapReduce over the aggregation framework is usually that the "document structure" of the collections has been poorly chosen and the JavaScript functionality is "required" to traverse the document for search and analysis.
So you can use it under the allowed constraints, but in most cases you probably should not be using this at all, but fixing the other issues that caused you to believe you needed this feature in the first place.