Define models' relations in models' files - sequelize.js - javascript

I saw a few questions with similar question/problem, but they are based on older versions of sequelize.js and unfortunately these answers cannot be applied to v.4.
I define two models: User and Tag. User can have multiple tags, tags can belong to one user (1:m relation). I have two separate files which define my models:
User.js file:
import Tag from './Tag';
const userFields = { /* my fields definitions... */ };
const User = sequelize.define('user', userFields);
User.hasMany(Tag, {
foreignKey: 'user_id',
constraints: true,
});
export default User;
Tag.js file:
import User from './User';
const tagFields = { /* tag fields definitions... */ };
const Tag = sequelize.define('tag', tagFields);
Tag.belongsTo(User, {
foreignKey: 'user_id',
constraints: false
});
export default Tag;
Obviously it doesn't work beacause these two files try to import each other and one of them see's other as undefined and I see error that User called with something that's not a subclass of Sequelize.Model.
Docs only give examples similar to the one below, where models are defined in one file:
const Player = this.sequelize.define('player', {/* attributes */});
const Team = this.sequelize.define('team', {/* attributes */});
Player.belongsTo(Team); // Will add a teamId attribute to Player to hold the primary key value for Team
So, my question is: how can I define associations between models in models' files? I know that one solution is to create a function (the one which will create associations) in a separate file and call it after models are defined. But I want to keep these associations with models' definitions. How can I do that in sequelize#v.4?

The difference in the library version does not matter. What actually matters is where you put the code responsible for declaring the associations. I know it is not very pretty, but what you have to do is to declare your associations outside both of the two files declaring the schemas.
This answers explains it and shows the example used by the library https://stackoverflow.com/a/36877672/614277

Related

Using sequelize-auto generates init-models.js. Is my app utilizing this for associations?

I'm working on an express rest api using sequelize. I successfully generated my models using sequelize-auto (which created init-models.js) and haven't thought about it since. My tables have associations and they show up in the init-models.js file but I can't seem to use query associating to utilize the associations.
Here's the init-models.js that sequelize-auto generated:
function initModels(sequelize) {
...
product.belongsTo(manufacturer, { as: "manufacturer", foreignKey: "manufacturer_id" });
manufacturer.hasMany(product, { as: "products", foreignKey: "manufacturer_id"});
return { product, manufacturer }
}
module.exports = initModels;
module.exports.initModels = initModels;
module.exports.default = initModels
So my question is.. is this module getting loaded when my server starts and initializes everything? If not, could I just move my associations directly to the model init function as suggested in the documentation (I think I'd rather do this)?
You'll get a better idea how to register and initialize models and their associations if you look at my other answer here.
As for shown generated code I suppose it would be better to call initModels inside the entry-point or a special DB module right after you have a Sequelize instance initialized to pass it to the function. And if you import this generated module then by default you will only have access to initModels to be able to call it wherever you wish to initialize models and to get them returned.

Organizing a big helper file full of exports

Apologies if this question is too open-ended.
I have a big "helper" file filled with useful functions. They're all exported functions, so
exports.getFirstNameLastName = nameString => { ... }
This file is getting to be pretty big and has enough functions in it that I feel that it can be divided up into smaller, categorized files (e.g. parsingHelper.js, webHelper.js, etc).
I'm coming from a heavily object oriented software dev background. In something like C# I'd create a static class file for each of these categories (parsing, web, etc), then simply do the one import (using ParserHelpers;), then do ParserHelpers.GetFirstNameLastName(...); instead of importing each and every function I end up using.
My question is, is there a way to organize all my helper functions in a similar manner? I'm trying to reduce the number of individually exported/imported items and trying to split this big file into smaller files.
Would rather not use additional packages if I don't have to (using ES6).
Yes! There is a way to do something like that!
// file users.js
exports.users = {
getFirstName: () => {},
getLastName: () => {},
// Add as many as you want
}
// file categories.js
exports.categories = {
getCategoryById: () => {},
getCategoryName: () => {}
}
// You can use them separately or you can create another file an union all them:
// file helpers.js
import users from './users'
import categories from './categories'
exports helpers = {
users,
categories
}
// This way you can do something like:
import helpers from './helpers.js'
helper.users.getFirstName()

js get filename of file containing class definition

I would need to get the filename of an imported class:
fileA.js
export default class User {
}
fileB.js
import User from './fileA'
function getClassFilename(constructor) {
// do something like __filename, but to get the filename where User is defined rather than the current filename
}
console.log(getClassFilename(User.constructor)) // fileA.js
This is the general idea. However the actual use case is based on decorators:
fileA.js
import someDecorator from './decorator'
#someDecorator
class User {
}
decorator.js
export default function (target) {
// can I somehow get the target filename without passing it as a property?
}
That information isn't available to you by default, the module in question would have to provide a means of accessing the information.
You've mentioned __filename so I'm assuming you're using Node. The module providing User could provide that information like this:
export const SourceFilename = __filename;
Note taht there's no in-spec way to do that without Node's __filename (but there's one under consideration and reasonably far down the path toward being added).
Updated answer for updated question: There's nothing stored on the class (constructor) User that provides this information. So again, the code defining User would need to provide that information (as a property on User, as something you can get from the module and pass separately to the decorator, etc.). Otherwise, it simply isn't available to you.

How can I join three tables in Bookshelf.js?

I read the Bookshelf documentation related to through, but, but I can't found out how I should proceed. I have three tables named in a different convention than Bookshelf uses. Basically, a Group has many Users through Profile. The last one that makes the connection.
Table Name: User
- id_user
- username
- password
Table Name: Profile
- id_user
- id_group
Table Name: Group
- id_group
- name
- description
- status
My group model is like so:
module.export = BookShelf.model('Group', {
tableName: 'pats_grupos',
users: function() {
return this.hasMany('User').through('Profile');
}
});
Taking in consideration that my tables don't follow the _id convention (but instead, the id_ one), how can I tell Bookshelf to work with my custom table naming pattern?
Accordingly to Bookshelf's idAttribute documentation, when not using the default 'id' you must change your models to explicitly declare the id attribute used. Like:
module.export = BookShelf.model('Group', {
tableName: 'pats_grupos',
idAttribute: 'id_group',
users: function() {
return this.hasMany('User').through('Profile');
}
});
And since your foreign keys are also not following Bookshelf default naming you may have to declare them explicitly on the through() call too. Something like:
//...
users: function() {
return this
.hasMany('User')
.through('Profile', 'id_user', 'id_group');
}
//...

Sails.js - Postgresql Adapter multiple schemas

I've been searching a lot about Sails.js multi tenancy capabilities and I know that such a feature is not yet implemented. My initial idea was to build multi tenant app by creating one database per tenant.
Since I realized that I can't do such a thing in Sails.js yet, I tried a different aproach by creating only one database ( POSTGRES ) but with lots of schemas, each one representing a tenant. My problem is that I can't/I dunno ( don't even know if that is possible in Sails/Postgres adapter ) how to dynamically ( on runtime ) define what schema a given object should query aganist, based on the logged user.
Has anyone faced a problem like this? How can I proceed?
Sorry for English and thanks.
In my experience adding in the model does not work.
The only thing that worked for me was using the meta call to specify the schema.
await Users.create(newUser).meta({ schemaName: 'admin' });
A bit cumbersome, but it is working.
Hope this helps someone.
I thinks is an issue of the waterline sequel adapter, based in this answer.
The way to do it is add a property in the model
meta: {
schemaName: 'schema'
},
but is not working, you can't define multiple schemas, only takes the user as an schema, if the property schema is set in true ins the config/models.js, the definition of a schema for every table is not working.
The clue is inside the sails-postgresql adapter code - several of its helpers include this bit:
var schemaName = 'public';
if (inputs.meta && inputs.meta.schemaName) {
schemaName = inputs.meta.schemaName;
} else if (inputs.datastore.config && inputs.datastore.config.schemaName) {
schemaName = inputs.datastore.config.schemaName;
}
So indeed the driver is looking for a schema named public by default, unless a different value is provides via calls to meta() as described above, OR the schema name is configured application-wide.
To configure the schema name for all models, a schemaName property needs to be included in the configuration of the postgresql datastore, which occurs in datastore.js:
...
default: {
adapter: 'sails-postgresql',
url: 'postgresql://username:password#localhost:5432/your_database_name',
schemaName: 'your_schema_name_here'
}
Once this is in place, you don't have to append meta({ schemaName: 'blah'}) to any of the queries. I struggled with this for a couple of days and have finally solved it in this manner.

Categories