Using Backbone Relational, how to Store Array of Object IDs in Mongoose, Retrieve as Objects, Save Back as IDs - backbone.js

Here's my situation:
I have a Mongo Database with the following schemas:
var SurveySchema = new Schema({
start_date: Date,
end_date: Date,
title: String,
questions: [ObjectId] //this I want to refer to questions
});
var QuestionSchema = new Schema({
question_text: String,
type: String,
....... (Lots and lots of fields)
});
Full Schema file: https://github.com/nycitt/node-survey-builder-api-server/blob/master/schemas.js
Backbone models: https://github.com/nycitt/node-survey-builder/blob/master/app/js/survey-builder/models.js and collection https://github.com/nycitt/node-survey-builder/blob/master/app/js/survey-builder/collections.js
The Questions should be able to stand alone (be part of 2-3 surveys say). But a survey should refer to the question.
My ideal flow would be:
Backbone calls: GET /api/survey/123 and retreives the survey object where Mongoose translates the Object IDs to actual objects (does the join)
Backbone calls: POST /api/survey/123 posting ONLY the IDs of the questions (is there some Backbone Relational magic to do this)

Related

When should I use _id in MongoDB?

MongoDB has a field for every document called "_id". I see people using it everywhere as a primary key, and using it in queries to find documents by the _id.
This field defaults to using an ObjectId which is auto-generated, an example is:
db.tasks.findOne()
{
_id: ObjectID("ADF9"),
description: "Write lesson plan",
due_date: ISODate("2014-04-01"),
owner: ObjectID("AAF1") // Reference to another document
}
But in JavaScript, the underscore behind a field in an object is a convention for private, and as MongoDB uses JSON (specifically, BSON), should I be using these _ids for querying, finding and describing relationships between documents? it doesn't seem right.
I saw that MongoDB has a way to generate UUID https://docs.mongodb.com/manual/reference/method/UUID
Should I forget that _id property, and create my own indexed id property with an UUID?
Use UUIDs for user-generated content, e.g. to name image uploads. UUIDs can be exposed to the user in an URL or when the user inspects an image on the client-side. For everything that is on the server/not exposed to the user, there is no need to generate a UUID, and using the auto-generated _id is preferred.
An simple example of using UUID would be:
const uuid = require('uuid');
exports.nameFile= async (req, res, next) => {
req.body.photo = `${uuid.v4()}.${extension}`;
next();
};
How MongoDB names its things should not interfere in how you name your things. If data sent by third-party hurts the conventions you agreed to follow, you have to transform that data into the format you want as soon as it arrives in your application.
An example based in your case:
function findTaskById(id) {
var result = db.tasks.findOne({"_id": id});
var task = {
id: result._id,
description: result.description,
something: result.something
};
return task;
}
This way you isolate the use of Mongo's _id into the layer of your application that is responsible to interact with the database. In all other places you need task, you can use task.id.

User to User Database Object Structure

What is the best practice to structure my objects in my database in a Teacher to Student application? To be more specific, a teacher will have multiple students, and students may have multiple teachers they will interact with. Right now, each class a teacher is instructing has a "class id" and when a student enrolls, they add that same class ID to a list in their model. When a teacher wants to view a roster, I use
Student.find({"class-id": "abc123"}, function(err, student){
if(err){
console.log(err)
} else {
res.render("/roster", {student: student});
}
});
"abc123" is an example class code.
It's very basic code at the moment-- just starting a rough app. Should I make some type of different database structure with some type of hierarchy between teachers and students?
Sorry for the basic and perhaps confusing question, I'm very new to web development and just don't want to paint myself into a corner.
I'm using the MEAN stack but I think this is a more conceptual question.
Thank you
dmfay's comment is insightful. with a relational model, using mongoose is suboptimal. However, you of course can still use it. it would go something like this (by the way, you were unclear about this, but its implied that each student and teacher have multiple class-ids, im working under that assumption)
var mongoose = require("mongoose");
var StudentSchema = new mongoose.Schema({
//other student details
teachers: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Teacher"
}],
classIds: [STRING]
});
module.exports = mongoose.model("Student", StudentSchema);
and then the teacher schema would be along the same lines:
var mongoose = require("mongoose");
var TeacherSchema = new mongoose.Schema({
//other teacher details
students: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Student"
}],
classIds: [STRING]
});
module.exports = mongoose.model("Teacher", TeacherSchema);
then, how you go about it is, when a student enrols for a class, you find all teachers with that classId in their classIds array, you loop over them adding the student to each one of their students array, and you add them (the teacher) to the student's teachers array.
when you want to search for all students in a class, you go about like you did, except that the classIds is an array of strings, not a single string, you need to check if any of them matches

MongoDb subdocument array populate (via Mongoose ORM) : Does it maintain array order when populate is called

Suppose I have 2 Schema's in Mongoose that look like this:
var movieSchema = mongoose.Schema({
name: String,
type: String
});
var moviePlaylistSchema = mongoose.Schema({
name: String,
movies: [{type: mongoose.Schema.Types.ObjectId, ref: 'Movie'}]
});
var Movie = mongoose.model('Movie', movieSchema);
var MoviePlaylist = mongoose.model('MoviePlaylist', moviePlaylistSchema);
If a query was made along the following lines:
MoviePlaylist.find({}).populate('movies').exec(function(err, res) {
if (err) console.log('err', err);
else {
console.log('res', res);
res.forEach(function(elem, index) {
console.log('elem.name', elem.name);
});
}
});
Would the order of the elements in the array be maintained? The objective here is to allow the user to maintain a playlist order of their movies. If, when the "populate" method fires, the array order of Movie object Ids is not maintained, then this will not serve my purpose. Hence thought I'd ask someone who is more knowledgeable in this area.
If this works, then I have another task which is allowing the user to change the order of movies in the playlist, which should be straight forward by allowing the movie object id index to be swapped in the array.
Thanks for your help in advance.
MongoDB will keep the order of the array, much like an array in any programming language.
You can view the BSON/JSON spec for reference which highlights that the array must contain integer values for keys, and be maintained in ascending numerical order.
Additionally, the Mongoose populate on an array works by calling Model.populate via forEach on each element of the array. This modifies the array in place, hence the order is preserved. You can see the relevant source code here.

Backbone.js Dynamic model creation based on JSON received from the server

I am trying to create a backbone client side application. I am receiving a list of json objects from the server on startup that will be a list of the possible tables exposed from the server, with their structure. E.g. Customers, Orders, Invoices, Employees
I want to create the models, collections and views dynamically based on the data I receive from the server.
Only when I receive the json on load will I know what the models should be and what the relationships between the models should be.
E.g. Customers structure might be Id, CustomerName, Address, Contact Numbers.
Order Structure might be Id, CustomerId, OrderDate, Amount
etc
By building Models, collections, views, controllers dynamically, I could in theory on startup point at another server who might give me a totally different set of tables e.g. : Movies, Actors etc.. with their structures.
Also, if additional fields are added I don't have to change the client side code again. E.g. Customer table might include a new field called ContactPerson
Please assist me as all the examples I saw on backbone is all based on statically defining the models on the client side up front. So create a model and collections and views for Customers, Orders, Invoices, Employees etc. etc.
Best wishes,
Andy
As already mentioned in the comments, Backbone models are dynamic by nature. So this is perfectly valid for example:
// A example dataset, this could be returned as JSON from the server
var jsonDataA = [
{
name: "Foo",
title: "Bar"
},
{
name: "a",
title: "b"
}
],
// A different example dataset
jsonDataB = [
{
make: "X",
model: "Y"
},
{
make: "Z",
model: "ZZ"
}
],
MyModel = Backbone.Model.extend({
/* Empty Model definition */
}),
MyCollection = Backbone.Collection.extend({
model: MyModel
}),
collection = new MyCollection();
collection.reset(jsonDataA);
console.log(collection.models);
collection.reset(jsonDataB);
console.log(collections.models);
Here I have reused the same Collection and Model definition to store completely different datasets.
One part is the raw data, the other part is its relations. You need to transport the metadata also, which contains the types and their relations. Model attributes will be populated automatically.
From your metadata a simple object can be constructed, where the keys describe one entity, for example:
var entites = {};
entities["Customer"] = Backbone.Model.extend({
/* Model definition based on metadata */
});
var parametersFromServer = {name: "John Doe"};
var customer = new entities["Customer"](parametersFromServer);
For building relations I would recommend using BackboneRelational plugin.

Saving Entries to Database Relationship (many-to-many) Table from a Backbone Project

I currently have two Backbone models: user and project. I would like to have a Backbone view containing a form that enables the creation of a new project, and the association of currently existing users (entries from the database users table) with this project. When a user completes this form and clicks the save button, the new project should be saved into the database (in the projects table) and the relationship between the saved project and the related users should be saved into a relationship table (projects_users table, containing the corresponding project id and the user id for each relationship). Currently, I can save the project information, but could not get any data into the projects_users table using Backbone-relational.
What do you think the best approach to achieve the above functionality is? It would be great if you could point me to specific code that I could use as a template.
Thank you,
Alexandra
After some frustrating trial-and-error period, I finally managed to get my code working! It's not beautiful, but it is functional, and now I can start thinking about improving it. Hopefully, some other people will find this information useful ...
The one thing that put me on the right track was the understanding that what needed to be changed was not only in the backbone view code (the one with the new projects form), but also in the corresponding rails models.
For the rails part (Rails 3.2.2), I made sure that the following model files had the necessary information:
project.rb
class Project < ActiveRecord::Base
has_and_belongs_to_many :users
#attr_accessible :name, :description, :users_attributes
#has_many :projects_users, foreign_key: "project_id", dependent: :destroy
#has_many :users, through :projects_users
#accepts_nested_attributes_for :users
end
user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :projects
end
projects_users.rb
class ProjectsUsers < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
I have read in many different places that has_and_belongs_to_many is not the best way to set many-to-many relationships in rails. Nevertheless, I could not get the same functionality using the has_many definition - the commented part in project.rb is the way I tried this different approach. The file user.rb had some corresponding code, which I removed for simplicity.
Now, what I needed to get done in the backbone form view was to send a POST request with a JSON object that the rails projects_controller.rb could recognize. Initially, I tried several POST requests without success (and no errors to guide me). But then, I remembered to have previously implemented a form for teams where users could be added to a particular team (HABTM Checkboxes - there is a railscast for this functionality). After looking at this example, I realized what was needed from my POST request. This is what I wanted to see in the rails server log file:
Started POST "/projects" for 127.0.0.1 at 2012-06-27 00:35:22 +0000
Processing by ProjectsController#create as JSON
Parameters: {"project"=>{"description"=>"with some description", "user_ids"=>["101", "1", "99"], "name"=>"some new project"}}
Backbone relevant files to achieve the above request:
project.js
App.Models.Project = Backbone.Model.extend({
urlRoot: '/projects',
// Default attributes for the project.
defaults: {
description: "",
user_ids: []
},
/* getters */
});
user.js
App.Models.User = Backbone.Model.extend({
/* getters */
});
form.js
App.Views.Projects.Common.Form = Backbone.View.extend({
...
events: {
"submit #new_project_form" : "formSubmit"
},
formSubmit: function(event) {
this.submitted($(event.target));
return false;
},
submitted: function(formElement) {
var newData = this.serializeFormData(formElement);
this.model = new App.Models.Project({
name : newData.name,
description : newData.description
});
this.saveFormData(newData);
return false;
},
serializeFormData: function(formElement) {
var fields = formElement.serializeArray();
var serializedData = {};
$.each(fields, function(index, field) {
serializedData[field.name] = field.value;
});
return serializedData;
},
// THE IMPORTANT PART FOR THE POST REQUEST
saveFormData: function(newData) {
// preserve reference to view for callbacks
var self = this;
var project = this.model;
project.set({
// a list of user ids associated with a project
"user_ids" : this.view_variables.user_ids
});
var project_object = ({
"project" : _.clone(project.attributes)
});
$.ajax({
type: 'POST',
url: '/projects',
data: project_object,
dataType: "json",
success: function() {
self.$el.hide();
self.addNewModelToCollection();
}
});
},
...
});
The code is kind of verbose, and includes some code that is specific to my project. Still, the relevant part is in the saveFormData function, where the jQuery ajax function is used.
In case you have any suggestions, either for the rails or for the Backbone part, please let me know. I will be happy to learn how to improve this solution.
I know it's old post but this looks interesting: http://backbonerelational.org/

Resources