Ruby on rails polymorphic model: dynamic fields - ruby-on-rails

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

Related

Using aws-api-gateway models, how can I require an object contains AT LEAST 1 valid key

I am using API gateway's request validation. Here is my model so far:
{
"type" : "object",
"required" : [ "dc", "uid", "data" ],
"properties" : {
"dc" : {
"type" : "string"
},
"uid" : {
"type" : "string"
},
"data" : {
"type" : "object"
}
},
"title" : "MyApi"
}
So similar to the required function, I want to ensure that the data object has at least one key in a list I define [a, b, c, whatever]
If this is not possible, is there a way to at least prevent a null value from being sent? I tried "nullable": false but AWS said that was an invalid model schema.
Setting minProperties to '1' may work. See https://swagger.io/docs/specification/data-models/data-types/
Can (should) data be strongly typed? If so you could use composition, inheritance, and polymorphism to indicate that the data is a defined schemas. See https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/

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.

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"})

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/

Mongoid and Mongodb querying

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')

Resources