Skip to content

Instantly share code, notes, and snippets.

@hetsch
Last active November 4, 2017 20:53
Show Gist options
  • Save hetsch/23b36e1ad01b46dcf2226e327667f3a6 to your computer and use it in GitHub Desktop.
Save hetsch/23b36e1ad01b46dcf2226e327667f3a6 to your computer and use it in GitHub Desktop.
M2M update with objectionjs
import Knex from 'knex';
import objection from 'objection';
import Promise from 'bluebird';
import util from 'util';
function initDatabase() {
const knex = Knex({
dialect: 'sqlite3',
connection: {
filename: './test.db' // ':memory:'
},
useNullAsDefault: true,
debug: false
});
objection.Model.knex(knex);
return knex;
}
class BaseModel extends objection.Model {
static get relations() {
return [];
}
static all() {
let query = this.query();
if (this.relations.length > 0) {
query = query.eager(this.relations);
}
return query;
}
static find(id) {
let query = this.query().findById(parseInt(id, 10));
if (this.relations.length > 0) {
query = query.eager(this.relations);
}
return query;
}
static create(data, upsertGraphOptions) {
return objection.transaction(this.knex(), trx => {
return this.query(trx).insertGraph(
data,
upsertGraphOptions || {
relate: true,
unrelate: true
}
);
});
}
static update(data, upsertGraphOptions) {
return objection.transaction(this.knex(), trx => {
return this.query(trx)
.where('id', data.id)
.upsertGraph(
data,
upsertGraphOptions || {
relate: true,
unrelate: true
}
);
});
}
static delete(id) {
return objection.transaction(this.knex(), trx => {
return this.query(trx)
.where('id', parseInt(id, 10))
.delete();
});
}
castBooleanFields(json) {
const jsonSchema = this.constructor.jsonSchema.properties;
// const booleanFields = Object.keys(json).filter(key => schema.hasOwnProperty(key) && schema[key].type === "boolean");
Object.entries(json).forEach(([key, value]) => {
if (
Object.prototype.hasOwnProperty.call(jsonSchema, key) &&
jsonSchema[key].type === 'boolean'
) {
json[key] = Boolean(value);
}
});
return json;
}
// SQLITE stores booleans as integers (0 or 1)
// Cast them to Booleans, otherwise tcomb-form
// throws validation errors that integers are no
// booleans.
// See: https://github.com/Vincit/objection.js/issues/204
$parseDatabaseJson(json) {
let newJson = super.$parseDatabaseJson(json);
newJson = this.castBooleanFields(newJson);
return newJson;
}
}
class ProductAttribute extends BaseModel {
static get tableName() {
return 'ProductAttribute';
}
static get jsonSchema() {
return {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
slug: { type: 'string' },
dataType: { type: 'string' },
/* extra field on the m2m table */
isOptional: { type: 'boolean' }
},
required: ['name', 'slug']
};
}
}
class ProductClass extends BaseModel {
static get relations() {
return '[productAttributes, variantAttributes]';
}
static get tableName() {
return 'ProductClass';
}
static get jsonSchema() {
return {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
slug: { type: 'string' },
hasVariants: { type: 'boolean' },
productAttributes: {
type: 'array',
items: ProductAttribute.jsonSchema
},
variantAttributes: {
type: 'array',
items: ProductAttribute.jsonSchema
}
},
required: ['name']
};
}
static get relationMappings() {
return {
productAttributes: {
relation: objection.Model.ManyToManyRelation,
modelClass: ProductAttribute,
join: {
from: 'ProductClass.id',
through: {
from: 'ProductClass_ProductAttribute.productClassId',
to: 'ProductClass_ProductAttribute.productAttributeId',
extra: ['isOptional', 'order']
},
to: 'ProductAttribute.id'
}
},
variantAttributes: {
relation: objection.Model.ManyToManyRelation,
modelClass: ProductAttribute,
join: {
from: 'ProductClass.id',
through: {
from: 'ProductClass_VariantAttribute.productClassId',
to: 'ProductClass_VariantAttribute.productAttributeId',
extra: ['isOptional', 'order']
},
to: 'ProductAttribute.id'
}
}
};
}
}
function* schema(knex) {
yield knex.schema.dropTableIfExists('ProductAttribute');
yield knex.schema.dropTableIfExists('ProductClass');
yield knex.schema.dropTableIfExists('ProductClass_ProductAttribute');
yield knex.schema.dropTableIfExists('ProductClass_VariantAttribute');
yield knex.schema.createTable('ProductAttribute', table => {
table.bigincrements('id').primary();
table.string('name');
table.string('slug');
table.boolean('isOptional');
table.string('dataType');
});
yield knex.schema.createTable('ProductClass', table => {
table.bigincrements('id').primary();
table.string('name');
table.string('slug');
table.boolean('hasVariants');
});
yield knex.schema.createTable('ProductClass_ProductAttribute', table => {
table.increments('id').primary();
table
.biginteger('productClassId')
.unsigned()
.references('id')
.inTable('ProductClass');
// .onDelete("CASCADE");
// .index();
table
.integer('productAttributeId')
.unsigned()
.references('id')
.inTable('ProductAttribute');
// .onDelete("CASCADE");
// .index();
table.boolean('isOptional');
table.boolean('order');
});
yield knex.schema.createTable('ProductClass_VariantAttribute', table => {
table.increments('id').primary();
table
.biginteger('productClassId')
.unsigned()
.references('id')
.inTable('ProductClass');
// .onDelete('CASCADE');
// .index();
table
.integer('productAttributeId')
.unsigned()
.references('id')
.inTable('ProductAttribute');
// .onDelete('CASCADE');
// .index();
table.boolean('isOptional');
table.boolean('order');
});
}
function* fixtures(knex) {
yield knex
.insert({
name: 'attribute_name_1',
slug: 'attribute_slug_1',
dataType: 'string'
})
.into('ProductAttribute');
yield knex
.insert({
name: 'attribute_name_2',
slug: 'attribute_slug_2',
dataType: 'number'
})
.into('ProductAttribute');
yield knex
.insert({
name: 'productclass_name_1',
slug: 'productclass_slug_1',
hasVariants: true
})
.into('ProductClass');
yield knex
.insert({
productClassId: 1,
productAttributeId: 1,
isOptional: true,
order: 1
})
.into('ProductClass_ProductAttribute');
yield knex
.insert({
productClassId: 1,
productAttributeId: 2,
isOptional: false,
order: 2
})
.into('ProductClass_VariantAttribute');
}
(async () => {
const knex = initDatabase();
// DB setup
await Promise.each(
[Promise.coroutine(schema), Promise.coroutine(fixtures)],
func => func(knex)
);
const dumpData = Promise.coroutine(function* dump() {
const line = '\n\n*****************\n%s:\n\n%s\n*****************\n\n';
console.info(
line,
'ProductClasses',
util.inspect(yield ProductClass.all())
);
console.info(
line,
'ProductAttributes',
util.inspect(yield ProductAttribute.all())
);
console.info(
line,
'ProductClass_ProductAttribute',
util.inspect(yield knex.select().from('ProductClass_ProductAttribute'))
);
console.info(
line,
'ProductClass_VariantAttribute',
util.inspect(yield knex.select().from('ProductClass_VariantAttribute'))
);
// yield Promise.resolve();
});
console.log('\nINITIAL DATABASE STATE\n');
await dumpData();
const productClass = await ProductClass.update({
id: 1,
name: 'productclass_name_1',
slug: 'productclass_slug_1',
hasVariants: true,
productAttributes: [
// See: https://github.com/Vincit/objection.js/issues/480
/* OLD RELATION: This is the ProductAttribute that was created in the fixtures */
{
isOptional: true,
id: 1 // this is the pk of the related ProductAttribute?
},
/* NEW RELATION: This should relate the current ProductClass with an EXISTING ProductAttribute
with ProductAttribute::id = 2 */
{
isOptional: false,
id: 2 // this is the pk of the related ProductAttribute?
},
/* NEW RELATION: Should create a NEW ProductAtribute and realte it to the current ProductClass */
{
name: 'attribute_name_3',
slug: 'slug_name_3',
dataType: 'float',
isOptional: true
}
],
variantAttributes: [
/* OLD RELATION: This is the ProductAttribute that was created in the fixtures */
{
isOptional: false,
id: 2 // this is the pk of the related ProductAttribute?
}
]
});
console.log('\nUPDATED PRODUCT CLASS\n');
// const productClass = await ProductClass.find(1);
console.log('\n\n%s\n\n', util.inspect(productClass));
console.log(productClass.productAttributes[0]);
console.log('\nAFTER UPDATE\n');
await dumpData();
})();
// Run it with: node --experimental-modules test.mjs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment