Setting foreignKey of Ember Data model - ember-data

It looks like during Revision 3 of Ember Data 1, you could set the foreign key:
App.Model = DS.Model.extend({
namingConvention: {
// LOUD NAMING CONVENTION
// Changes fooKey to FOOKEY
keyToJSONKey: function(key) {
return key.toUpperCase();
},
// Determines the name of foreign keys in
// belongsTo relationships
foreignKey: function(key) {
return key.toUpperCase()+"_ID";
}
}
});
That does not seem to work now (currently Revision 7). How do you set the foreign key?

You may be looking for keyForBelongsTo. See http://emberjs.com/api/data/classes/DS.RESTSerializer.html
App.ApplicationSerializer = DS.RESTSerializer.extend({
keyForBelongsTo: function(type, name) {
return this.keyForAttributeName(type, name) + "_id";
},
});

Related

Sequelize: Avoid duplicates when create with manyToMany associations

I'm developing a small e-commerce to sell cinema tickets. I'm using Sequelize to design all models and their associations. The issue I'm facing is related with the route defined to create the final order:
Order.create({
userId: req.body.userId, // Number
sessionId: req.body.sessionId, // Number
seats: req.body.seats, // Array of objects
offsiteProducts: req.body.offsiteProducts // Array of objects
},
{
include: [
{
model: Seat,
attributes: ['area', 'number', 'roomId']
},
{
model: OffsiteProduct,
attributes: ['name', 'unitPrice']
}
]
}).then(order => {
res.json(order);
})
The relation between models is as follows:
User.hasMany(Order);
Order.belongsTo(User);
Session.hasMany(Order);
Order.belongsTo(Session);
Seat.belongsToMany(Order, { through: "reserved_seats" });
Order.belongsToMany(Seat, { through: "reserved_seats" });
OffsiteProduct.belongsToMany(Order, { through: ReservedOffsiteProduct });
Order.belongsToMany(OffsiteProduct, { through: ReservedOffsiteProduct });
For the "one-to-many" relationships, passing a foreign key is enough for Sequelize to associate models properly.
But for "many-to-many" associations ("belongsToMany" in Sequelize) it would duplicate the data entered for seats and offsite products and create it both as part of the order and as a new independent seat and offsite product respectively.
How can I avoid this behaviour and include the arrays of seats and offsite products only inside the final order? Thanks.
One way to do this is to execute everything in a transaction. That way, it will be atomic, i.e. "all or nothing".
Sequelize provides some methods for many-to-many associations. In this case, a separate call could be made for each junction table.
With this in mind, the following should accomplish the inserts without any duplicates:
let t = await sequelize.transaction()
try {
let order = await Order.create({
userId: req.body.userId,
sessionId: req.body.sessionId
}, {
transaction: t
})
let seats = await Seat.findAll({
where: {
id: {
[Op.in]: req.body.seatIds
}
}
})
let offsiteProducts = await OffsiteProducts.findAll({
where: {
id: {
[Op.in]: req.body.offsiteProductIds
}
}
})
await order.addSeats(seats, { transaction: t })
await order.addOffsiteProducts(offsiteProducts, { transaction: t })
await t.commit()
} catch (err) {
if (t) {
await t.rollback()
}
}
Alternatively, if the primary keys for the rows in the OffsiteProducts and Seats tables are already known, the above could be shortened to:
let t = await sequelize.transaction()
try {
let order = await Order.create({
userId: req.body.userId,
sessionId: req.body.sessionId
}, {
transaction: t
})
await order.addSeats(req.body.seatIds, { transaction: t })
await order.addOffsiteProducts(req.body.offsiteProductIds, { transaction: t })
await t.commit()
} catch (err) {
if (t) {
await t.rollback()
}
}
There's a bit more explanation for passing arrays of primary keys to the .addSeats and .addOffsiteProducts methods in the docs here.

Sequelize foreign key reference composite primary key

i'm trying to create with sequelize (postgre) 'Ingredient' table, with columns normalizedName/userId where normalizedName is unique per userId.
The second table is 'IngredientQuantity', with columns ingredientId/userId/quantity.
I tried to set in 'Ingredient' normalizedName and userId as primaryKey, and to foreign key this composite PK from 'IngredientQuantity' table with ingredientId, but i saw that was impossible with sequelize, only normalizedName is used for reference in foreign key.
Whats is the best approach to do that ? I thought about id auto increment, but all id are shared among all users. For example user1 create his first ingredient with id = 1, when user2 create his first ingredient he will have id = 2. So. i don't know if it's good idea, if all users have lot of ingredients i should use bigint etc..and if they delete/add/delete/add id will grow up.
Ingredient table
module.exports = (sequelize, DataTypes) => {
var Ingredient = sequelize.define('ingredient', {
name: DataTypes.STRING,
normalizedName: { type: DataTypes.STRING, primaryKey: true },
userId: { type: DataTypes.INTEGER, primaryKey: true }
}, {
freezeTableName: true
});
Ingredient.associate = function (models) {
models.ingredient.hasMany(models.ingredientQuantity);
models.ingredient.belongsTo(models.user, {
onDelete: "CASCADE",
foreignKey: {
allowNull: false
}
});
};
return Ingredient;
};
IngredientQuantity table
module.exports = (sequelize, DataTypes) => {
var IngredientQuantity = sequelize.define('ingredientQuantity', {
quantity: DataTypes.FLOAT,
}, {
freezeTableName: true
});
IngredientQuantity.associate = function (models) {
models.ingredientQuantity.belongsTo(models.ingredient);
models.ingredientQuantity.belongsTo(models.user, {
onDelete: "CASCADE",
foreignKey: {
allowNull: false
}
});
};
return IngredientQuantity;
};
Whats is the best approach if i consider lot of data with lot of users ? Is there an other solution ? Thanks
It's totally normal to use SERIAL as autoincremented integer surrogate PK. Also you can use UUID as autogenerated PKs (in such case you should set default value as uuid_generate_v4()) if you somehow afraid that integer value range will not be enough.
Because it's a service field there is no need it to be unique only for a certain user. Usually you shouldn't rely on a PK value.

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.

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

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.

Waterline ORM equivalent of insert on duplicate key update

I have a table user_address and it has some fields like
attributes: {
user_id: 'integer',
address: 'string' //etc.
}
currently I'm doing this to insert a new record, but if one exists for this user, update it:
UserAddress
.query(
'INSERT INTO user_address (user_id, address) VALUES (?, ?) ' +
'ON DUPLICATE KEY UPDATE address=VALUES(address);',
params,
function(err) {
//error handling logic if err exists
}
Is there any way to use the Waterline ORM instead of straight SQL queries to achieve the same thing? I don't want to do two queries because it's inefficient and hard to maintain.
The answer above is less than ideal. It also has the method as part of the attributes for the model, which is not correct behavior.
Here is what the ideal native solution looks like that returns a promise just like any other waterline model function would:
module.exports = {
attributes: {
user_id: 'integer',
address: 'string'
},
updateOrCreate: function (user_id, address) {
return UserAddress.findOne().where({user_id: user_id}).then(function (ua) {
if (ua) {
return UserAddress.update({user_id: user_id}, {address: address});
} else {
// UserAddress does not exist. Create.
return UserAddress.create({user_id: user_id, address: address});
}
});
}
}
Then you can just use it like:
UserAddress.updateOrCreate(id, address).then(function(ua) {
// ... success logic here
}).catch(function(e) {
// ... error handling here
});
Make a custom model method that does what you want using Waterline queries isntead of raw SQL. You will be doing two queries, but with Waterline syntax.
Example below (if you don't know about deferred objects then just use callback syntax, but the logic is the same):
var Q = require('q');
module.exports = {
attributes: {
user_id: 'integer',
address: 'string',
updateOrCreate: function (user_id, address) {
var deferred = Q.defer();
UserAddress.findOne().where({user_id: user_id}).then(function (ua) {
if (ua) {
// UserAddress exists. Update.
ua.address = address;
ua.save(function (err) {deferred.resolve();});
} else {
// UserAddress does not exist. Create.
UserAddress.create({user_id: user_id, address: address}).done(function (e, ua) {deferred.resolve();});
}
}).fail(function (err) {deferred.reject()});
return deferred.promise;
}
};
#Eugene's answer is good but it will always run 2 operations: findOne + update or create. I believe we can optimize it further because if the record exists we just need to run update. Example:
module.exports = {
attributes: {
user_id: 'integer',
address: 'string'
},
updateOrCreate: function (user_id, address) {
return UserAddress.update({user_id: user_id}, {address: address})
.then(function(ua){
if(ua.length === 0){
// No records updated, UserAddress does not exist. Create.
return UserAddress.create({user_id: user_id, address: address});
}
});
}
}
BTW, there is an open request to implement .updateOrCreate in waterline: #790