I'm working on realm to make it work offline as local db in Electron. Now I want to make join(aggregation), so I defined a relationship between two schema's but then the data is not getting synced. It would be great to get help.
Here's my schema:
const articleMetaSchema = {
name: 'articlemeta',
properties: {
_id: 'objectId?',
catalog_id: 'objectId?',
content: 'objectId?',
createdAt: 'date?',
description: 'string?',
main_image_url: 'string?',
origin_type: 'string?',
pub_date: 'date?',
publication: 'objectId?',
sub_title: 'string?',
title: 'string?',
updatedAt: 'date?',
url: 'string?'
},
primaryKey: '_id'
}
const articleSchema = {
name: 'article',
properties: {
_id: 'objectId?',
active_search: 'bool?',
article_meta: 'articlemeta?',
catalog_id: 'objectId?',
content: 'objectId?',
createdAt: 'date?',
flagged: 'bool?',
owner_id: 'objectId?',
rating: 'int?',
read: 'bool?',
status: 'string?',
status_updated_at: 'date?',
updatedAt: 'date?'
},
primaryKey: '_id'
}
config = {
schema,
path: getDBPath(),
sync: {
user: app.currentUser,
partitionValue: new ObjectID(getCatalogId()),
error: (error) => {
console.log(error.name, error.message)
}
}
}
let realm = await Realm.open(config)
// query
I want to query and get the article result and in article schema we have defined a key 'article_meta' which is and objectId. Now I want article result with all the properties with article_meta as an object fetched on the basis of (article.article_meta = articlemeta._id)
Expected result:
[{ _id: '1', catalog_id: '', content: '', createdAt: '', main_image_url: '', origin_type: '', pub_date: '', publication: '', sub_title: '', title: '', updatedAt: '', url: '', article_meta: {} }, {..}, {..}]
Edit:
As pointed out in a comment, there is a bit of ambiguity in the question as the text doesn't match the code. The code shows the parent object article having a child object articlemeta which is generally correct and how relationships are built in Realm. Accessing child objects by querying their objectId is generally unnecessary if the relationship is built in a "Realmy" way as shown in this answer.
-- Original Answer below --
Realm objects can reference 'joined' realm objects via dot notation. from the docs
Filter on Related and Embedded Object Properties
To filter a query based on a property of an embedded object or a
related object, use dot-notation as if it were in a regular, nested
object.
So for example, a Person object has a Dog object as one of it's properties and you want to get that pesons' dogs name.
Let's get a specific person by their _id
const person = realm.objectForPrimaryKey("Person", 1234)
if we then want the persons dogs name
const dogName = person.dog.dog_name
In your case you can get a specific article, and then access all of the articlemeta properties using dot notation
const article = realm.objectForPrimaryKey("article", 1234)
and then access the rest of the properties through dot notiation
const id = article._id
const catalogId = article.catalog_id
const mainImageUrl = article.article_meta.main_image_url
As you can see, a query is not needed (for this example) as the Realm objects know their own child properties and can be accessed via dot notation.
Related
Hello beautiful community. Hope you guys are doing okay.
So I'm working on this app where I have two MongoDB collections, Posts and CommentsView.
Posts collection consists of post title, post type and their comments. The comments is an array of objects that consists of how many times each comment is viewed and the id.
In the CommentsView collection, I intend to store all the comments when they are viewed. Duplicates is not a problem.
Here's how the schema looks like:
Posts Schema:
const postsSchema = new mongoose.Schema( {
postTitle: {
type: String
},
comments: [ {
totalViews: {
type: Number
},
uid: {
type: String
}
}],
postId: {
type: String
}
} );
CommentsView Schema:
const commentsViewSchema = new mongoose.Schema( {
text: {
type: String,
},
uid: {
type: String
}
} );
Suppose I have a post with two comments with uid 'A' and 'B' respectively. Whenever the comment with uid 'A' is viewed, I will create a comment in CommentsView collection. And automatically add 1 view in Posts collection's totalView field. When the same comment is viewed again, I will first add it in CommentsView collection then increment totalView field in Posts collection.
Suppose I have these documents in Comments collection:
{
text: 'Life is good',
uid: 'A'
},
{
text: 'Boom Boom',
uid: 'B'
},
{
text: 'Bam Bam',
uid: 'A'
},
So the Posts document will look like this:
{
postTile: '60 seconds to Mars',
comments: [
{
uid: 'A',
totalViews: 2,
},
{
uid: 'B',
totalViews: 1,
},
],
postId: '1s93njs'
}
I have not tried anything yet as I have no idea where to start. It seems so complicated.
How can I achieve this if I want the whole process to be automatic?
Since you are using two different schema, I recommend you to use references,
beacause if you want to add more features like number of likes and threads then this model suggested below will be useful
Don't use String type for storing Id's of the model use the type ObjectId provided by the mongoose module.
Post Model
const postsSchema = new mongoose.Schema( {
postTitle: {
type: String
},
comments: [
type:ObjectId,
ref:"model name"
],
postId: {
type: String
}
}, {timestamps:true});
Comment Model
const commentsViewSchema = new mongoose.Schema( {
text: {
type: String,
required:true
},
totalView:{
type: Number,
default:0
}
},{timestamps:true} );
now each time a post is viewed you can search for the commment id and increment the count in the document. and everything will be reflected in the post model if you populate all the comments in the array
Example
work flow
User Viewed the comment-->(getcomment_id) --> update the comment
Now all subsequent request for the post with this comment will also have the updated view because of reference.
query to update the comments
comment.findByIdAndUpdate({_id:<comment_id>}, {$inc:{totalView:1}})
other way is to have embedded do, I recommend this if you think there won't be much comments for the posts, otherwise having the above model is good.
you can further refer to these articles
rules to follow for designing mongodb schema
Hope this helped you get a good idea
I'm using node with typescript, and mongoose for database.
I have this code that takes user model from database (using mongoose) by user id, then takes the user roles attribute. The roles attribute contains array of object. Inside each object (roles) contains _id
// Check User Roles
const user = await this.userModel.findById(req.identitas.user._id)
const userRoles: Array<string> = []
for (let index in user.roles) {
userRoles.push((user.roles[index]._id))
}
When i try to check the type of what's pushed to userRoles
console.log(typeof(userRoles[0]))
It says that it has object type variable.
I then decided to try
userRoles.push({ dummy: "dummy" })
and it wouldn't let me do that. So I know for sure that the type declaration in
const userRoles: Array<sring> = []
is working properly. But, at the same time, it isn't. When I try to push _id to userRoles, it still somehow allows me to do that.
This is a problem because I'm using this userRoles array of string to compare it with another string value, using
userRoles.includes("some-string-here")
Why and how did this thing could happen?
*This is what user variable contains:
{
roles: [
{
_id: 5f93fdc**********36,
name: 'Karyawan Cabang Neutron',
...
__v: 2,
policies: [],
organization: 5f8fe9a62*****000082e3fef
}
],
units: [
{
_id: 5fc1f9******00081b2897,
name: 'Neutron Yogyakarta 2',
code: '102',
organization: 5f8fe****42a2000kj13fef,
createdAt: 2020-11-28T07:18:09.628Z,
updatedAt: 2020-11-28T07:18:09.628Z,
__v: 0
}
],
organizations: [
{
_id: 5f8c6b*****b75jg008d4a156,
...
__v: 34,
code: 'NEO',
logo: [Object],
content: [Array],
object: [Object],
config: [Array]
}
],
_id: 5fdad1a25e9f78****c42,
email: 'email#example.com',
username: 'EmiliaBestGirl',
...
isEmailVerified: true,
identities: [],
createdAt: 2020-12-17T03:33:54.484Z,
updatedAt: 2020-12-22T08:12:24.019Z,
__v: 3,
...
}
I've one collection i.e 'comments', Which contain two fields. Like below -
Comments -
{
_id: 'unique_string',
dataVar: [{
name: {
type: string
},
value: {
type: string
}
}]
}
You can assume the collection data is like below -
[
_id: 'xxxxxx',
user: 'robin',
dataVar: [{
name: 'abc',
value: 123
}, {
name: 'bcd',
value: 12345
}]
]
Now the problem is - (I'm using mongoose in nodeJS application)
- How to update and insert the data in 'dataVar' ?
- If data is no available then new document should be created.
Case 1.
If user send the post data like
{
_id: 'xxxxxx',
user: 'robin',
dataVar: [{name: 'abc', value: 12345}]
}
then after performing query, the above document (whose _id is 'xxxxxx') should be update like below -
{
_id: 'xxxxxx',
user: 'robin',
dataVar: [
{name: 'abc', value: 12345},
{name: 'bcd', value: 12345}
]
}
Case 2.
If data is not present then new document should be created.
To update record in mongodb, you can use like
comments.findOneAndUpdate({ _id: 'xxxxxx' }, { $push: { dataVar: {$each: [{name: 'abc', value: 123}, {name: 'def', value: 456}] } } })
You can use {upsert: true} option for creating field in collection if not exist.
For updating record, you have to use sub-schema inside your main schema. So that _id field is created in every object of array. So you can uniquely identify object to update inside array.
comments.findOneAndUpdate({ "dataVar._id": 'xxxxxx' }, { $set: { name: 'xyz', value: 789 } })
In this way, you can update object, which is inside array.
If you don't want to use _id, you have at-least one field which contains unique values. So that you can findOneAndUpdate that specific object inside array.
Planning to use AJV
for validating user inputs. AJV needs data model JSON Schema to validate user inputs. So, we need to derive JSON Schema from Sequelize model. Is there a way to get JSON schema from Sequelize model programatically?
A late answer, but I ended up creating sequelize-to-json-schema to solve this for our needs.
It offers more customisation in terms of which attributes you include in your schema and adding virtual attributes that might be used by your create method or similar.
Example
// assuming you have a user model with the properties
// name (string) and status (enum: real, imagined)
const schemaFactory = require('sequelize-to-json-schema');
const factory = new SchemaFactory({
customSchema: {
user: {
name: { description: "The user's name" },
status: { description: 'Was it all just a dream?' },
},
}
hrefBase: 'http://schema.example',
});
const schemaGenerator = factory.getSchemaGenerator(User);
const schema = schemaGenerator.getSchema();
// Results in
schema = {
{
title: 'User',
'$id': 'http://schema.example/user.json',
type: 'object',
'$schema': 'http://json-schema.org/draft-06/schema#',
properties: {
name: {
'$id': '/properties/fullname',
type: 'string',
examples: [],
title: 'Name',
description: "The user's name",
},
status: {
'$id': '/properties/status',
type: 'string',
examples: ['REAL', 'IMAGINED'],
enum: ['REAL', 'IMAGINED'],
title: 'Status',
description: 'Was it all just a dream?'
}
}
}
}
Note: sequelize-to-json-schema generates draft-06 schemas, to use that with AJV, their README says you'll need to do:
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
I'm using Mongoose.js to create models with schemas.
I have a list of models (many) and at times I'd like to get the attributes/keys that make up a particular model.
Is there a method to pull out the attribute schemas for any given model?
For example,
var mySchema = module.exports = new Schema({
SID: {
type: Schema.Types.ObjectId
//, required: true
},
teams: {
type: [String]
},
hats: [{
val: String,
dt: Date
}],
shields: [{
val: String,
dt: Date
}],
shoes: [{
val: String,
dt: Date
}]
}
);
Is it possible to pull out/identify the attributes of the schema [SID, hats, teams, shields, shoes]??
Yes, it is possible.
Each schema has a paths property, that looks somewhat like this (this is an example of my code):
paths: {
number: [Object],
'name.first': [Object],
'name.last': [Object],
ssn: [Object],
birthday: [Object],
'job.company': [Object],
'job.position': [Object],
'address.city': [Object],
'address.state': [Object],
'address.country': [Object],
'address.street': [Object],
'address.number': [Object],
'address.zip': [Object],
email: [Object],
phones: [Object],
tags: [Object],
createdBy: [Object],
createdAt: [Object],
updatedBy: [Object],
updatedAt: [Object],
meta: [Object],
_id: [Object],
__v: [Object]
}
You can access this through an model too. It's under Model.schema.paths.
Don't have enough rep to comment, but this also spits out a list and loops through all of the schema types.
mySchema.schema.eachPath(function(path) {
console.log(path);
});
should print out:
number
name.first
name.last
ssn
birthday
job.company
job.position
address.city
address.state
address.country
address.street
address.number
address.zip
email
phones
tags
createdBy
createdAt
updatedBy
updatedAt
meta
_id
__v
Or you could get all Attributes as an Array like this:
var props = Object.keys(mySchema.schema.paths);
My solution uses mongoose model.
Schema attributes
const schema = {
email: {
type: String,
required: 'email is required',
},
password: {
type: String,
required: 'password is required',
},
};
Schema
const FooSchema = new Schema(schema);
Model
const FooModel = model('Foo', FooSchema);
Get attributes from model:
Object.keys(FooModel.schema.tree)
Result:
[
'email',
'password',
'_id',
'__v'
]
Solution for lodash, function which picked all schema properties, excluding specified
_.mixin({ pickSchema: function (model, excluded) {
var fields = [];
model.schema.eachPath(function (path) {
_.isArray(excluded) ? excluded.indexOf(path) < 0 ? fields.push(path) : false : path === excluded ? false : fields.push(path);
});
return fields;
}
});
_.pickSchema(User, '_id'); // will return all fields except _id
_.pick(req.body, _.pickSchema(User, ['_id', 'createdAt', 'hidden'])) // all except specified properties
read more here https://gist.github.com/styopdev/95f3fed98ce3ebaedf5c
You can use Schema.prototype.obj that returns the original object passed to the schema constructor. and you can use it in a utility function to build the object you're going to save.
import Todo from './todoModel'
import { validationResult } from 'express-validator'
const buildObject = (body) => {
const data = {};
const keys = Object.keys(Todo.schema.obj);
keys.forEach(key => { if (body.hasOwnProperty(key)) data[key] = body[key] })
return data;
}
const create = async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.json(errors);
let toBeSaved = buildObject(req.body);
const todo = new Todo(toBeSaved);
const savedTodo = await todo.save();
if (savedTodo) return res.json(savedTodo);
return res.json({ 'sanitized': keys })
} catch (error) {
res.json({ error })
}
}
another way is to to not call the buildObject function and add it in two lines but you will write every key you want to save
let { title, description } = req.body;
let toBeSaved = { title, description };
Using ES6 shorthand property names
If you want to have only the attributes you added and not the add methods by the ORM that starts with '$___', you have to turn the document into object then access the attributes like this:
Object.keys(document.toObject());
The accepted answer did not work for me.
But using Mongoose 5.4.2 I was able to get the keys by doing the following:
const mySchema = new Schema({ ... });
const arrayOfKeys = Object.keys(mySchema.obj);
I'm using typescript, however. That might have been the problem.
In case you want to have all property values (including nested and populated properties), just use toObject() method:
let modelAttributes = null;
SomeModel.findById('someId').populate('child.name').exec().then((result) => {
modelAttributes = result.toObject();
console.log(modelAttributes);
});
The output would be:
{
id: 'someId',
name: 'someName',
child: {
name: 'someChildName'
}
...
}
Just insert the field name you like to get.
let fieldName = 'birthday'
console.log(mySchema.schema.paths[fieldName].instance);
Iterate over keys aka attributes
for (var key in FooModel.schema.obj) {
//do your stuff with key
}