Strategy for sharing and reusing of schema parts in mongoose.js - database

Suppose, I have two schemas, e.g. for person and company. Both of them should have an address, which consists of a street name, number, zip and city.
What would be the strategy to avoid copying the address properties between the two schema definitions? I read about sub docs, but they seem to be (1) tied to exactly one parent schema, (2) always occur in an array.

Almost too obvious, but this is what I came up with finally:
Define the reusable portions separately, however, contrary to my first thoughts: do not use a Schema here:
var addressSubschema = {
street: String, number: String, zip: String, city: String
}
Simply include this part in actual schemas:
var personSchema = new mongoose.Schema({
name: { type: String, required: true },
title: { type: String },
address: addressSubschema
});
var companySchema = new mongoose.Schema({
name: { type: String, required: true },
addresses: [addressSubschema]
});

My (naive?) approach might be to create a new schema for address, and use Mongoose's population mechanic to link to a given address by ObjectID. However, this is essentially emulating a relational DB behaviour, and I'm not sure how PC that is when using a flat-file store like Mongo.

Related

How to design the user/order/shipping model in mongo for a e-commerce app?

Every order has a shipping info and every user has several shipping addresses. This is my design:
User:
...
defaultAddress: ObjectId
...
Address:
...
...
...
Order:
...
shipping: ObjectId
...
Here are my questions:
Is it reasonable to use a reference for shipping in Order Model, or just embed the entire Address object.
Every time add a new address from App, Is there any better way to check if it is already exist in mongodb?
Should I keep a defaultAddress key in User Model, or should I just use a array to keep all the address object?
Thanks very much.
Addresses bind closely to Users, so make Addresses an array inside Users.
Depending on your overall audit / revisioning data architecture, you may wish to copy the address into the Order doc upon creation to create an unambiguous record of where it went. Having Order reference back into User and address implies you cannot change the address; you would have to mark it as no longer active but not deleted.
Note you do not have to laborious copy all the fields; you can treat the selected address as an object, something like:
var addr = get addr; // from DB or wherever; any shape with any number of subfields is fine.
db.order.insert({orderId:"someID", date:new ISODate(), shippingAddress: addr});
To prevent dupes, it is a matter of looking up the info in the array -- but of course caveats apply such as making sure you canonicalize case, strip white space, remove unnecessary punctuation, etc. etc. Let's assume you have street,city, and state as the matchable fields:
{
"user":"U1",
"addrs": [
{street: "20 Pine Ln", city: "Smallville", state: 'NM'},
{street: "16 Elm Ln", city: "Denver", state: 'CO'},
{street: "77 Sunshine Pkway", city: "Denver", state: 'CO'},
]
}
After grooming the input from app, assume we have a target lookup doc like this. Note: We do NOT have to create the doc with fields in a particular order.
var target = {city: "Denver", state: 'CO', street: '16 Elm Ln'};
The lookup is simple. Since addrs is an array, the match will dive into and iterate over the array. The $elemMatch operator ensures that ALL fields in the candidate match those in the target.
db.foo.aggregate([
{$match: {user:'U1', "addrs":{$elemMatch: target} }}
]);
If no records are found, then this is a new address.

Meteor inserting into a collection schema with array elements

Hi I created a SimpleSchema for a Mongo collection which has a variable number of sub-documents called measurables. Unfortunately it's been a while since I've done this and I can't remember how to insert into this type of schema! Can someone help me out?
The schema is as follows:
const ExerciseTemplates = new Mongo.Collection('ExerciseTemplates');
const ExerciseTemplateSchema = new SimpleSchema({
name: {
type: String,
label: 'name',
},
description: {
type: String,
label: 'description',
},
createdAt: {
type: Date,
label: 'date',
},
measurables: {
type: Array,
minCount: 1,
},
'measurables.$': Object,
'measurables.$.name': String,
'measurables.$.unit': String,
});
ExerciseTemplates.attachSchema(ExerciseTemplateSchema);
The method is:
Meteor.methods({
addNewExerciseTemplate(name, description, measurables) {
ExerciseTemplates.insert({
name,
description,
createdAt: new Date(),
measurables,
});
},
});
The data sent by my form for measurables is an array of objects.
The SimpleSchema docs seem to be out of date. If I use the example they show with measurables: type: [Object] for an array of objects. I get an error that the the type can't be an array and I should set it to Array.
Any suggestions would be awesome!!
Many thanks in advance!
edit:
The measurable variable contains the following data:
[{name: weight, unit: kg}]
With the schema above I get no error at all, it is silent as if it was successful, but when I check the db via CLI I have no collections. Am I doing something really stupid? When I create a new meteor app, it creates a Mongo db for me I assume - I'm not forgetting to actually create a db or something dumb?
Turns out I was stupid. The schema I posted was correct and works exactly as intended. The problem was that I defined my schema and method in a file in my imports directory, outside both client and server directories. This methods file was imported into the file with the form that calls the method, and therefore available on the client, but not imported into the server.
I guess that the method was being called on the client as a stub so I saw the console.log firing, but the method was not being called on the server therefore not hitting the db.
Good lesson for me regarding the new recommended file structure. Always import server side code in server/main.js!!! :D
Thanks for your help, thought I was going to go mad!

Mongoose schema to require array that can be empty

I have this schema
var StuffSchema = new mongoose.Schema({
_id: { type: String, required: true, unique: true },
name: { type: String, required: true }
});
mongoose.model('Stuff', StuffSchema);
Works fine.
Now I need to add another schema "Cargo" containing this
mystuff: { type:[String], ref: 'Stuff', required:true},
that is, I want mystuff to contain array of ids of Stuff, but this fails with validation error when running this code
mongoose.model('Cargo').create( some data...)
if I use an empty array for the mystuff field.
It seems to work if I change the Cargo schema to
mystuff: { type:[String], ref: 'Stuff'},
but I want the mystuff field to be required and allow empty arrays
What can I do to make this happen?
Empty arrays are created by default (see also this). The attribute required: true requires the array to have at least one element in it (source code). You can remove that attribute to get your desired behavior.
(Aside, mongoose assigns a default _id field with the type ObjectId to all schemas. Declaring it is unnecessary, and using a string is not typical, although certainly allowed.)
Edit Nov 2017: This is a candidate change in Mongoose 5. See https://github.com/Automattic/mongoose/issues/5139.

When to use a nested array versus using a separate collection

I need to use mongoose with dbref but I don't know which design is better for me.
First design:
var user = mongoose.Schema({
name: 'string'
});
var eventSchema = mongoose.Schema({
title: 'string',
propietary_id: 'String',
comments : [{
text: 'string',
user: { type : mongoose.Schema.Types.ObjectId, ref : 'users' },
createdAt: {type: Date, default: Date.now }
}]
});
Second design:
var user = mongoose.Schema({
name: 'string'
});
var eventSchema = mongoose.Schema({
title: 'string',
propietary_id: 'String'
});
var commentSchema = mongoose.Schema({
text: 'string',
event_id : { type : mongoose.Schema.Types.ObjectId, ref : 'events' },
user_id : { type : mongoose.Schema.Types.ObjectId, ref : 'users' },
createdAt: {type: Date, default: Date.now }
});
How it works? On my website there is an event list and if you want to see comments you have to click every event, then angularjs gets all comments (text, user name and user photo) of the selected events.
There are pros and cons with both solutions and the best one for you depends on your usage. Remember that you can produce exactly the same API independent of your design it only comes down to how quickly and easily you can maintain the backend. First some thoughts on both designs:
First design:
First a comment, I wouldn't save comments as a nested document but as an array instead. Otherwise you are limited to one comment per event. Use this schema instead:
comments: [
{
text: { type: String },
user: { type: mongoose.Schema.Types.ObjectId, ref : 'users' },
createdAt: { type: Date, default: Date.now },
}
]
Pros:
No need for multiple collections
You will have the comments returned with the event in the get request which will mean less requests to your backend
No need to map comments to events
Cons:
You will have the comments returned to you with the event, even if you don't want them displayed
If there are a lot of comments to an event, the request response will be pretty large
If you want to remove or edit comments in your array it will be trickier (not impossible though)
Second design:
Pros:
You will have the events and comments separated which means leaner objects
You can much easier extract one comment for edit or delete
You can more easily get events without comments and then request comments at another point
Cons:
You will need to always map comments to events which will mean more code
Two collections will mean two requests usually
Maintenance of another collection
Verdict:
All the pros and cons are judged by how much extra code you need to write. Of course you can always have comments returned with your events in the second design as well but then you will have the extract the comments first and returned them with the event object which will mean extra code to maintain.
I think the second design would work better for you. I'm judging this by your comment that you will only need comments if the user click on an event. I would then be requesting the events first and do another request for comments as soon as the user click on the event, however, having the comments always be returned with the events should make the ui more snappy as the comments will already have been loaded.
It all depends in the end what is more important for you to do with the data. Please let me know if you have any questions on any of the points.

Data Modeling Best Practices in Firebase/AngularFire

I'm developing an application in Firebase for the first time and was curious how I should model the data between two objects, a user and a post. I come from more of a relational db background and was curious not only how this would be done in nonrelational DBs but specifically how to set up a relationship between two objects in Firebase.
For example, my application has many Users, and each user creates many Posts.
User {
firstName: String,
lastname: String,
userName: String
}
Post {
title: String,
content: String,
date: Date,
writtenBy: [User object?]
}
How should I structure these two objects in Firebase so that a Post belongs to a User, but all Posts can be queried for regardless of User, and both User and Post objects can be edited without disrupting the other object's data and/or relationship?
And how should I create new "relational" objects via firebase:
sync.$set({userA: {
firstname: "Billy",
lastName: "Bob",
userName: "BillyBob",
Posts: {
// .....
}
}
});
Thanks!
Firebase is built with performance in mind. This is the reason you have to design data structures differently, normalization is your enemy in most cases. Every object in Firebase can be accessed by URL, and you should always keep this in mind.
There are still many ways of designing the data structures, it depends on what queries do you want to execute. If one of the queries is to be able to display all messages (I believe a number of latest messages would be the most common use case), but at the same time you want to be able to show messages per user than one of the possible data structures could look like this:
User {
userId(assigned by Firebase automatically) {
firstName: String,
lastname: String,
userName: String
}
}
Post {
User {
userId(matching userId in the User object) {
postId(assigned by Firebase for every new post automatically) {
title: String,
content: String,
date: Date,
writtenBy: String, userName or userId (this is not really needed, but may keep it for easier data access)
}
}
}
}
Then you can change any user data without triggering data change events in Posts, like in your example, (which would be extremely heavy if you have large number of messages).
You can get all messages independently of user:
var postListRef = new Firebase(URL);
var lastPostQuery = postListRef.child("Post").limit(500);
You can also use startAt() and endAt() quesries https://www.firebase.com/docs/web/api/query/limit.html
As a drawback - you have to unpack every message in the for loop if you need to show only messages, but I would expect you would show user info as well, so it should be ok.
If you want to listen for just one user messages, it's very simple and fast:
var postListRef = new Firebase(URL);
var lastPostQuery = postListRef.child("Post/User").child(userId);
And Angular/AngularFire has great support for this kind of data structures.
I am also new to Firebase, I would recommend the following structure.
Users: {
userID: {
firstName: String,
lastname: String,
userName: String,
posts: {
postID1:true,
postID2:true
}
Posts: {
postID1:{
title: String,
content: String,
date: Date,
writtenBy: userID
}
}
It allows you to get the latest posts without having to through any users. Plus you can get all the post made by any user.

Resources