Sails.js - Postgresql Adapter multiple schemas - javascript

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.

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.

Sequelize, Postgresql, and created/modified timestamp management

I'm really new to Sequelize, but I've used another ORM in another language for a long time. I'm trying to figure out how the magic stuff behind getting model timestamps to be managed automatically, and I feel like I'm going around in circles.
What I'd like is for Sequelize to manage the job of knowing when to update "createdAt" and "modifiedAt" for me. However, I would strongly prefer that it would use statement_timestamp() (mostly out of long habit and the advice of a much smarter database person than me). I don't want important stuff like those to rely on the client clocks.
My previous ORM just maintained two prepared statements (per table), one for creation and one for update, and the queries just updated the right fields the right way because they were coded like that. Of course I could write raw SQL but that kind-of defeats the purpose of using something like Sequelize.
I've found some material online that seems to suggest that if I want to do what I'm talking about, I'm on my own, because would have to turn timestamps off on the models. That linked post is about MySQL, which has some trick to make that work.
I would love to post code, but it's a chicken and egg issue. Oh, also: I am creating my schema manually with scripts I run through psql. In the scripts I define the columns like I have been doing for years:
CREATE TABLE unit_type (
utyp_id serial NOT NULL PRIMARY KEY,
utyp_name varchar(32) NOT NULL,
utyp_description varchar(512) NOT NULL,
created timestamp without time zone NOT NULL DEFAULT statement_timestamp(),
modified timestamp without time zone NOT NULL DEFAULT statement_timestamp(),
version SMALLINT NOT NULL DEFAULT 0
);
(I don't care about the column names; "createdAt" would be fine.) My old ORM didn't pay attention to the column defaults; the timestamp stuff was an intrinsic feature of it. (It was home-made so it has no name. It worked though.)
My current code via Sequelize all works fine, but I'm interested in getting the real database stuff worked out, and I just don't understand how the timestamp feature is supposed to work.
You can overwrite the Sequelize's default timestamp function with hooks.
const sequelize = new Sequelize(
...
{
host: 'localhost',
port: 5432,
dialect: 'postgres',
hooks : {
beforeCreate : (record, options) => {
record.dataValues.createdAt = Sequelize.fn('statement_timestamp');
record.dataValues.updatedAt = Sequelize.fn('statement_timestamp');
},
beforeUpdate : (record, options) => {
record.dataValues.updatedAt = Sequelize.fn('statement_timestamp');
}
}
}
);

Is it possible to create multiple dataStore in angular-localForage?

Is it possible to have multiple dataStore in IndexedDB using angular-localForage?
I tried this:
var $tblStage = $localForage.createInstance({
storeName : 'tblStage', // name of the table
});
but it triggered an error which says:
Error: A localForage instance with the name dbName is already defined.
I read this Question before but, is the only way is working with Raw IndexedDB? Or I missed something in my code? Any Idea?
Yes, only configure when you use.
https://github.com/mozilla/localForage#configuration
But no its possible two instance at same db.
If you intend to use the directive, local-forage, you'll have to copy and configure it.
Just like this https://github.com/ocombe/angular-localForage/blob/master/src/angular-localForage.js#L51, for change the provide,
only change defaultConfig at #L20, and name of angular module directive #L465.
And give more information but the reputation is very low and this leaves hook and my English so bad.

Fully virtual Mongoose schema, held in memory and not persisted

Can't find any docs or posts for this, which may indicate I'm trying to do something incorrect.
Is it possible to use a Mongoose schema that is entirely virtual, i.e. not persisted to the db?
I have a number of models, most of which are persisted to db, but would like to consistently include models that are only retained in memory, not persisted?
The closest I can come up with is along these lines, but it will still persist objects with only an id attribute in the database. Simplified here:
// access_token.js
var schema = mongoose.Schema({});
schema.virtual('token').get(function() {
return 'abcde12345';
});
module.exports = mongoose.model('AccessToken', schema);
The idea in doing this is to abstract models so that the consuming part of the app does not need to be aware of whether a model is persisted to the database or only held in memory. Of course this could be achieved by creating the same object and methods as a plain object, but that approach would quickly become repetitive.
You could override (monkey patch) the Mongoose methods which save data (e.g. .save) but I suspect what you are trying to do is difficult/impossible.
You could take a look at sift.js, which is a query library to do in-memory querying.
https://github.com/crcn/sift.js
You can set a pre middleware for this model which always fails.
schema.pre('save', function (next) {
next(new Error('This can't be saved!');
});
So you will know when you are doing wrong.

breezejs: adding referential constraint to an entity type

This is a follow-up question to my previous issue - this one was getting a bit messy and is more related to the Telerik Data Service.
The metadata I receive from the server are missing the referential constraints in the association node, although I've set the foreign key attribute on my model.
Therefore I was thinking about manually adding these constraints to my entities in the callback of FetchMetadata.
Is that possible and can someone provide a simple example on how to do it ?
[EDIT]
Here's what I have so far:
manager.fetchMetadata().then(function () {
var mandatType = manager.metadataStore.getEntityType("Mandate");
mandatType.autogeneratedKeyType = breeze.AutoGeneratedKeyType.Identity;
var openPositionsProp = new breeze.NavigationProperty({
name: "OpenPositions",
entityTypeName: "OpenPositions:#DirectDebitModel",
isScalar: true,
associationName: "OpenPosition_Mandate_Mandate_OpenPositions",
foreignKeyNames: ["Id"]
});
mandatType.addProperty(openPositionsProp);
});
But it raises the exception:
The 'Mandate:#DirectDebitModel' EntityType has already been added to a MetadataStore and therefore no additional properties may be added to it.
Ok, I have a possible approach that you might be able to use right now.
Fetch the metadata from Teleriks OData feed just like you do now.
Export the metadataStore created as a result of the previous step via the MetadataStore.exportMetadata method. This will return "stringified" json for the same metadata in Breeze's native format. This format is much easier to work with.
Convert this string to json via JSON.parse.
Modify the json to add referential constraint information. See Breeze Native Metadata format docs here
Create a new MetadataStore and import the modified json into it.
Create a new EntityManager with this MetadataStore and use it. This EntityManager should now have complete Breeze metadata for use with the rest of the feed.
Hope this makes sense!
We are planning on releasing a form of hybrid metadata in the next release. Unfortunately, it doesn't cover your case because we are focusing on how to add custom metadata to an existing metadataStore, and not actually edit/modify the existing metadata.
Another alternative is that we (IdeaBlade) do offer consulting for this type of work. We could probably write a tool that does steps 1 thru 6 for you. Please contact breeze#ideablade.com if this is of interest and mention this post.
So you are getting meta data but it doesn't have a relationship between the entities. Hmm I have not gotten metaData AND tried to create additional model properties that are related.
Your best bet is to add a property that is a navigation type on the constructor.
http://www.breezejs.com/sites/all/apidocs/classes/EntityType.html#method_addProperty
If it were me, I would try it this way (or something similar) inside of the constructor -
myEntity.addProperty({
associatedEntity: {
entityTypeName: "AssociatedEntity", isScalar: true,
associationName: "AssociatedEntity_MyEntitys", foreignKeyNames: ["associatedEntityId"]
}
});
Where myEntity is the name of the current entity, AssociatedEntity would be your navigation property, the associatedEntityId is a property of myEntity that refers to the other entity. Of course to have this be a two-way relationship you would need to add a property to AssociatedEntity as well.
associatedEntity.addProperty({
myEntitys: {
entityTypeName: "MyEntity", isScalar: true,
associationName: "AssociatedEntity_MyEntitys", foreignKeyNames: ["myEntityId"]
}
});

Categories