How do I make a self-referential foreign key in Sequelize? - javascript

Below is my model definition for the leases table. As you can see, I'm getting an error because I can't reference a key on a model I haven't defined yet.
Line 56 works fine to set the key's relationship because I have already created the model. But how do I access the model on line 40 before I've created the model?
Here's what getForeignKey() does:
getForeignKey(name: string, foreignModel: any): any {
return {
type: Sequelize.UUID,
references: {
'model': foreignModel,
'key': name,
'deferrable': Sequelize.Deferrable.INITIALLY_DEFERRED,
'allowNull': true
}
};
}

OK, removing my entire answer because you're already associating the model to itself. That's what result.belongsTo(result, { foreignKey: 'renewed_from__lease_id' }) is doing. Does that not fulfil your needs?

Related

Sequelize - do I have to specify "as" in both model and query?

This seems redundant, and forces me to hard-code the same string in two spots - or stick it in a variable that has to be passed around. Either way, if I specify the "as" of a relationship in my model, why do I have to call it later with the same "as" property when querying?
My relationship:
organization.hasMany(client, { as: "Clients", foreignKey: "organization_id" });
client.belongsTo(organization, { as: "AuthOrganization", foreignKey: "organization_id" });
Query:
let data = await client.findOne({
include: [{ model: organization, as: "AuthOrganization" }]
}, { raw: true });
If I omit the same "as" property, an error is thrown telling me to put it in there. I'm new to Sequelize, but it appears to be this way because "as" can be used to identify relationships where it's ambiguous. However, seems like a reasonable default would be the value you set in the model, no?
What I really want is this, when I write a query:
let data = await client.findOne({
include: organization
}, { raw: true });
I'm only doing this to avoid the automatic underscore in the mixin function names. I couldn't stomach the fugly "addAuth_organization" function name, and I couldn't find another way around this issue, either.
I'll take that as a "yes". 😆

GraphQL Unions and Sequelize

I'm having trouble understanding how to retrieve information from a GraphQL Union. I have something in place like this:
const Profile = StudentProfile | TeacherProfile
Then in my resolver I have:
Profile: {
__resolveType(obj, context, info) {
if (obj.studentId) {
return 'StudentProfile'
} else if (obj.salaryGrade) {
return 'TeacherProfile'
}
},
},
This doesn't throw any errors, but when I run a query like this:
query {
listUsers {
id
firstName
lastName
email
password
profile {
__typename
... on StudentProfile {
studentId
}
... on TeacherProfile {
salaryGrade
}
}
}
}
This returns everything except for profile which just returns null. I'm using Sequelize to handle my database work, but my understanding of Unions was that it would simply look up the relevant type for the ID being queried and return the appropriate details in the query.
If I'm mistaken, how can I get this query to work?
edit:
My list user resolver:
const listUsers = async (root, { filter }, { models }) => {
const Op = Sequelize.Op
return models.User.findAll(
filter
? {
where: {
[Op.or]: [
{
email: filter,
},
{
firstName: filter,
},
{
lastName: filter,
},
],
},
}
: {},
)
}
User model relations (very simple and has no relation to profiles):
User.associate = function(models) {
User.belongsTo(models.UserType)
User.belongsTo(models.UserRole)
}
and my generic user resolvers:
User: {
async type(type) {
return type.getUserType()
},
async role(role) {
return role.getUserRole()
},
},
The easiest way to go about this is to utilize a single table (i.e. single table inheritance).
Create a table that includes columns for all the types. For example, it would include both student_id and salary_grade columns, even though these will be exposed as fields on separate types in your schema.
Add a "type" column that identifies each row's actual type. In practice, it's helpful to name this column __typename (more on that later).
Create a Sequelize model for your table. Again, this model will include all attributes, even if they don't apply to a specific type.
Define your GraphQL types and your interface/union type. You can provide a __resolveType method that returns the appropriate type name based on the "type" field you added. However, if you named this field __typename and populated it with the names of the GraphQL types you are exposing, you can actually skip this step!
You can use your model like normal, utilizing find methods to query your table or creating associations with it. For example, you might add a relationship like User.belongsTo(Profile) and then lazy load it: User.findAll({ include: [Profile] }).
The biggest drawback to this approach is you lose database- and model-level validation. Maybe salary_grade should never be null for a TeacherProfile but you cannot enforce this with a constraint or set the allowNull property for the attribute to false. At best, you can only rely on GraphQL's type system to enforce validation but this is not ideal.
You can take this a step further and create additional Sequelize models for each individual "type". These models would still point to the same table, but would only include attributes specific to the fields you're exposing for each type. This way, you could at least enforce "required" attributes at the model level. Then, for example, you use your Profile model for querying all profiles, but use the TeacherProfile when inserting or updating a teacher profile. This works pretty well, just be mindful that you cannot use the sync method when structuring your models like this -- you'll need to handle migrations manually. You shouldn't use sync in production anyway, so it's not a huge deal, but definitely something to be mindful of.

Ember.Object that does not implement Ember.Copyable

I have an actual quite simple situation:
A route to add a new item. In the corresponding controller I pre define a mockup model of my new item:
item: Ember.Object.create({
date: moment(),
amountTotal: '',
netto: '',
//...more properties
}),
This needs to be an Ember-Object, not a plain js-Object, because otherwise other things would break.
When I try to safe that newly created item:
actions: {
addItem: function() {
let expense = this.store.createRecord('expense', this.get('item'));
},
//....
}
I get the error
Assertion Failed: Cannot clone an Ember.Object that does not implement Ember.Copyable
So my Question is:
How can I create an Object that implements Ember.Copyable?
Or is there any way around this?
Yes, I've read the two other questions about that.
The first gives a soulution where I would initially create a record in the store. This has the usual downsides to it (already populating in lists, ..).
I've also tried all ways I could think of to get around that like
item: Ember.Copyable.create({...})
// or
let newItem = Ember.copy(this.get('item'));
let expense = this.store.createRecord('expense', newItem);
// and many more
Finally:
If there is a way to mock up a new Item (best with the definitions of the model) without creating a record, this would be the absolute best...
You can try specifying default value for all the model properties, and then simply you don't need to provide argument for createRecord method.
Like the below, models/expense.js and you can simply say this.store.createRecord('expense') this will come up with all the default values.
export default Model.extend({
name: attr('string',{ defaultValue: 'Sample Name'}),
date: attr('date',{
defaultValue(){
//You can write some code and the return the result.
//if you have included moment, you can use that.
return Date();
}
}),
amount: attr('number',{ defaultValue: 10}),
totalAmount: Ember.computed('amount',function(){
return this.get('amount')*10;
})
});
Using JSON.stringify and JSON.parse like the below,
this.store.createRecord('expense', JSON.parse(JSON.stringify(this.get('item'))))
Created twiddle for reference.

Ember.js (2.5.0) how to set nested object value

Sounds like a simple enough thing to do yet is causing me all sorts of grief.
I have a simple server model which has a few nested objects,
export default DS.Model.extend({
type: DS.attr('string'),
attributes: DS.attr(),
tasks: DS.attr()
});
I can create a new record in the route using
export default Ember.Route.extend({
model() {
return this.store.createRecord('server');
},
actions: {
create(server) {
server.save().then(() => this.transitionTo('servers'));
}
}
});
and in the related .hbs I'm setting a few properties of attributes and tasks using value=model.attributes.name from a form for example.
This all works fine. I however want to add a few more properties from the route during create such as default values.
Using server.set('attributes.size', 'large'); doesn't work as Ember doesn't know about size yet as it's a new record.
I can use setProperties but this seems to wipe out every other value
server.setProperties({
attributes: {
size: "large"
},
tasks: {
create: true
}
});
size is now correctly set, however name is now null because I didn't specify it in the setProperties...
What's the proper way to go about this? Surely I don't need to map out all the properties in setProperties? That seems wasteful and very error prone.
Something I've thought is should attributes just be its own model and have a relationship with Server? Even though this is always a 1-to-1 and 1-to-1 relationship?
I would recommend using ember-data-model-fragments addon as a solution in this case.
https://github.com/lytics/ember-data-model-fragments
Other option using a separate model for attributes and setting up a 1-to-1 relation. Both would be belongsTo, however it is depend on your database and API also, so you have to align your backend system to match with this new structure.

'Backbone Relational' model relation adds an 'id' with a collection as a value, preventing PUT and DELETE

I successfully implemented loading and showing relations with 'Backbone Relational' from an API I created. I get how things work by trial and error. I do think the docs are lacking some clarity though since it took a lot of time to figure out how things work. Especially on how to map things to the API I think the docs are lacking a bit.
Problem
Adding a bookmark works, it's the editing and deletion that don't work. The PUT becomes a POST and the DELETE simply doesn't fire at all. When I set an id to the model hardcoded it does work. So the id is missing which makes sense for the PUT becoming a POST.
The problem seems to be that the id doesn't hold an actual id, but a collection. The view where the problem occurs does not requires the BookmarkBinding, it's used somewhere else. Simply the fact that it has Bookmark as a relation makes the DELETE and PUT break.
BookmarkBinding model:
App.Model.BookmarkBinding = Backbone.RelationalModel.extend({
defaults: {
set_id: null,
bookmark_id: null
},
relations: [{
type: Backbone.HasOne,
key: 'bookmark',
relatedModel: 'App.Model.Bookmark',
reverseRelation: {
type: Backbone.HasOne,
key: 'id'
}
}],
urlRoot: 'http://api.testapi.com/api/v1/bookmark-bindings'
});
Bookmark model:
App.Model.Bookmark = Backbone.RelationalModel.extend({
defaults: {
url: 'undefined',
description: 'undefined',
visits: 0,
},
relations: [{
type: Backbone.HasMany,
key: 'termbindings',
relatedModel: 'App.Model.TermBinding',
reverseRelation: {
key: 'bookmark_id',
}
}],
urlRoot: 'http://api.testapi.com/api/v1/bookmarks'
});
From Backbonejs.org
The default sync handler maps CRUD to RESTful HTTP methods like so:
create → POST /collection
read → GET /collection[/id]
update → PUT /collection/id
delete → DELETE /collection/id
Your question suggests that you're making an HTTP PUT request, and therefore a Backbone update. If you want to make an HTTP POST, use Backbone create. The PUT request maps onto update, and requires that an id be sent in the URL, which isn't happening according to your server log. If your're creating a new object, then most server-side frameworks such as Rails / Sinatra / Zend will create an id for the object
Another possible source of error is the keys that you chose for the relations, like you suspected.
A Bookmark has many BookmarkBindings, and it seems that Backbone-relational will store them in the field that you specify in BookmarkBindings.relations.reverseRelation.key, which is currently defined as 'id'.
So the collection of related BookmarkBindings ids will to be stored on the same attribute as the Bookmark.id, creating a collision. Backbone.sync will send an undefined value to the server (which you see in your logs), because it finds a collection there instead of an integer.
First suggestion - You may not need a bidirectional relation, in which case drop it from the BookmarkBinding model.
Second suggestion - define the reverse relation on another key, so that it doesn't collide with Bookmark.id, such as BookmarkBindings.relations.reversRelation.key : 'binding_ids'
due disclosure - I've never used Backbone-relational.js, only Backbone.js.
The problem was that on editing or deleting the bookmark model, the bookmark binding model wanted to do it's work too since it is related too the bookmark from it's side. I already tried to remove the reverse relation which didn't prove to be a solution since in the other part of my application where I used the bookmark bindings things wouldn't work anymore.
Solution
I did end up removing the reverse relation (#jarede +1 for that!), but the crux was how to implement the foreign key to fetch relations from the API without a reverse relation. I ended up adding the keySource and keyDestination which made everything work out.
Sidenote
Backbone Ralational cannot handle identical foreign keys either, this gave me some problems too. This will make the lastly declared foreign key overwrite all the previous ones. This can be quite impractical since within an API it's not uncommon that model's are related to a column named id. So the idAttribute can be set with idAttribute: '_id' for example, but the foreign key has to be unique across your application.
BookmarkBinding model:
App.Model.BookmarkBinding = Backbone.RelationalModel.extend({
defaults: {
set_id: null,
bookmark_id: null
},
relations: [{
type: Backbone.HasOne,
key: 'bookmark',
keySource: 'id',
keyDestination: 'bookmark',
relatedModel: 'App.Model.Bookmark'
}],
urlRoot: 'http://api.testapi.com/api/v1/bookmark-bindings'
});

Categories