Realm and react Native Enable Updating Objects With Primary Keys for Nested Objects - react-native

How Can I Enable Update objects with Primary Key Globally
For the first instance I create the realm object and pass true for update with primary key, it works for root level object but the child objects throw an error on the second run
Error: Attempting to create an object of type 'Category' with an existing primary key value
Is there a way to set update via primary key globally
This is my structure
class Response extends Realm.Object {}
Response.schema = {
name: 'Response',
primaryKey: 'id',
properties: {
id:'int', // primary key
categories: {type: 'list', objectType: 'CategoryList'},
}
};
class CategoryList extends Realm.Object {}
CategoryList.schema = {
name: 'CategoryList',
properties: {
category: {type: 'Category'},
}
};
Category extends Realm.Object {}
Category.schema = {
primaryKey:'categoryId',
name: 'Category',
properties: {
categoryId:'string',
name:'string',
itemsCount:'int',
createdDate:'string'
}
};
And this is how I persist to the db
let Response = realm.objects('Response');
let CategoryList = realm.objects('CategoryList');
realm.write(()=>{
realm.create('Response', {id:1,name: 'Response',categories: CategoryList},true);
let Categories = Response[0].categories;
for (let i=0;i<responseData.categories.length;i++){
Categories.push(responseData.categories[i]);
}
});

My guess would be you are getting this error from this line:
Categories.push(responseData.categories[i]);
push implicitly tries to create a new category rather than updating/reusing existing categories. Try changing this to:
Categories.push(realm.create('Catetory', responseData.categories[i], true));
This will reuse existing categories if they exist, or create them if they don't.

Related

setting up a one to many relationship for a self referencing table

I have a Project entity with a non-autogenerated id field and a successor field. This successor is the project that follows next. But maybe there is no following project so this might be null.
#Entity()
export class Project extends BaseEntity {
#PrimaryColumn({ unique: true })
public id: string;
#OneToMany(() => Project, project => project.id, { nullable: true })
public successorId?: string;
}
When creating a new project via
public createProject(id: string, successorId?: string): Promise<Project> {
const project: Project = new Project();
project.id = id;
project.successorId = successorId;
return project.save();
}
there are multiple cases I have to take care for.
Passing in an id that already exists:
This will not throw an error. It just overrides the existing entity.
Passing in undefined for the successorId:
The code works fine then but it does not create a successorId column with null then. The column simply does not exist in the database.
Passing in the same id for id and successorId (this should be possible):
TypeORM throws the error
TypeError: Cannot read property 'joinColumns' of undefined
Passing in a successorId of another existing project:
I'm getting the same error as above
Passing in a successorId of a project that doesn't exist:
I'm getting the same error as above
So how can I fix that? I think my entity design seems to be wrong. Basically it should be
One project might have one successor
A project can be the successor of many projects
Would be awesome if someone could help!
Update
I also tried this
#OneToMany(() => Project, project => project.successorId, { nullable: true })
#Column()
public successorId?: string;
but whenever I want to call the createProject method I'm getting this error
QueryFailedError: null value in column "successorId" violates not-null
constraint
and this
#OneToMany(() => Project, project => project.successorId, { nullable: true })
public successorId?: string;
but then I'm getting this error
TypeError: relatedEntities.forEach is not a function
Please try this solution
#Entity()
export class Project extends BaseEntity {
#PrimaryColumn({ unique: true })
public id: string;
#Column({ nullable: true })
public successorId?: string;
#ManyToOne(() => Project, project => project.id)
#JoinColumn({ name: "successorId" })
public successor?: Project;
}
public successor?: Project; - property is used for building relation between entities (same entity in this case). Related entity must be specified as a property type, because TypeORM uses this type to determine target entity and build relation metadata. You can read more about TypeORM relations here
public successorId?: string; - property is just an "extracted" join column. When you use ManyToOne relation, TypeORM automatically creates a column in the database named propertyName + referencedColumnName (successorId in this case). But you cannot use this column in your code, because it is defined only in table and not in your class. And if you need this column defined in class (for further saving or displaying) you can create a property and mark it with a #Column decorator. Property name must be the same as the join column name in the table. Described in more detail here
Creating an entity with the same id just overrides the existing one
this is an expected behaviour. When you trying to save entity with an existing Id, TypeORM recognizes this as an update, not a create
You can try this
export class RolesPermission {
#PrimaryGeneratedColumn('uuid')
#PrimaryColumn({ type: 'varchar', length: 36, default: 'UUID()' })
entityId?: string;
#Column({ unique: true })
name: string;
#OneToMany(() => RolesPermission, (rolePermission) => rolePermission.parent)
rolePermissions?: RolesPermission[];
#ManyToOne(() => RolesPermission, (rolePermission) => rolePermission.rolePermissions, { nullable: true, createForeignKeyConstraints: false })
parent?: RolesPermission;
#Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
createdAt?: Date;
#Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' })
updatedAt?: Date;}

Why vuex-orm returns null in relationship field?

I followed the guide of Defining Relationships in Vuex ORM.
I did everything like the guide, so why I get null value in category field in Article Model?
codesandbox
This is how I defining my models (category on article is: hasOne/belongTo):
export class Category extends Model {
static entity = "categories";
static primaryKey = "_id";
static fields() {
return {
_id: this.attr(null),
name: this.attr("")
};
}
}
export class Article extends Model {
static entity = "articles";
static primaryKey = "_id";
static fields() {
return {
_id: this.attr(null),
name: this.attr(""),
category: this.hasOne(Category, "_id")
};
}
}
Config Vuex-orm with vuex in main.js:
import VuexORM from "#vuex-orm/core";
import { Article, Category } from "./models";
Vue.use(Vuex);
const database = new VuexORM.Database();
const Articles = {
namespaced: true,
actions: {
test() {
console.log(this);
}
}
};
const Categories = {
namespaced: true,
actions: {
test() {
console.log(this);
}
}
};
database.register(Article, Articles);
database.register(Category, Categories);
const store = new Vuex.Store({
plugins: [VuexORM.install(database)]
});
Inside app component I have the data that I insert to vuex and get the values like so:
const articleData = [
{
_id: "6ce9bae00000000000000000",
name: "article-1",
category: "5ce9acd00000000000000000"
}
];
const categoryData = [
{ _id: "5ce9acd00000000000000000", name: "category-1" }
];
const x = await Article.insertOrUpdate({ data: articleData });
const y = await Category.insertOrUpdate({ data: categoryData });
const a = Article.query()
.with("category")
.first();
console.log({ a });
console.log({ type: a.category });
console.log("why a.category is null???");
Firstly, the relationship between an article and a category is likely to be a one to many relationship however the issue with your code is the way you are defining the model and inserting data.
In your example you are attempting to provide a foreign key on the relationship target (I am an article and I know the category id I belong to). This kind of relationship is a one-to-one inverse relationship in Vuex-ORM.
In order to get your expected behavior, change your Article model as follows:
export class Article extends Model {
static entity = "articles";
static primaryKey = "_id";
static fields() {
return {
_id: this.attr(null),
name: this.attr(""),
category_id: this.attr(""),
category: this.belongsTo(Category, "category_id")
};
}
}
Note that you are explicitly defining a category_id as a separate field on the Article model rather than pointing to the id field on the Category model. This is the same pattern in all Vuex-ORM relationships. The category field stores the resolved relationship target object, so you destination category object. This is filled in by Vuex-ORM after you query using .with(Category) and should not be included in the data you insert.
Although it wouldn't make much sense, to fix your original example and use a standard one-to-one define your model as follows:
export class Article extends Model {
static entity = "articles";
static primaryKey = "_id";
static fields() {
return {
_id: this.attr(null),
name: this.attr(""),
category: this.hasOne(Category, "article_id")
};
}
}
export class Category extends Model {
static entity = "categories";
static primaryKey = "_id";
static fields() {
return {
_id: this.attr(null),
name: this.attr(""),
article_id: this.attr(null),
};
}
}
And provide an article_id in the data you pass to Article.insertOrUpdate.

Ember.js - Accessing nested data via serializer

What is the best approach for accessing a single nested record in Ember?
The JSON response which we are trying to manipulate looks gets returned as the following: (the attribute being targeted is the tradeIdentifier property)
trade:
tradeIdentifier:"83f3f561-62af-11e7-958b-028c04d7e8f9"
tradeName:"Plumber"
userEmail:"test#gmail.com"
The project-user model looks partially like:
email: attr('string'),
trade:attr(),
tradeId: attr(),
The project-user serializer looks partially like:
export default UndefinedOmitted.extend(EmbeddedRecordsMixin, {
primaryKey: 'userRoleId',
attrs: {
'email': { key: 'userEmail' },
'trade': { key: 'trade' },
'tradeId': { key: 'tradeIdentifier' },
},
});
The trade attr here is a placeholder to make sure that the data was accessible.
I would like to be able to access the tradeIdentifier without having to do the following in the component:
const trade = get(formRole, 'trade');
if (trade) {
set(formProps, 'tradeId', trade.tradeIdentifier);
}
Have tested creating a trade-id transform (referenced via tradeId: attr('trade-id')), however to no avail.
export default Transform.extend({
deserialize(val) {
const trade = val;
const tradeId = val.tradeIdentifier;
return tradeId;
},
serialize(val) {
return val;
},
});
Can anyone suggest where I'm going wrong?
A transform seems a bit overkill for what I'm trying to achieve here, however it does the job. Managed to get it working by modifying the following:
In serializers/project-user.js:
'tradeId': { key: 'trade' },
Note that this references the property in the payload to transform, not the property being targeted (which was my mistake).
In models/project-user.js:
tradeId: attr('trade-id'),
Attribute references the transform.
In transform/trade-id.js:
export default Transform.extend({
deserialize(val) {
let tradeId = val
if (tradeId) {
tradeId = val.tradeIdentifier;
}
return tradeId;
},
serialize(val) {
return val;
},
});
If there's a simpler solution outside of transforms, I would still be open to suggestions.

Ember: Must include an 'id' in an object passed to 'push'

Currently experiencing the following error: You must include an 'id' for failed-shotlist in an object passed to 'push'. This is in a code base I have inherited mid-development and I am fairly new to Ember.
From what I understand, this occurs when the backend does not respond with an ID. The server payload looks like the following (returning an alert object with an embedded failedShotlist record):
alertAuthor: "Test name"
alertDate:"2018-06-28T16:25:21+12:00"
alertIdentifier:"456e15c7-7a8b-11e8-84a8-06f4aef780e3"
alertType:"failedShotlist"
email:"test#gmail.com"
failedShotlist:
projectIdentifier:"79050dfb-5faf-11e8-84a8-06f4aef780e3"
projectName:"8888 st"
projectRoleENUM:"bp"
projectRoleName:"Building Participant"
shotlistDescription:"Framing"
shotlistIdentifier:"79d52773-5faf-11e8-84a8-06f4aef780e3"
inviteIdentifier:null
profileId:"c4e02bee-3d26-11e8-84a8-06f4aef780e3"
shotlistIdentifier:"79d52773-5faf-11e8-84a8-06f4aef780e3"
Since the backend doesn't respond with an ID attr, the primary key needs to be transformed using a serializer's 'primaryKey' property:
serializers/alert.js
export default ApplicationSerializer.extend(EmbeddedRecordsMixin, {
primaryKey: 'alertIdentifier',
attrs: {
'invite': { deserialize: 'records' },
'failedShotlist': { deserialize: 'records' },
},
});
I couldn't find any mention of this, but I assume that embedded records are further serialized by their own serializers. The existing one is as follows:
serializers/failedShotlist.js
export default ApplicationSerializer.extend({
attrs: {
'shotlistId': { key: 'shotlistIdentifier' },
'projectId': { key: 'projectIdentifier' },
},
});
Since the ID's for the failedShotlist object also need to be transformed, I have updated this to include the primaryKey prop:
serializers/failedShotlist.js
export default ApplicationSerializer.extend({
primaryKey: 'shotlistIdentifier',
attrs: {
'shotlistId': { key: 'shotlistIdentifier' },
'projectId': { key: 'projectIdentifier' },
},
});
Unfortunately, this results in the same error I originally encountered. Any ideas as to how this might be resolved?
Something I had overlooked was that the source files for the adapter and the serializer weren't following the naming convention of the rest of the codebase.
Where the serializer was called failedShotlist.js, the model related to it was called failed-shotlist.js.
Renaming the serializer file to failed-shotlist.js allowed my existing code to work:
export default ApplicationSerializer.extend({
primaryKey: 'shotlistIdentifier'
}

RealmJS:How to write sorted fetch query where inner object is Key-Value?

Product has many details like: productName, manufactured date, manufacturer name, product type etc. But I need to support flexibility of having no defined schema for product details. To achieve this below is the Realm-JS schema.
import Realm from 'realm';
export default class ProductModel extends Realm.Object { }
ProductModel.schema = {
name: 'ProductModel',
properties: {
productAttributes: { type: 'list', objectType: 'ProductDetailsModel' },
ID: { type: 'string', optional: true },
}
};
import Realm from 'realm';
export default class ProductDetailsModel extends Realm.Object { }
ProductDetailsModel.schema = {
name: 'ProductDetailsModel',
properties: {
attributeName: { type: 'string', optional: true },
attributeValue: { type: 'string', optional: true },
}
};
This flexibility is yielding in complexity of writing sort query. I need to be able to sort based on Product Attribute values. Let us say 'Product Name' is on attribute of Product, I need to fetch Product sorted by 'Product Name' in 'A-Z' or reverse order.
var productList = realm.objects('ProductModel').sorted(<Some query string>)
Please help me to write query to fetch the ProductModel from DB based on some values(productName or manufacturer name etc..) sorted order?
P.S. Other option which I am aware is get the realm objects and sort using Array.sort.
Update 1:
When I try to do sort with below query:
let item = this.realm.objects('ProductModel').filtered("productAttributes.attributeName = " + "\"" + "serialNo" + "\"")
.sorted('productAttributes.attributeValue', false);
I am getting below error
Cannot sort on key path 'productAttributes.attributeValue': property 'ProductModel.productAttributes' is of unsupported type 'array'.