I'm dealing with three tables.
Right now I can get the feed of a user (activities table) and activityTypes object (id,type from activitytypes table), but I also need to get related_user (users table) object of a given activity, so I'll know the details of the related_user if the column isn't empty.
users
id
email
username
password_hash
activitytypes
id
type
activities
id
user_id (foreign key from users, owner of the activity)
status
activitytype_id (foreign key from activitytypes)
related_user (foreign key from users.
Shows relation with the user_id, if there's any.
if activitytype_id is 2 or 3, this will be shown as "followed, "commented", etc.)
My models
var activityType = db.Model.extend({
tableName: 'activitytypes',
hasTimestamps: false
});
var activity = db.Model.extend({
tableName: 'activities',
hasTimestamps: true,
activityTypes: function() {
return this.belongsTo(activityType);
}
});
var user = db.Model.extend({
tableName: 'users',
hasTimestamps: true,
feed: function() {
return this.hasMany(activity);
}
});
This is my existing query, how can I add related_user object to it?
user.where({id : 43}).fetchAll({withRelated : ['feed.activityTypes']}).then(function(data) {
data = data.toJSON();
res.send(data);
});
I solved it.
I added a new method to activity, like below.
userRelated : function() {
return this.belongsTo(user,'related_user');
}
And here's the updated query. I don't know whether it's the right way or not in terms of optimization, but it works.
user.where({
id: 43
}).fetchAll({
withRelated: ['feed.userRelated', 'feed.activityTypes']
}).then(function(data) {
data = data.toJSON();
res.send(data);
});
Right now feed, userRelated and activityTypes return without a problem, everything is in data in JSON format.
Related
I am trying to filter list of maps from a dynamodb table which is of the following format.
{
id: "Number",
users: {
{ userEmail: abc#gmail.com, age:"23" },
{ userEmail: de#gmail.com, age:"41" }
}
}
I need to get the data of the user with userEmail as "abc#gmail.com". Currently I am doing it using the following dynamodb query. Is there any another efficient way to solve this issue ?
var params = {
TableName: 'users',
Key:{
'id': id
}
};
var docClient = new AWS.DynamoDB.DocumentClient();
docClient.get(params, function (err, data) {
if (!err) {
const users = data.Item.users;
const user = users.filter(function (user) {
return user.email == userEmail;
});
// filtered has the required user in it
});
The only way you can get a single item in dynamo by id if you have a table with a partition key. So you need to have a table that looks like:
Email (string) - partition key
Id (some-type) - user id
...other relevant user data
Unfortunately, since a nested field cannot be a partition key you will have to maintain a separate table here and won't be able to use an index in DynamoDB (neither LSI, nor GSI).
It's a common pattern in NoSQL to duplicate data, so there is nothing unusual in it. If you were using Java, you could use transactions library, to ensure that both tables are in sync.
If you are not going to use Java you could read DynamoDB stream of the original database (where emails are nested fields) and update the new table (where emails are partition keys) when an original table is updated.
I have a dynamodb table that stores users videos.
It's structured like this:
{
"userid": 324234234234234234, // Hash key
"videoid": 298374982364723648 // Range key
"user": {
"username": "mario"
}
}
I want to update username for all videos of a specific user. It's possible with a simple update or i have to scan the complete table and update one item a time?
var params = {
TableName: DDB_TABLE_SCENE,
Key: {
userid: userid,
},
UpdateExpression: "SET username = :username",
ExpressionAttributeValues: { ":username": username },
ReturnValues: "ALL_NEW",
ConditionExpression: 'attribute_exists (userid)'
};
docClient.update(params, function(err, data) {
if (err) fn(err, null);
else fn(err, data.Attributes.username);
});
I receive the following error, I suppose the range key is necessary.
ValidationException: The provided key element does not match the schema
Dynamo does not support write operations across multiple items (ie. for more than one item at a time). You will have to first scan/query the table, or otherwise generate a list of all items you'd like to update, and then update them one by one.
Dynamo does provide a batching API but that is still just a way to group updates together in batches of 25 at a time. It's not a proxy for a multi-item update like you're trying to achieve.
I have the following use case:
I have a users table in MongoDB on the backend, which is a separate service than the frontend. I use DDP.connect() to connect to this backend service.
Each user has a set of "subjects"
Each subject in the users table is referenced by id, not name. There is a separate table called "subjects" that holds the subjects by id.
I want to publish the user down to the client, however I want the published user to be populated with the subjects first.
I tried the following, inspired by this blog post:
// in the backend service on port 3030
Meteor.publish('users', function(userId) {
var _this = this;
Meteor.users.find({
_id: userId
}).forEach(function(user) {
user.profile = populate(user.profile);
console.log(user);
_this.changed('users', userId, {
_id: userId,
profile: user.profile
});
});
_this.ready();
});
// in the client
var UserService = DDP.connect('http://localhost:3030');
var UserServiceProfile = UserService.subscribe('users', Meteor.userId());
console.log(UserServiceProfile);
This gives the following error on the backend:
Exception from sub users id akuWx5TqsrArFnQBZ Error: Could not find element with id XhQu77F5ChjcMTSPr to change.
So I tried changing _this.changed to _this.added. I don't get any errors, but the changes aren't showing up in the client minimongo, even though I can see that the populate function worked through the console.log(user) line.
I'm not sure how you'd fix your code, but you might not need to. It looks like you want the https://atmospherejs.com/maximum/server-transform package.
Add the server-transform package to your project, and replace your code with this (I'm going to assume you also have underscore added to your project, and the subjects collection in your database corresponds to a global variable called Subjects in your code.):
Meteor.publishTransformed('users', function(userId) {
return Meteor.users.find({
_id: userId
}).serverTransform({
'profile.subjects': function(doc) {
var subjects = [];
_(doc.profile.subjects).each(function(subjectId) {
subjects.push(Subjects.findOne(subjectId));
});
return subjects;
}
});
});
Right now I have a working messaging system developed in Meteor where users can send private messages to each other.
The server looks like this:
// .. lot of code
Meteor.publish("privateMessages", function () {
return PMs.find({ to: this.userId });
});
PMs.allow({
insert: function(user, obj) {
obj.from = user;
obj.to = Meteor.users.findOne({ username: obj.to })._id;
obj.read = false;
obj.date = new Date();
return true;
}
});
// .. other code
When the user subscribes to privateMessages, he gets a mongo object that looks like this:
{ "to" : "LStjrAzn8rzWp9kbr", "subject" : "test", "message" : "This is a test", "read" : false, "date" : ISODate("2014-07-05T13:37:20.559Z"), "from" : "sXEre4w2y55SH8Rtv", "_id" : "XBmu6DWk4q9srdCC2" }
How can I change the object to return the username instead of the user id?
You need to do so in a way similar to how you changed username to _id. You can create a utility function:
var usernameById = function(_id) {
var user = Meteor.users.findOne(_id);
return user && user.username;
};
Edit:
If you don't want to poll minimongo for each message, just include username instead of _id inside your message object. Since username is unique, they will be enough.
If in your app you allow users to change username, it might be a good idea to also keep the _id for the record.
In one of larger apps I've been working with we kept user's _id in the model (to create links to profile etc.), as well as cached his profile.name (for display purposes).
I suggest adding the collection helpers package from atmosphere. Then create a helper for PMs called toUser that returns the appropriate user.
Then you can get the name using message.user.name.
Assume the following current case:
I do have a collection "Tables"
A table is an object with properties like {private:0, private1,private2,…} (seats 0,1,2…)
I publish the collection with 2 arguments, one the tableId the second one the seat.
Given the seat, the publication will filter (hide) properties the client must not be able to see.
For now the tableID and seat were taken from the client's session so everything was reactive.
I have a "takeSeat(seatNb)" method. If a client invoke this method and is allowed to seat at the table, the seat number is sent back to client which then put it into it's session under the seat key. This will therefore update the subscription and filter the table's seats content correctly.
I'm not satisfied by this design because I realised that the client might be cheating by subscribing to a seat by itself. Also (and more important) I'm using another DDP client in c++ and would like to keep this logic part in the server side. i.e. not to have to subscribe with another seat once I get one, if I do take a seat at a table I would like the server to show the right fields on the table by itself.
After several searches I decided to add a collection aside for "Players" so that I might easily get notified within my "tables" collection a "player" is added or removed to a table. But this is only half of the problem. I do have to actually change the handler of the publication itself so that the filter will become reactive. This is where I'm stuck, here is some simplified code to understand the case:
Meteor.publish("current-table", function(table_id)
{
var self = this;
var handle = Players.find({"tableID": table_id}).observeChanges(
{
added: function(id)
{
console.log("A player joined the table added");
self.changed("tables", table_id);
},
removed: function(id) {
console.log("A player left the table");
self.changed("tables", table_id);
}
});
self.onStop(function() {
handle.stop();
});
// PUBLISH THE TABLE BUT HIDE SOME FIELDS BEFORE
var player = Players.findOne({"userID": this.userId, "tableID": table_id}) || {"seat": -1};
var seat = player.seat;
var privateFilter = {"private0": false, "private1": false, "private2": false, "private3": false};
delete privateFilter["private" + seat];
return Tables.find(table_id, {fields: privateFilter});
});
How to proceed ? Is there a more elegant way to achieve this ?
You could store the seat in the user's profile. Then your publication would watch for changes to the user's profile and adjust as appropriate.
For example:
Meteor.publish("current-table", function() {
var self = this;
var getFilteredTableForSeat = function(seat_id) {
// Get the table for the given seat_id, filtering the fields as appropriate
};
var handle = Meteor.users.find({_id: this.userId}).observeChanges({
changed: function (id, fields) {
if(fields.profile)
self.changed("tables", 'current-table', getFilteredTableForSeat(fields.profile.seat_id));
}
});
self.added("tables", 'current-table', getFilteredTableForSeat(Meteor.users.findOne(this.userId).profile.seat_id));
self.ready();
self.onStop(function() {
handle.stop();
});
});
If the user's seat changes then the current-table document of the Tables collection will update.
This example makes some assumptions, and will require adjustment if these aren't true for you:
You can find a table given a seat_id (if you can't, you may need to store the table id in the user's profile as well)
A seat_id always belongs to the same table (if it doesn't, you'll need to add a changed handler to wherever that information is stored)
The table information returned by the publication doesn't change (if it does, you'll need to add a changed handle to the Table collection, similar to the user handle)
Thanks to jrullmann's answer I decided to make a custom filtered publication using the reactivity of 2 collections. Here is my final code:
Meteor.publish("current-table", function(table_id)
{
var self = this;
function getFilteredTable()
{
var player = Players.findOne({"userID": self.userId, "tableID": table_id}) || {"seat": -1};
var seat = player.seat;
var privateFilter = {"prv": false, "prv0": false, "prv1": false, "prv2": false, "prv3": false};
delete privateFilter["prv" + seat];
return Tables.findOne(table_id, {fields: privateFilter});
}
var tableHandler = Tables.find(table_id).observeChanges(
{
added: function()
{
self.added('tables', table_id, getFilteredTable());
},
removed: function()
{
self.removed('tables', table_id);
},
changed: function()
{
self.changed('tables', table_id, getFilteredTable());
}
});
self.ready();
self.onStop(function() {
tableHandler.stop();
});
var handle = Players.find({"tableID": table_id}).observeChanges(
{
added: function(collection, id, fields)
{
self.changed('tables', table_id, getFilteredTable());
console.log("added");
},
removed: function(collection, id, fields)
{
// Little trick to avoid meteor use the former deleted (hidden) properties
self.removed('tables', table_id);
self.added('tables', table_id, getFilteredTable());
console.log("removed");
}
});
self.onStop(function() {
handle.stop();
});
});
I had a similar problem and wrote these two atmosphere packages which solve the problem:
https://atmosphere.meteor.com/package/server-deps
https://atmosphere.meteor.com/package/reactive-publish
Install the second package with meteorite, use "Meteor.reactivePublish" instead of "Meteor.publish" and it will automatically update when the results of any queries with the option {"reactive": true} change.
This example from the readme will publish precisely those items which the user's team can see, and will update when either the user changes team or the team's visible items change.
Meteor.reactivePublish(null, function() {
if (this.userId) {
var user = Meteor.users.findOne({_id: this.userId}, {reactive: true});
if (user.team) {
var team = Collections.teams.findOne({_id: user.team}, {reactive: true});
var visibleItems = _.compact(team.visibleItems);
return Collections.items.find({_id: {$in: visibleItems}});
}
}
});