Mongoid and Mongodb querying - ruby-on-rails

I have a data strcuture like this
Courses
{
"name" : "Course1",
"student_id"
"subjects" :
[{
"level" : "1",
"short_name" : "Maths",
"topics" : [{
"name" : "Algebra 101",
"week" : "1",
"submission_week" : ISODate("2013-07-28T00:00:00Z"),
"correction_week" : ISODate("2013-07-28T00:00:00Z")
},
{
"name" : "Algebra 201",
"week" : "1",
"submission_week" : ISODate("2013-07-28T00:00:00Z"),
"correction_week" : ISODate("2013-07-28T00:00:00Z")
}
]},
{
"level" : "2",
"short_name" : "Chem"
}
]
}
Using Mongoid I am trying to retrieve all topics.
I have tried all sorts of queries but cannot seem get it.
e.g I don't understand why this doesn't work?
Topic.where( name: "Algebra 101", 'subject.short_name' =>"Maths", 'subject.course.name' =>"Course1")
Can I query like this?
My ruby code is
class Course
embeds_many :subjects
class Subject
embedded_in :course
embeds_many :topics
class Topic
embedded_in :subject

A far as I know it's only possible to make such a query on the top-most model (in this case Course); I have not seen a way to directly obtain an embedded model like that yet.
So this should work, but only gives you the Course:
Course.where(name: 'Course1', 'subjects.short_name' => 'Maths', 'subjects.topics.name' => "Algebra 101")
And this is the (unfortunately rather ugly) query that should give you what you want:
Course.where(name: 'Course1').subjects.where(short_name: 'Maths').topics.where(name: 'Algebra 101')

Related

What should an iOS JSON payload submitting to an API look like?

We have an iOS application that grabs model data from an API. The user of the iOS device performs actions that link up the model data and creates relationships between them. The iOS then submits back to the API to save the relationship and allow for the relationship to be viewable in the web application.
The JSON payload has the potentially of being large if all the objects that were associated with the new relationship are submitted with their attributes.
"peoples_vehicles": [
{
"owner" : {
"id" : 8282, <----------this would be a UUID
"name" : "John Smith",
"address": "123 Fake Street",
},
"vehicles" : [
{
"id" : 1234, <----------this would be a UUID
"name" : "FORD F150",
"make" : "F150",
"model" : "FORD",
"year" : "2017",
},
{
"id" : 5678, <----------this would be a UUID
"name" : "FORD ESCAPE",
"make" : "ESCAPE",
"model" : "FORD",
"year" : "2013",
}
]
},
{
... another person with their vehicles
}
]
Since the data being sent back is all data that was originally from the API, should the iOS application even bother sending all the attributes back? Should the iOS application simply send back all the relationships with only the objects ids? We use UUIDs
"peoples_vehicles": [
{
"owner" : {
"id" : 8282, <----------this would be a UUID
},
"vehicles" : [
{
"id" : 1234, <----------this would be a UUID
},
{
"id" : 5678, <----------this would be a UUID
}
]
},
{
... another person with their vehicles
}
]
We seem to be leaning more towards just sending ID for pre-existing data. It makes the submit payload from iOS to API have very few attributes that are actually strings (none in this example).
There are cases when we would have to create new custom objects. In that case, all the attributes would be sent over and the ID of this new object would be null to indicate that this object has to be created.
"peoples_vehicles": [
{
"owner" : {
"id" : 8282, <----this would be a pre-existing object
},
"vehicles" : [
{
"id" : 1234, <----this would be a pre-existing object
},
{
"id": null <----new object that needs to be saved
"name" : "FORD F150",
"make" : "F150",
"model" : "FORD",
"year" : "2017",
}
]
},
{
... another person with their vehicles
}
]
Would this be a decent approach? Looking at Stripe and Shopify API as examples, this seems to work nicely, but I wanted to make sure I wasn't missing anything and if I should be included the attributes for objects that are pre-existing.

mongodb: best way to get specific documents and then the rest

lets say I have 1000 documents where each one has:
user_id
text
Now, I would like to pull all those documents but first pull the documents from a few specific users (given an array of user ids) and then all the rest.
I was thinking to use map reduce to create a new weight inline attribute if the user_id exists in the specific users array (using scope to pass the array) and then to sort that new attribute. But from what I could understand, you can not sort after map reduce.
Any one has a good suggestion how to pull this off? Any suggestion will be welcome.
Thanks!
Well there isn't a lot of detail here, but I can give a sample case for consideration. Consider the following set of documents:
{ "user" : "fred", "color" : "black" }
{ "user" : "bill", "color" : "blue" }
{ "user" : "ted", "color" : "red" }
{ "user" : "ted", "color" : "black" }
{ "user" : "fred", "color" : "blue" }
{ "user" : "bill", "color" : "red" }
{ "user" : "bill", "color" : "orange" }
{ "user" : "fred", "color" : "orange" }
{ "user" : "ted", "color" : "orange" }
{ "user" : "ally", "color" : "orange" }
{ "user" : "alice", "color" : "orange" }
{ "user" : "alice", "color" : "red" }
{ "user" : "bill", "color" : "purple" }
So suppose you want to bubble the items for the users "bill" and "ted" to the top of your results, then everything else sorted by the user and the color. What you can do is run the documents through a $project stage in aggregate, as follows:
db.bubble.aggregate([
// Project selects the fields to show, and we add a weight value
{$project: {
_id: 0,
"user": 1,
"color": 1,
"weight": {$cond:[
{$or: [
{$eq: ["$user","bill"]},
{$eq: ["$user","ted"]}
]},
1,
0
]}
}},
// Then sort the results with the `weight` first, then `user` and `color`
{$sort: { weight: -1, user: 1, color: 1 }}
])
So what that does is conditionally assign a value to weight based on whether the user was matched to one of the required values. Documents that do not match are simply given a 0 value.
When we move this modified document on to the $sort phase, the new weight key can be used to order the results so the "weighted" documents are on top, and anything else will then follow.
There a quite a few things you can do to $project a weight in this way. See the operator reference for more information:
http://docs.mongodb.org/manual/reference/operator/aggregation/

How to make scope from embedded_in that get only first element?

I have many coordinates embedded in place. How to get only first "start" coordinate for each self Place object? Scope is correct idea? I can select only first, last or all of the places with all(is very slow) coordinates, my commented out scope doesn't work.
code:
class Place
include Mongoid::Document
field :title, :type => String
embeds_many :coordinates
# def self.start_coordinate
# self.coordinates.first
# first = self.coordinates.first
## first = self.find({}, { "coordinates" => { "_id" => firstobj?}})
## first = self.find({}, { "coordinates" => {}, :limit=>1})
## self.includes(:coordinates).first
## self.collection.(:coordinates).find_one()
### self.all
# end
end
class Coordinate
include Mongoid::Document
include Mongoid::Spacial::Document
field :coordinates, :type => Array, spacial: true
spacial_index :coordinates
embedded_in :place, :inverse_of => :coordinate
end
MongoDB place object:
{ "_id" : ObjectId( "4ece5a04ca6a175b08000016" ),
"coordinates" : [
{ "lat" : 51.54983275438141,
"lng" : 17.31981750561522,
"_id" : ObjectId( "4ece5a04ca6a175b08000002" ) },
{ "lat" : 51.55282151156834,
"lng" : 17.35552307202147,
"_id" : ObjectId( "4ece5a04ca6a175b08000003" ) },
{ "lat" : 51.53830285151265,
"lng" : 17.39397522045897,
"_id" : ObjectId( "4ece5a04ca6a175b08000004" ) } ],
"created_at" : Date( 1322146308000 ),
"description" : "description",
"title" : "test",
"updated_at" : Date( 1322154405000 ),
"user_id" : ObjectId( "4ecd7d4eca6a175783000010" ) }
Scopes are typically used to filter the result set of whole objects you want based upon some criteria. Instead, it looks like you are trying to select certain attributes of a given document. To do that you should use the only method.
This might work, not sure about the exact syntax though:
Place.only(":coordinates.0")

Ruby on rails polymorphic model: dynamic fields

Suppose I have base model class Item
class Item
include Mongoid::Document
field :category
end
Each category determines which fields should item contain. For example, items in "category1" should contain additional string field named text, items in "category2" should contain fields weight and color. All the fields are of basic types: strings, integers and so on.
These additional values are to be stored in mongodb as document's fields:
> db.items.find()
{ "_id" : ObjectId("4d891f5178146536877e1e99"), "category" : "category1", "text" : "blah-blah" }
{ "_id" : ObjectId("4d891f7878146536877e1e9a"), "category" : "category2", "weight" : 20, "color" : "red" }
Categories are stored in the mongodb, too. Fields' configuration is defined at runtime by an administrator.
> db.categories.find()
{ "_id" : "category1", "fields" : [ { "name" : "text", "type" : "String" } ] }
{ "_id" : "category2", "fields" : [
{
"name" : "weight",
"type" : "Integer"
},
{
"name" : "color",
"type" : "String"
}
] }
Users need to edit Items with html forms entering values for all additional fields defined for the category of particular item.
The question is
What approaches could I take to implement this polymorphism on rails?
Please ask for required details with comments.
Just subclass the Item, and Mongoid will take care of rest, e.g. storing type.
class TextItem < Item; end
Rails will like it, but you'll probably want to use #becomes method, as it will make form builder happy and path generation easier:
https://github.com/romanbsd/mongoid/commit/9c2fbf4b7b1bc0f602da4b059323565ab1df3cd6

MongoDB/Mongoid: Can one query for ObjectID in embedded documents?

For the record, I'm a bit of a newbie when it comes to Rails and MongoDB.
I'm using Rails+Mongoid+MongoDB to build an app and I've noticed that Mongoid adds ObjectID to embedded documents for some reason.
Is there any way to query all documents in a collection by ObjectID both the main documents and nested ones?
If I run this command
db.programs.findOne( { _id: ObjectId( "4d1a035cfa87b171e9000002" ) } )
I get these results which is normal since I'm querying for the ObjectID at root level.
{
"_id" : ObjectId("4d1a035cfa87b171e9000002"),
"created_at" : "Tue Dec 28 2010 00:00:00 GMT+0000 (GMT)",
"name" : "program",
"routines" : [
{
"name" : "Day 1",
"_id" : ObjectId("4d1a7689fa87b17f50000020")
},
{
"name" : "Day 2",
"_id" : ObjectId("4d1a7695fa87b17f50000022")
},
{
"name" : "Day 3",
"_id" : ObjectId("4d1a76acfa87b17f50000024")
},
{
"name" : "Day 4",
"_id" : ObjectId("4d1a76ecfa87b17f50000026")
},
{
"name" : "Day 5",
"_id" : ObjectId("4d1a7708fa87b17f50000028")
},
{
"name" : "Day 6",
"_id" : ObjectId("4d1a7713fa87b17f5000002a")
},
{
"name" : "Day 7",
"_id" : ObjectId("4d1a7721fa87b17f5000002c")
}
],
"user_id" : ObjectId("4d190cdbfa87b15c2900000a")
}
Now if I try to query with an ObjectID with one of the embedded document (routines) I get null like so.
db.programs.findOne( { _id: ObjectId( "4d1a7689fa87b17f50000020" ) } )
null
I know one can query embedded objects like so
db.postings.find( { "author.name" : "joe" } );
but that seems a bit redundant if you get passed an ObjectID of some sort and want to find in what document that ObjectID resides.
So I guess my question is this...
Is it possible, with some method I'm not familiar with, to query by ObjectID and search the ObjectID's in the embedded documents?
Thanks.
You can't query ObjectIDs globally like that. You would have to do
db.programs.find({"routines._id": ObjectId("4d1a7689fa87b17f50000020")})
no, you can search only by field like { "routines._id" : ObjectId("4d1a7689fa87b17f50000020")}
If you want to get the matched sub-document only you can use $elemMatch with '$' operator like below:
db.programs.find({"_id" : ObjectId("4d1a035cfa87b171e9000002"),
routines:{$elemMatch:{"_id" : ObjectId("4d1a7689fa87b17f50000020")}}},{"routines.$":1})
It will return you only that matched sub-document instead of the complete sub-document.

Resources