(EDITED to clarify that I want to delete/drop an attribute from the database when it is deleted from an object)
I have a small Sails application with a schema-less model that allows attributes to be added and saved to the database (currently just the sails-disk file system adaptor for testing purposes).
The problem I am having is that I can't find a way to permanently delete attributes from the database.
When I delete an attribute on an object which is a model instance I also want the attribute dropped from the database. This will be useful when I move to an object database like MongoDB and have objects with dynamically created attributes.
Deleting an attribute using delete object[attributeName] and then saving with the instance method object.save() does not work, the deleted attributes still remain.
For example if I have this object:
{
"name": "Chair",
"colour": "white"
}
Let's say I want to remove the colour attribute to replace it with a material attribute like this:
{
"name": "Chair",
"material": "pine"
}
After updating the object in the database the new material attribute is added, but the deleted colour attribute is not removed.
So the end result in the database will be this:
{
"name": "Chair",
"colour": "white",
"material": "pine"
}
So this is not the outcome I am after.
Is there a way to permanently delete/drop attributes from an object in the database using Sails/Waterline?
I am sure it is too late for this but I had the same issue. What I did was just create a new object and delete the old one. If you want to do this programatically, just compare the key of the current object and the old one. The problem with this is that you must also update all related models (and collections) with the new id.
You can't by default "unset" an attribute because structured SQL databases do not work that way. You can set the attribute to NULL is the most you can do with straight SailsJS. In a structured DB like MySql, MSSQL, Oracle, PGSQL when you do not supply an attribute the DB will insert a default value (NULL or other prescribed value by the schema definition) or throw an error at you.
For NoSQL DB like Mongo they do have an "unset" option which Sails does not support out of the box. In Sails you would have to do a native query using sails.model["name"].getDatastore().manager then run the native Mongo update query using $unset to remove the attribute.
const db = sails.model["products"].getDatastore().manager;
await db.collection("products").update(
{ sku: "unknown" },
{ $unset: { quantity: "", instock: "" } });
** updateMany can also be used the same way for multiple records at the same time.
ref: https://docs.mongodb.com/manual/reference/operator/update/unset/
Otherwise document replacement (cringe) is the only other alternative as stated before.
Related
I'm facing a little difficulty finding information about how to modify objects in Firebase Realtime Database. I'm adding items into my database in real-time, so I won't know ahead of time what the object key is. As you know, the database structure looks like this:
Say I want to address a child of "testing", whose category is "social", what I do is this:
firebase.database().ref("testing").orderByChild("category").equalTo("social")
But how exactly can I then address this child so as to update this entire child (including all the fields - "category", "date", etc.) or even delete it? Thanks so much for any help!
To update (or delete) a node in Firebase you must know the complete path to that node. If you don't know the complete path, you can use a query to determine the node(s) matching a certain condition.
So in your case you'll need to execute the query, loop over the results, and update each child node in turn. In code:
let query = firebase.database().ref("testing").orderByChild("category").equalTo("Social");
query.once("value").then((results) => {
results.forEach((snapshot) => {
snapshot.ref.update({ propertyToUpdate: "new value" });
});
});
If you want to delete the matching node(s), the innermost line would be: snapshot.ref.remove().
I am trying to learn how to use MongoDB and am really confused how to do this. What I have are two collections, one which has a number of users and another collection which has a number of items. For example:
users:
{
"_id" : ObjectId("56dba03438e1a255b97e82b6"),
"name" : "john",
"age" : 25
}
items:
{
"_id" : ObjectId("56dba0db38e1a255b97e82b7"),
"name" : "pencil"
}
Now what I want to do in my app is to allow users to select an item but multiple users can select the same item. So I need to keep track of which users clicked which items. I thought about doing this using another collection which keeps track of the user id and item id (a user can only select an item once). Is this the correct approach? I created this collection:
useritems:
{
"_id" : ObjectId("56dba0db38e1a255b97e82b7"),
"userid" : "56db9fb038e1a255b97e82b5",
"itemid" : "56dba03438e1a255b97e82b6"
}
If this is the right approach, then I want to be able to click on an item in my app and for it to display a list of all the users who selected that item. How can I do this? I got as far as to display only the useritems collection documents where the itemid = itemid selected on the app...but now how would I display all of the users from the users collection based on the ones in the useritems collection?
router.get('/userlist/:id', function(req, res) {
var db = req.db;
var collection = db.get('useritems');
collection.find({'itemid' : '_id'},{},function(e,docs){
res.json(docs);
});
});
Thanks for the help, I'm really having a hard time understanding how this would work.
The idea of creating a third collection is a solution that mirrors how you would solve this problem in a relational database. With MongoDB, it often pays off to think about different patterns based on how you access your data.
In your case, I would not create another collection, but track which user has selected which item within the user document, within the item document, or within both documents. Which way you do this depends on your data access patterns.
Adding Selected Item to User Document
{
"_id": ObjectId("56dba03438e1a255b97e82b6"),
"name": "john",
"age": 25,
"selectedItemId": "56dba0db38e1a255b97e82b7"
}
If you will often want to see the item each user has selected, it makes sense to store the item inside the user document. When you retrieve a user, you would only have to do one extra call to the items collection to retrieve the item for that user. (If you decide to use Mongoose as an object-document mapper (ODM), then you can achieve this extra call by using Mongoose's populate method).
Adding User to the Item Document
{
"_id": ObjectId("56dba03438e1a255b97e82b7"),
"name": "pencil",
"selectedBy": [
"56dba0db38e1a255b97e82b4",
"56dba0db38e1a255b97e82b5",
"56dba0db38e1a255b97e82b6"
],
}
If you will often want to see which users have selected a given item, it makes sense to store an array of users inside the item document. When you retrieve an item, you would then have the IDs of the users that selected that item, which you could then retrieve from the database. (Again, if you decide to use Mongoose you can do this by using its populate method).
Adding Both Solutions
The reason why you would prefer one solution over another is that given your access pattern, you will be spared from iterating through the whole collection to get the data you need. For example, in the case were you add the array of users to an item, if you wanted to find the item a given user has selected, you would have to iterate though all the items and look in the array of user IDs until you found the user you wanted. Something similar would occur if you only stored the item ID inside a user document and suddenly needed to look at all the users for a given item. If both of these calls are made often, then it pays off having the data in both places. Indeed this "denormalises" your data and you will have to make sure that when you insert, update, and delete the data you do so in both places, but it's a far more scalable solution if you're making both types of queries often.
Embedding the Whole Item Document inside the User Document
{
"_id": ObjectId("56dba03438e1a255b97e82b6"),
"name": "john",
"age": 25,
"selectedItem": {
"name": "pencil"
}
}
Following the OP's comment, I'll address this scenario too. This is also a possible solution and can be very useful in simplifying the query needed to access the data. Just by querying the user document, you will be able to access what item he/she has selected without the extra query to a collection of items. The limitation of this approach is that if for whatever reason you want to update the name of the item from say "pencil" to "Pencil", you will have to ensure that you update it across all of the user documents, otherwise your data will be inconsistent. This gets more complicated when your embedded documents are more complex. Nonetheless, it is a widely used solution. If you're rarely updating your data, but reading it very often, especially if you are more interested in seeing the item picked by a given user, then it definitely speeds up your most frequent data access patterns.
You are right, only you need populate the userid to get all atributes of that collection. I suggest you use (if your are not) Mongoose
With mongoose:
UserItems
.find({'itemid' : '_id'})
.populate('userid')
.then( useritems => {
// here you have all users with their data for a specific item
return res.json(useritems);
});
You can add an array in the item document that keeps track of the IDs of the users who clicked that item.
This is assuming the ID is stored in a active session.
docs.user_who_clicked.push(req.user._id);
docs.save()
I wouldn't create a separate collection just for that unless you have a good reason. Just add a selectedBy to each Item document. Also, I find it simpler to just use my own unique names or IDs rather than looking things up with the internal Mongo IDs. Something like this:
var items = db.collection('items');
items.updateOne({itemname:'nuts'},{$push:{selectedBy:'johns'}});
//...
items.find({itemname:'nuts'}).toArray(function(err,items) {
console.log(items[0].selectedBy);
db.close();
});
I am new to sails.js. I want to select all record from a table. How to use .find() .
Specially how waterline will know from which table i want data ? Because we are not mentioning any table name in model. I know there is .query(). But is this possible within waterline basic create / update / find / delete method ?
Another question how to use prefix for table name in sails.js ? Like i want to use sails_product as table name.
I am new to sails.js. I want to select all record from a table. How to use .find() .
If your model name is, for example, Book, you'd select all Book records with
Book.find()
.exec(function(err, books) {
if (err) return res.serverError();
console.log(books); // 'books' is an array of the found records
})
Specially how waterline will know from which table i want data ? Because we are not mentioning any table name in model. I know there is .query(). But is this possible within waterline basic create / update / find / delete method ?
Yes, it's possible. You don't have to deal with table names and such at all with waterline, all you need is your model name. Create, update, delete all work the same way as the find example above - so ModelName.actionName().
Another question how to use prefix for table name in sails.js ? Like i want to use sails_product as table name.
By default, waterline uses the model name lowercased as the corresponding table name. You can, however, overwrite this in your model settings. For example, if you have your model defined in a file called Book.js, its contents would look like this:
module.exports = {
attributes: {
name: {
type: 'String',
required: true
},
price: {
type: 'float'
}
},
tableName: 'custom_book_table'
}
This way the actual table created in the database will be called custom_book_table, while you'll still refer to your model in find queries etc. as Book.
Here's links to both Waterline and Sails docs to get you going. In model/query related issues, I'd definitely search from Waterline docs first.
First of all excuse me since I don't know how it is called in computer since:
For each of my document types in my mongo app I want to define a structure, with every field defined with its constraints, validation patterns and, generally, roles that can view modify and delete this document.
For example: Book:
{
name: "Book",
viewRoles: ["Admin","User"],
createRoles: ["Admin"],
modifyRoles: ["Admin", "User"],
fields: [
{
id:"title",
name:"Book Title",
validation: "",
maxLength: 50,
minLength: 3,
required: true
},
{
id:"authorEmail",
name:"Email of the Author",
validation: "email",
maxLength: 50,
minLength: 3,
required: false
}
]
}
Then if I have this "schema" for all of my documents, I can have one view for creating modifying and showing this "entities".
I also want to have the ability to create new document types, modify their fields through admin panel of my application.
When I google "mongo dynamic schema", "mongo document meta design" I get useless information.
My question is how it is called -- when I want to have predefined schema of my documents and have the ability to modify it. Where I can get more information about how to design such systems?
Since you tagged this as having a Meteor connection, I'll point you to Simple Schema: https://github.com/aldeed/meteor-simple-schema/. I use it, along with the related collection2 package. I find it's a nice way to document and enforce schema design. When used with the autoform package, it also provides a way to create validated forms directly from your schema.
I think you are looking for how to model your data. The below link might be helpful:
http://docs.mongodb.org/manual/data-modeling/
I also want to have the ability to create new document types, modify
their fields through admin panel of my application.
For Administrative activities you may look into the options given in:
http://docs.mongodb.org/ecosystem/tools/administration-interfaces/
And once you are done, you might want to read this as a kick off:
https://blog.serverdensity.com/mongodb-schema-design-pitfalls/
In Mongo DB you don't create collections. You just start using them. So you can't define schemas before hand. The collection is created on the first insert you make to the collection. Just make sure to ensure Index on the collection before inserting documents into it:
db.collection.ensureIndex({keyField: 1})
So it all depends on maintaining the structure of the documents inserted to the collection rather than defining the collection.
So, with sailsJS you can just put any old data into your model and put it into the database even if the item is not defined in the attributes object.
Is there any way to prevent this happening? is seems silly to try and do an if and then throw errors because there would be too many things to stop getting through.
For example I have just ran into the problem where I have a list of users and I'm displaying them through angular ng-repeat on the frontend and when I put the data to save a user it also decides that "$$hashKey": "00I" is going with them too !
So it automatically saved to the database and when I refresh and get the data again, the "$$hashKey": "00I" is coming back, and therefore causing this error
Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by'
expression to specify unique keys.
Repeater: (key, user) in admins | filter:userSearch, Duplicate key: object:00I
This is coming from angular.
ps.
as far as i know, the server is using the default sails-disk
The default mode for Sails.js models is schemaless, which is probably not what you need. So, all you have to do is to add schema to your model:
module.exports = {
schema: true,
attributes: {
// Define all your attributes here...
}
};
After that all the values that are not described in the attributes will be automatically filtered out during the model saving.