I'm new to Emberjs, and I'm trying to get my head around how to get data out of my model.
Here's the data structure as its being returned from my server
candidate: {
image: [image],
user: {
name: [User Name]
},
agent: {
phone: [phone]
team: {
name: [Team Name]
}
user: {
name: [User Name]
}
}
}
I cant get ember to recognize any associations more than one level deep.
heres my candidate controller
App.CandidateController = Ember.ObjectController.extend({
location: function() {
var address = this.get('address');
var city = this.get('city');
var state = this.get('state');
var zip = this.get('zip');
return address + ' ' + city + ' ' + state + ', ' + zip;
}.property('address', 'city', 'state', 'zip')
});
App.CandidateSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
agent: {embedded: 'always'},
user: {embedded: 'always'}
}
});
This allows me to get one level deep, but I need two or three levels of association.
Is there a different way to go about it?
I thought organizing everything on the server the way I want it displayed, and just returning the data as its supposed to be rendered to ember, but I have a feeling I'll run into problems when trying to update the models.
Any help is appreciated.
/****** UPDATE *******/
I have refactored my code and I am now returning data from the serializer like this:
{
candidate: {
// relevant fields
},
agent: {
//relevant fields
},
team: {
// relevant fields
}
user: [
{ // candidate user fields },
{ // agent user fields }
]
};
However, data is still not available in my template. In the Ember chrome extension, in the data tab, I get
candidate: (1)
agent: (0)
team: (0)
user (2)
I can see candidate and user data, but not agent or team data. In my candidate model, I have:
App.Candidate = DS.Model.extend({
// other fields
user_id: DS.attr('number'),
agent_id: DS.attr('number'),
user: DS.belongsTo('user'),
agent: DS.belongsTo('agent')
});
It doesn't seem like the belongsTo association actually does anything.
So the first issue is that I'm not getting the correct data, the second issue, and the one that makes me think I am going about this incorrectly, is that I have two users information that I need to display in the template. The first is user information associated with the candidate, and the second is user information that is associated with the agent. Both data need to appear in the same template. Since there is no hierarchy to the data, how would the template know which user info to display in each location?
Again, I think Im thinking about this whole thing incorrectly.
Thanks for your help.
Ember data expects models in a JSON response to be flat, and it's the job of the Serializer to transform your server response into that format. Associations usually are done by ids. The second level is not working because Ember needs to turn each level into an Ember.Object in order to observe property changes.
It might help to look over the JSON conventions portion of the guides. You can also plug your models into the ember data model maker and see what your responses should look like. Finally, make sure you're using the ember-inspector Chrome extension. When I am debugging ember-data related issues, it's usually easiest to just stop after the model hook returns, look in the inspector's store tab, and examine what data has been loaded.
Related
In my user collection, I have an object that contains an array of contacts.
The object definition is below.
How can this entire object, with the full array of contacts, be written to the user database in Meteor from the server, ideally in a single command?
I have spent considerable time reading the mongo docs and meteor docs, but can't get this to work.
I have also tried a large number of different commands and approaches using both the whole object and iterating through the component parts to try to achieve this, unsuccessfully. Here is an (unsuccessful) example that attempts to write the entire contacts object using $set:
Meteor.users.update({ _id: this.userId }, {$set: { 'Contacts': contacts}});
Thank you.
Object definition (this is a field within the user collection):
"Contacts" : {
"contactInfo" : [
{
"phoneMobile" : "1234567890",
"lastName" : "Johnny"
"firstName" : "Appleseed"
}
]
}
This update should absolutely work. What I suspect is happening is that you're not publishing the Contacts data back to the client because Meteor doesn't publish every key in the current user document automatically. So your update is working and saving data to mongo but you're not seeing it back on the client. You can check this by doing meteor mongo on the command line then inspecting the user document in question.
Try:
server:
Meteor.publish('me',function(){
if (this.userId) return Meteor.users.find(this.userId, { fields: { profile: 1, Contacts: 1 }});
this.ready();
});
client:
Meteor.subscribe('me');
The command above is correct. The issue is schema verification. Simple Schema was defeating the ability to write to the database while running 'in the background'. It doesn't produce an error, it just fails to produce the expected outcome.
I'm developing a website with angular-meteor and I've faced a strange problem. On every page I have a chat block, the messages are stored in collection chatMessages and displayed correctly. The problem is that every time I edit current user (manual mongo document edition or updating inside meteor's method) my app removes all subscriptions. As the result, messages disapper from the chat. But not only chat messages disappear. I used this piece of code to understand what's the cause of the problem, but I can barely understand it. Here's the most interesting parts from browser console output:
// On page load
send Object {
msg: "sub",
id: "EPgQwMiDggM7wafCN",
name: "chatMessages",
params: Array[0]
}
receive Object {
msg: "added",
collection: "chatMessages",
id: "G6tv76ZSJYXmnsrAY",
fields: Object
}
// Calling 'createArenaRoom' method
send Object {
msg: "method",
method: "createArenaRoom",
params: Array[1],
id: "8",
randomSeed: "516b5128615f1c7849f2"
}
// User updated
receive Object {
msg: "changed",
collection: "users",
id: "sQnaLPj2FvH692rQM",
fields: Object
}
// ??? WHY ????
send Object {
msg: "unsub",
id: "EPgQwMiDggM7wafCN"
}
receive Object {
msg: "removed",
collection: "chatMessages",
id: "G6tv76ZSJYXmnsrAY"
}
receive Object {
msg: "nosub",
id: "EPgQwMiDggM7wafCN"
}
By the way, data is not removed from mongo. If I refresh the page, it chat messages and everything else appear again.
Any help would be appreciated.
Finally I found out the cause of the problem. In my main.html I had something like this:
<div ng-if="$root.loggingIn" class="loading-screen"></div>
<myapp ng-if="!$root.loggingIn"></myapp>
This was made to prevent component's code execution before current user's data is available. I removed ng-if="!$root.loggingIn" from myapp directive and the problem disappeared. I can only guess why it caused such a strange behaviour. Now I use this.autorun() in components to wait for user data.
The official line from Facebook is that Relay is "intentionally agnostic about authentication mechanisms." In all the examples in the Relay repository, authentication and access control are a separate concern. In practice, I have not found a simple way to implement this separation.
The examples provided in the Relay repository all have root schemas with a viewer field that assumes there is one user. And that user has access to everything.
However, in reality, an application has has many users and each user has different degrees of access to each node.
Suppose I have this schema in JavaScript:
export const Schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: () => ({
node: nodeField,
user: {
type: new GraphQLObjectType({
name: 'User',
args: {
// The `id` of the user being queried for
id: { type: new GraphQLNonNull(GraphQLID) },
// Identity the user who is querying
session: { type: new GraphQLInputObjectType({ ... }) },
},
resolve: (_, { id, session }) => {
// Given `session, get user with `id`
return data.getUser({ id, session });
}
fields: () => ({
name: {
type: GraphQLString,
resolve: user => {
// Does `session` have access to this user's
// name?
user.name
}
}
})
})
}
})
})
});
Some users are entirely private from the perspective of the querying user. Other users might only expose certain fields to the querying user. So to get a user, the client must not only provide the user ID they are querying for, but they must also identify themselves so that access control can occur.
This seems to quickly get complicated as the need to control access trickles down the graph.
Furthermore, I need to control access for every root query, like nodeField. I need to make sure that every node implementing nodeInterface.
All of this seems like a lot of repetitive work. Are there any known patterns for simplifying this? Am I thinking about this incorrectly?
Different applications have very different requirements for the form of access control, so baking something into the basic Relay framework or GraphQL reference implementation probably doesn't make sense.
An approach that I have seen pretty successful is to bake the privacy/access control into the data model/data loader framework. Every time you load an object, you wouldn't just load it by id, but also provide the context of the viewer. If the viewer cannot see the object, it would fail to load as if it doesn't exist to prevent even leaking the existence of the object. The object also retains the viewer context and certain fields might have restricted access that are checked before being returned from the object. Baking this in the lower level data loading mechanism helps to ensure that bugs in higher level product / GraphQL code doesn't leak private data.
In a concrete example, I might not be allowed to see some User, because he has blocked me. You might be allowed to see him in general, but no his email, since you're not friends with him.
In code something like this:
var viewer = new Viewer(getLoggedInUser());
User.load(id, viewer).then(
(user) => console.log("User name:", user.name),
(error) => console.log("User does not exist or you don't have access.")
)
Trying to implement the visibility on GraphQL level has lots of potential to leak information. Think of the many way to access a user in GraphQL implementation for Facebook:
node($userID) { name }
node($postID) { author { name } }
node($postID) { likers { name } }
node($otherUserID) { friends { name } }
All of these queries could load a user's name and if the user has blocked you, none of them should return the user or it's name. Having the access control on all these fields and not forgetting the check anywhere is a recipe for missing the check somewhere.
I found that handling authentication is easy if you make use of the GraphQL rootValue, which is passed to the execution engine when the query is executed against the schema. This value is available at all levels of execution and is useful for storing an access token or whatever identifies the current user.
If you're using the express-graphql middleware, you can load the session in a middleware preceding the GraphQL middleware and then configure the GraphQL middleware to place that session into the root value:
function getSession(req, res, next) {
loadSession(req).then(session => {
req.session = session;
next();
}).catch(
res.sendStatus(400);
);
}
app.use('/graphql', getSession, graphqlHTTP(({ session }) => ({
schema: schema,
rootValue: { session }
})));
This session is then available at any depth in the schema:
new GraphQLObjectType({
name: 'MyType',
fields: {
myField: {
type: GraphQLString,
resolve(parentValue, _, { rootValue: { session } }) {
// use `session` here
}
}
}
});
You can pair this with "viewer-oriented" data loading to achieve access control. Check out https://github.com/facebook/dataloader which helps create this kind of data loading object and provides batching and caching.
function createLoaders(authToken) {
return {
users: new DataLoader(ids => genUsers(authToken, ids)),
cdnUrls: new DataLoader(rawUrls => genCdnUrls(authToken, rawUrls)),
stories: new DataLoader(keys => genStories(authToken, keys)),
};
}
If anyone has problems with this topic: I made an example repo for Relay/GraphQL/express authentication based on dimadima's answer. It saves session data (userId and role) in a cookie using express middleware and a GraphQL Mutation
I'm developing and app with Sails.js and using Waterline orm for db. I'm developing functionality for users to do friend requests and other similar requests to each other. I have following URequest model for that:
module.exports = {
attributes: {
owner: {
model: 'Person'
},
people: {
collection: 'Person'
},
answers: {
collection: 'URequestAnswer'
},
action: {
type: 'json' //TODO: Consider alternative more schema consistent approach.
}
}
};
Basically owner is association to Person who made the request and people is one-to-many association to all Persons who the request is directed. So far fine.
Now I want to have a controller which returns all requests where certain user is involved in meaning all requests where user is either in owner field or in people. How I do query like "give me all rows where there is association to person P" ? In other words how I ca know which URequest models have association to a certain Person?
I tried something like this:
getRequests: function (req, res) {
var personId = req.param('personId');
URequest.find().where({
or: [
{people: [personId]}, //TODO: This is not correct
{owner: personId}
]
}).populateAll().then(function(results) {
res.json(results);
});
},
So I know how to do the "or" part but how do I check if the personId is in people? I know I should somehow be able to look into join-table but I have no idea how and couldn't find much from Waterline docs relating to my situation. Also, I'm trying to keep this db-agnostic, though atm I'm using MongoDB but might use Postgres later.
I have to be honest this is a tricky one, and, as far as I know what you are trying to do is not possible using Waterline so your options are to write a native query using query( ) if you are using a sql based adapter or native otherwise, or try doing some manual filtering. Manual filtering would depend on how large of a dataset you are dealing with.
My mind immediately goes to reworking your data model a bit, maybe instead of a collection you have a table that stores associations. Something like this:
module.exports = {
attributes: {
owner: {
model: 'URequest'
},
person: {
model: 'Person'
}
}
Using the sailsjs model methods (like beforeCreate) you could auto create these associations as needed.
Good Luck, I hope you get it working!
I have started working with EmberJS and Ember-Data, but I've run into a small problem.
I have an application where my data source uses a non-id as the primary key (e.g. /model/<slug_name>). I am using the FixtureAdapter to handle things until I connect it to the backend:
App.ApplicationAdapter = DS.FixtureAdapter.extend({})
App.MyModel = DS.Model.extend({
slug: DS.attr('string'),
body: DS.attr('string')
});
App.MyModel.FIXTURES = [{
slug: 'test-slug',
body: 'Body goes here'
}];
There seem to be several sources of information that explain how to use a different primary key, but they are all oriented around working with the actual data source (and involve modifying the serializer; something that isn't used when working with FixtureAdapter).
If I do the naive thing and just wire up routes...
// ...
App.MyModelEditRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('mymodel', params.slug)
}
});
... and change the template...
{{#link-to "mymodel.edit" slug}}Edit{{/link-to}}
...then I get this error:
Assertion failed: You made a request for a mymodel with id test-slug, but the adapter's response did not have any data
How can I use a non-'id' field as the primary key when working with a FixtureAdapter?
Ideally, is there a solution that works when using a FixtureAdapter or a real data source?
For reference, these are the versions of EmberJS and Ember-Data that I am using:
"ember": "~1.3.2",
"ember-data": "~1.0.0-beta.8"
I'll have to look back for the fixture adapter/serializer, but in the rest adapter/serializer you can define the primary key on the serializer. Within the app, it'll just be known as id, but when it deserializes/serializes it will transform it to the primary key name
App.ColorSerializer = DS.RESTSerializer.extend({
primaryKey: 'piano'
});
App.Color = DS.Model.extend({
color: DS.attr()
});
Example: http://emberjs.jsbin.com/OxIDiVU/774/edit