I'm curious if there is a way to change the id used when specifying withRelated on a model?
Here's the situation:
I'm working on a platform that has a calendar component and that calendar needs to support recurring appointments. Rather than creating every potential instance of a recurring appointment, I'm generating these recurrences on the fly, which leads to much less maintenance going forward. My Appointment model also has relations that are associated with each instance. An Appointment can have many services, etc. When loading these relations using withRelated on an appointment that exists in the database (is not dynamically generated), the id of that appointment is passed into whatever relation withRelated attempt to load and works great.
However, with these dynamically generated appointments, I actually need to load the relations using the id of the parent appointment from which these instances were generated. Unfortunately, I can't seem to find a way to either specify to use that parent id or a place where I can intercept and change it before the relation is loaded.
My situation is an odd one, but it would be a simple fix if I could change what is passed in when withRelated is called.
Is this possible?
I'm also open to other suggestions.
Goal/Desired Results
Just to be clear, my goal is fairly simple. When Bookshelf loads a many-to-many relationship, it uses the idAttribute of the joining model in order to load related relations.
For example, Appointment is my primary model and has a many-to-many relationship with Pets. Therefore, when I load the pets relation, Bookshelf does a join on the appointment_pets table looking for all records where appointment_pet.appointment_id IN (?) where the parameters are ids from the appointment table.
My desired goal is to be able to substitute appointment.id with appointment.parent_id when looking up these relations as in my situation, these dynamic records don't actually exist in the database yet and therefore need to use the information attached to the parent record, which does exist.
Updates
I was finally able to get different ids injected into the above query by temporarily changing the idAttribute on the model from id to parent_id. The query is correct and parameters being passed are also correct, but the relationships are empty when the model is returned.
// appointment model
pets() {
return this.belongsToMany('Pet', 'appointment_pet', 'parent_id');
},
},
// Returns all appointments
fetchAll(businessId, options) {
options = options || {};
// If a start and end date are passed, return all appointments
// within that range, with recurrences expanded.
if (!_.isUndefined(options.startDate) &&
!_.isUndefined(options.endDate)) {
const startDate = Moment(options.startDate);
const endDate = Moment(options.endDate);
// temporarily change id attribute so `parent_id` is used when looking up the relation
Appointment.prototype.idAttribute = 'parent_id';
return Appointment
.query((qb) => {
qb.from(Bookshelf.knex.raw('get_appointments(?, ?, ?) AS appointment', [businessId, startDate.toISOString(), endDate.toISOString()]));
})
.fetchAll({
withRelated: [{
pets: (qb) => {
qb.select(['appointment_id as parent_id', 'pet.*']);
}
}]
});
}
// Otherwise, return all appointments
return Appointment.findAll({ businessId: businessId }, options);
},
I see the following debug output:
{ method: 'select',
options: {},
bindings:
[ null,
'35a2941c-d73f-4cf3-87de-bfbcbc92fbc5', <-- these are all `appointment.parent_id`
'5de28a57-ce4c-4fcc-865a-54cf97e08c6c',
'bf4b6784-b96a-4321-8335-e449aa8dcda1',
'695edc54-3a93-42a5-a3e0-b331f26912cf',
'76a99204-9659-4270-904c-2c42e5c40a15',
'28437e3c-abc1-4fd6-8c26-5543c09ab730',
'350da29d-0e82-4e6c-b9fe-be31c64655b4',
'bcaee041-c9ec-4a34-8c0d-8f21d2d79197',
'91380fcc-2f0d-4344-a24b-dc37abe3931f',
'31796797-5041-4fae-815b-0f2c368654b0',
'94800af3-fd23-4b59-af88-15b1f622e8e1',
'aa8c202d-249e-4ffc-ab55-e535f2c2121f',
'8a1fd093-45a6-4864-8c52-ea8851740f77',
'5f91ad6f-4231-4d18-b718-d99fcc17a18b',
'5f879e43-a44f-41dd-b9dd-11f06af4cc8d' ],
sql: 'select "pet".*, "appointment_pet"."parent_id" as "_pivot_parent_id", "appointment_pet"."pet_id" as "_pivot_pet_id" from "pet" inner join "appointment_pet" on "appointment_pet"."pet_id" = "pet"."id" where "appointment_pet"."parent_id" in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'}
Running this command in postgres, I see the correct output, but still no relations. I saw #1203 and tried adding the parent_id explicitly to the select, but still no luck.
Unfortunately I don't have enough data to replicate your problem. I tried it on a basic one-to-many example, and I think the easiest way is to define a new relation in the model itself. E.g.:
var Genre = bookshelf.Model.extend({
tableName: 'genres',
movie: function() {
return this.hasMany(Movie);
},
movie_parent: function() {
return this.hasMany(Movie, 'parent_id');
}
});
var Movie = bookshelf.Model.extend({
tableName: 'movies'
genre: function() {
return this.belongsTo(Genre);
},
genre_parent: function() {
return this.belongsTo(Genre, 'id');
}
});
So then, you can query it reasonably simple, in the withRelated property.
new Movie().fetchAll({
withRelated: ['genre_parent'] //can be replaced with withRelated: ['genre']
}).then(function(movies) {
res.json(movies);
});
If this is not the solution you're looking for, please provide additional data (database structure would be interesting to see)
Related
This question is more of a theoretical one, since I have not started implementation yet.
The situation is as follows:
I have an application in which users can upload structured data (like Excel, CSV etc.).
Due to requirements, I want to store them in the database, ideally creating a new table on the fly with the table name set to the file name and columns based on the file itself.
This initialisation is still doable with sequelize, I think.
However, as sequelize relies on models, then I am stuck as I am not sure what type and how many columns there will be, thus creating the need for something off 'Dynamic model' or 'Generic model'.
I am not sure how to do this, and I cannot find anything related when searching. I would appreciate your 2 cents on this approach, and if there are other ideas I am very eager to hear them.
Thanks in advance!
First, you need to add code to map your dynamic columns into Sequelize column definition like:
const columnDefinitions = [
{
field: 'field_name_from_csv',
type: DataTypes.INTEGER,
allowNull: true
},
{
field: 'field_name2_from_csv',
type: DataTypes.INTEGER,
allowNull: true
},
]
So you need to determine the data type of a certain column and create an appropriate column definition.
Second, you need to store all these mapping info in the special table(s) so you know what dynamic tables you have and how to register them in Sequelize.
Once you have a table name and column mappings you can register all tables as models in Sequelize:
// here you need to convert an array of mappings into the object where field names should be keys and column definitions should be values.
const columnMappingsAsObject = ...
const tableName = 'dynamic_table_name'
const dynamicModel = sequelize.define(tableName, columnMappingsAsObject, {
tableName
});
// now you can use it to get records and so on:
const records = await dynamicModel.findAll({})
I have a doc in couchDB:
{
"id":"avc",
"type":"Property",
"username":"user1",
"password":"password1",
"server":"localhost"
}
I want to write a view that returns a map of all these fields.
The map should look like this: [{"username","user1"},{"password","password1"},{"server","localhost"}]
Here's pseudocode of what I want -
HashMap<String,String> getProperties()
{
HashMap<String,String> propMap;
if (doc.type == 'Property')
{
//read all fields in doc one by one
//get value and add field/value to the map
}
return propMap;
}
I am not sure how to do the portion that I have commented above. Please help.
Note: right now, I want to add username, password and server fields and their values in the map. However, I might keep adding more later on. I want to make sure what I do is extensible.
I considered writing a separate view function for each field. Ex: emit("username",doc.username).
But this may not be the best way to do this. Also needs updates every time I add a new field.
First of all, you have to know:
In CouchDB, you'll index documents inside a view with a key-value pair. So if you index the property username and server, you'll have the following view:
[
{"key": "user1", "value": null},
{"key": "localhost", "value": null}
]
Whenever you edit a view, it invalidates the index so Couch has to rebuild the index. If you were to add new fields to that view, that's something you have to take into account.
If you want to query multiple fields in the same query, all those fields must be in the same view. If it's not a requirement, then you could easily build an index for every field you want.
If you want to index multiple fields in the same view, you could do something like this:
// We define a map function as a function which take a single parameter: The document to index.
(doc) => {
// We iterate over a list of fields to index
["username", "password", "server"].forEach((key, value) => {
// If the document has the field to index, we index it.
if (doc.hasOwnProperty(key)) {
// map(key,value) is the function you call to index your document.
// You don't need to pass a value as you'll be able to get the macthing document by using include_docs=true
map(doc[key], null);
}
});
};
Also, note that Apache Lucene allows to make full-text search and might fit better your needs.
I am querying data between two specific unixtime values. for example:
all data between 1516338730 (today, 6:12) and 1516358930 (today, 11:48)
my database receives a new record every minute. Now, when i want to query the data of last 24h, its way too dense. every 10th minute would be perfect.
my question now is: how can i read only every 10th database record, using DynamoDB?
As far as i know, theres no posibility to use modulo or something similar that pleases my needs.
This is my AWS Lambda Code so far:
var read = {
TableName: "user",
ProjectionExpression:"#time, #val",
KeyConditionExpression: "Id = :id and TIME between :time_1 and :time_2",
ExpressionAttributeNames:{
"#time": "TIME",
"#val": "user_data"
},
ExpressionAttributeValues: {
":id": event, // primary key
":time_1": 1516338730,
":time_2": 1516358930
},
ScanIndexForward: true
};
docClient.query(read, function(err, data) {
if(err) {
callback(err, null);
}
else {
callback(null, data.Items);
}
});
};
You say that you insert 1 record every minute?
The following might be an option:
At the time of insertion, set another field on the record, let's call it MinuteBucket, which is calculated as the timestamp's minute value mod 10.
If you do this via a stream function, you can handle new records, and then write something to touch old records to force a calculation.
Your query would change to this:
/*...snip...*/
KeyConditionExpression: "Id = :id and TIME between :time_1 and :time_2 and MinuteBucket = :bucket_id",
/*...snip...*/
ExpressionAttributeValues: {
":id": event, // primary key
":time_1": 1516338730,
":time_2": 1516358930,
":bucket_id": 0 //can be 0-9, if you want the first record to be closer to time_1, then set this to :time_1 minute value mod 10
},
/*...snip...*/
Just as a follow-up thought: if you want to speed up your queries, perhaps investigate using the MinuteBucket in an index, though that might come at a higher price.
I don't think that it is possible with dynamoDB API.
There are FilterExpression that contains conditions that DynamoDB applies after the Query operation, but before the data is returned to you.
But AFAIK it isn't possible to use a custom function. And build-in functions are poor.
As a workaround, you could mark each 10th item on the client side. And then query with checking attribute_exists (or attribute value) to filter them.
BTW, it would be nice to create the index for 'Id' attribute with sort key 'TIME' for improving query performance.
I want to obtain all the fields of a schema in mongoose. Now I am using the following code:
let Client = LisaClient.model('Client', ClientSchema)
let query = Client.findOne({ 'userclient': userclient })
query.select('clientname clientdocument client_id password userclient')
let result = yield query.exec()
But I want all the fields no matter if they are empty. As always, in advance thank you
I'm not sure if you want all fields in a SQL-like way, or if you want them all in a proper MongoDB way.
If you want them in the proper MongoDB way, then just remove the query.select line. That line is saying to only return the fields listed in it.
If you meant in a SQL-like way, MongoDB doesn't work like that. Each document only has the fields you put in when it was inserted. If when you inserted the document, you only gave it certain fields, that document will only have those fields, even if other documents in other collections have different fields.
To determine all available fields in the collection, you'd have to find all the documents, loop through them all and build an object with all the different keys you find.
If you need each document returned to always have the fields that you specify in your select, you'll just have to transform your object once it's returned.
const fields = ['clientname', 'clientdocument', 'client_id', 'password', 'userclient'];
let Client = LisaClient.model('Client', ClientSchema)
let query = Client.findOne({ 'userclient': userclient })
query.select(fields.join(' '))
let result = yield query.exec()
fields.forEach(field => result[field] = result[field]);
That forEach loop will set all the fields you want to either the value in the result (if it was there) or to undefined if it wasn't.
MongoDB is schemaless and does not have tables, each collection can have different types of items.Usually the objects are somehow related or have a common base type.
Retrive invidual records using
db.collectionName.findOne() or db.collectionName.find().pretty()
To get all key names you need to MapReduce
mapReduceKeys = db.runCommand({
"mapreduce": "collection_name",
"map": function() {
for (var key in this) {
emit(key, null);
}
},
"reduce": function(key, stuff) {
return null;
},
"out": "collection_name" + "_keys"
})
Then run distinct on the resulting collection so as to find all the keys
db[mapReduceKeys.result].distinct("_id") //["foo", "bar", "baz", "_id", ...]
I wish to find all users, not the current User. A pair of users are stored within a "Room" array under this collection structure:
structure of each room (from another html page)
var newRoom = Rooms.insert({
owner : Meteor.userId(),
receiver : receiver,
people : [ owner , receiver ],
});
Collection.js (using dburles collection helper)
Rooms.helpers({
receiverName: function() {
return Meteor.users.findOne({ _id: this.receiver }).username;
}
});
html
<!-- **allRooms.html** Works fine, names appear -->
{{#each rooms}} {{receiverName}}{{/each }}
<!-- **roomDetail.html** names dont show, this.receiver undefined -->
{{receiverName}}
roomDetail js template helper
self.subscribe('room', Router.current().params._id);
self.subscribe('users');
});
How do I return and display the user's Id thats not the current user from the people field which is an array? I hope to show it in the child page (roomDetail).
Assuming:
Rooms is a collection, and you already have a room document to search on.
You only want to fetch a single user.
Give this a try:
// The list of userIds in room minus the current user's id.
var userIds = _.without(room.People, Meteor.userId());
// Assuming we want only one user...
var user = Meteor.users.findOne({ _id: userIds[0] });
Some thoughts about your original code:
You can't include references to Rooms in your Meteor.users selector unless Rooms is a field of users. Mongo has no notion of joins.
$ne isn't that you want. If you had 100 users published, and your array only contained 2 users (one of which you didn't want), using $ne would return 99 users.
Based on your comments, it looks like you need this in a collection helper. Maybe something like this:
Rooms.helpers({
findUser: function() {
var userIds = _.without(this.People, Meteor.userId());
return Meteor.users.findOne({ _id: userIds[0] });
},
});
And then elsewhere in your code, for a given room instance you could do:
room.findUser()