I'm using ember 2.7.0, and I am trying to set up my ember app with a currentUser.organization derived from the authenticated token. I am able to resolve the currentUser, but I'm unable to resolve the properties of the user's organization in my routes/controllers.
My user model:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
email: DS.attr('string'),
organization: DS.belongsTo('organization', { polymorphic: true, async: false } )
});
I've created a service which pulls the user like this:
//app/services/session-account.js
import Ember from 'ember';
import jwtDecode from 'npm:jwt-decode';
const { inject: { service }, RSVP } = Ember;
export default Ember.Service.extend({
session: service('session'),
store: service(),
loadCurrentUser() {
return new RSVP.Promise((resolve, reject) => {
const token = this.get('session.data').authenticated.access_token;
if (!Ember.isEmpty(token)) {
var token_payload = jwtDecode(token);
return this.get('store').findRecord('user', token_payload.user_id, { include: 'organization' }).then((user) => {
this.set('account', user);
this.set('organization', user.organization);
resolve();
}, reject);
} else {
resolve();
}
});
}
});
I'm triggering loadCurrentUser after the user logs in, and I've verified it successfully fetches the user from the back-end (and the organization data is included in the jsonapi response), but while I can inject the service into my controllers/routes and access the user and fetch its direct properties, I can't access any properties of the related organization, via either myservice.get('currentUser.organization.name') (comes back undefined), or myservice.get('currentOrganization.name'), which throws the following error:
Uncaught TypeError: Cannot read property '_relationships' of undefined.
If I load a user as a model and reference properties of the user.organization in a template, everything works fine- but on the javascript side, I can't get to the organization model.
EDIT:
I've subsequently tried the following variant:
return this.get('store').findRecord('user', token_payload.user_id, { include: 'organization' }).then((user) => {
this.set('currentUser', user);
user.get('organization').then((organization) => {
this.set('currentOrganization', organization);
}, reject);
resolve();
}, reject);
and this version (which draws from the ember guides relationship documentation) throws the error TypeError: user.get(...).then is not a function
I'd suggest to try:
1) Replace this.set('organization', user.organization); with this.set('organization', user.get('organization'));
2) Put console.log(user.get('organization')) after this.set('account', user); and look at output
Related
UPDATE:
Can anyone help? I have been pursuing this without luck for the better half of this week. I do notice that the client is generating two POSTs. I have added code for the adapter. Is there anywhere else I should be looking?
I am going through the video tutorial provided below and am unable to resolve two errors when I click the submit button to save data to the database.
No model was found for 'user'
Two POSTs are being generated. This results in an Assertion Failed error, which I suspect is because the ID returned from the server does not match the current ID on the front-end.
I see that the database has two new records. When I click on the submit button again then the application takes me back to the todo-items page where it shows the two records. Can anyone advise what I am doing wrong?
Current versions:
Ember : 3.2.2
Ember Data : 3.2.0
jQuery : 3.3.1
Ember Simple Auth : 1.7.0
Video tutorial (the error occurs at the 11:30 mark): https://www.youtube.com/watch?v=bZ1D_aYGJnU. Note: the author of the video seems to have gotten the duplicate POST issue to go away right at the end of the video, but I do not see how.
Component/forms/todo-item-form/component.js
import Component from '#ember/component';
export default Component.extend({
actions:{
save(){
this.get('submit')();
}
}
});
Component/forms/todo-item-form/template.hbs
<form {{action "save" on="submit"}}>
{{input placeholder="description" value=todoItem.description}}
<br />
{{#if todoItem.validations.isValid}}
<button type="submit">Add</button>
{{else}}
<button type="submit" disabled>Add</button>
{{/if}}
</form>
templates/s/todo-items/add.hbs
{{forms/todo-item-form
todoItem=model
submit=(route-action "submitAction")
}}
{{outlet}}
models/todo-item.js
import DS from 'ember-data';
import { validator, buildValidations } from 'ember-cp-validations';
const { attr, belongsTo } = DS;
const Validations = buildValidations({
description: [
validator('presence', true),
validator('length', {
min: 4
})
]
});
export default DS.Model.extend(Validations, {
description: attr('string'),
owner: belongsTo('person')
});
adapter/Application.js
import DS from 'ember-data';
import ENV from 'todo-list-client/config/environment';
const {computed, inject :{service} } = Ember;
export default DS.JSONAPIAdapter.extend({
session: service(),
namespace: ENV.APP.namespace,
host: ENV.APP.host,
headers: computed('session.data.authenticated.token', function() {
let token = this.get('session.data.authenticated.access_token');
return { Authorization: `Bearer ${token}` };
}),
})
routes/s/todo-items/add.js
import Route from '#ember/routing/route';
export default Route.extend({
model(){
return this.store.createRecord('todo-item');
},
actions: {
submitAction() {
this.get('controller.model')
.save()
.then(() => {
this.transitionTo('s.todo-items');
});
}
},
});
The author adds Ember-Data-Route at about 15m5s for the add.js route as a mixin. This cleans up after the model.
He starts the explanation at that point, adds it in over the next minute or two in the video:
https://youtu.be/bZ1D_aYGJnU?t=15m5s
import Ember from 'ember';
import DataRoute from 'ember-data-route';
export default Ember.Route.extend(DataRoute, {
model() {
return this.store.createRecord('todo-item');
},
actions: {
save() {
this.get('controller.model')
.save()
.then(() => {
this.transitionTo('s.todo-items');
});
}
},
});
i have a service to manage all the errors and alerts in my app. and the code looks like this
Service
import Ember from 'ember';
export default Ember.Service.extend({
messages: null,
init() {
this._super(...arguments);
this.set('messages', []);
},
add: function (severity, msg, messageType) {
if (severity === 'error') {severity = 'danger';}
var msgObject ={
severity: severity,
messageType: messageType,
msg: msg,
msgId: new Date()
};
this.get('messages').pushObject(msgObject);
},
remove(msgId) {
this.get('messages').removeObject(msgId);
},
empty() {
this.get('messages').clear();
}
});
Component
import Ember from 'ember';
export default Ember.Component.extend({
messageType:'global',
messageHandler: Ember.inject.service(),
messages: function(){
return this.get('messageHandler.messages').filterBy('messageType',this.get('messageType'));
}.property('messageHandler.messages'),
actions : {
dismissAllAlerts: function(){
this.get('messageHandler').empty();
},
dismissAlert: function(msgId){
this.get('messageHandler').remove(msgId);
}
}
});
Initializer
export function initialize(container, application) {
application.inject('component', 'messageHandler', 'service:message-handler');
}
export default {
name: 'message-handler',
initialize : initialize
};
Template
import Ember from 'ember';
export default Ember.Component.extend({
messageType:'global',
messageHandler: Ember.inject.service(),
messages: function(){
return this.get('messageHandler.messages');
}.property('messageHandler.messages'),
actions : {
dismissAllAlerts: function(){
this.get('messageHandler').empty();
},
dismissAlert: function(msgId){
this.get('messageHandler').remove(msgId);
}
}
});
and whenever there is an error i will add it like this
this.get('messageHandler').add('error',"Unable to get ossoi details","global");
my problem is the filterBy in the component is not working. if i remove the filterBy() it works and i can see the error in the template. am kinda new to ember so if anyone can help me figure out what am missing here or if there is a better way of doing this please let me know
filterBy usage is good and it should be working well. but messages computed property will not be recomputed whenever you add/remove item from messageHandler.messages.
messages: Ember.computed('messageHandler.messages.[]', function() {
return this.get('messageHandler.messages').filterBy('messageType', this.get('messageType'));
}),
In the above code I used messageHandler.messages.[] as dependant key for the messages computed property so that it will be called for add/remove items.
Refer:https://guides.emberjs.com/v2.13.0/object-model/computed-properties-and-aggregate-data/
Computed properties dependent on an array using the [] key will only
update if items are added to or removed from the array, or if the
array property is set to a different array.
I've been successfully calling Meteor methods until I created a new Mongo collection. Both collections are found under /imports/collections/, so I know it's available to both client and server.
Here is the Meteor.method, which is pretty much the same as my working collection.:
import { Mongo } from 'meteor/mongo';
Meteor.methods({
'messages.insert': function(data) {
return Messages.insert({
otherCollectionId: data.otherCollectionId,
type: data.type,
createdAt: new Date(),
sender: this.userId,
text: data.text
});
}
});
export const Messages = new Mongo.Collection('messages');
Here's how I call it:
import React from 'react';
import { Messages } from '../../imports/collections/messages';
// other imports
export default class Chat extends React.Component {
// other code
handleComposeClick() {
if (this.refs.text) {
let data = {
playlistId: this.props.playlist._id,
type: 'user',
text: this.refs.text.value
};
Meteor.call('messages.insert', data, (error, playlistId) => {
if (!error) {
this.setState({error: ''});
this.refs.text.value = '';
} else {
this.setState({ error });
console.log(error);
}
});
}
}
// render()
}
Whenever I click and trigger handleComposeClick(), I get this error:
errorClass {error: 404, reason: "Method 'messages.insert' not found", details: undefined, message: "Method 'messages.insert' not found [404]", errorType: "Meteor.Error"}
Remember that anything inside the /imports folder will not work unless it's actually imported, either with this syntax:
import somethingINeed from '/imports/wherever/stuff';
import { somethingElseINeed } from '/imports/wherever/things';
or:
import '/imports/server/stuff';
So for methods, you may want to set up a structure where you have the following:
/lib/main.js
import '../imports/startup/lib';
/imports/startup/lib/index.js
import './methods';
// any other shared code you need to import
/imports/startup/lib/methods.js
import { Meteor } from 'meteor/meteor';
import { Messages } from '/imports/collections/messages'; // don't define your collections in a methods file
Meteor.methods({
'messages.insert': function(data) {
return Messages.insert({
otherCollectionId: data.otherCollectionId,
type: data.type,
createdAt: new Date(),
sender: this.userId,
text: data.text
});
}
});
Though if I were you, I'd use validated methods, where you actually import the method you want to use in order to use it, e.g.:
import { messagesInsert } from '/imports/common-methods/message-methods';
// code...
messagesInsert.call({ arg1, arg2 }, (error, result) => { /*...*/ });
With this structure, you would instead have /server/main.js import ../imports/startup/server which would import ./methods, which (in my particular project) looks like this:
// All methods have to be imported here, so they exist on the server
import '../../common-methods/attachment-methods';
import '../../common-methods/comment-methods';
import '../../common-methods/tag-methods';
import '../../common-methods/notification-methods';
import '../../features/Admin/methods/index';
import '../../features/Insights/methods';
import '../../features/Messages/methods';
Keep in mind, this doesn't actually execute the methods, it just makes sure they're defined on the server, so that when you import these validated methods on the client side and run them, it doesn't bomb out saying the method can't be found on the server side.
Import 'Messages' in your server side (/server/main.js)
Shout out to #MasterAM for helping me find my typo. Turns out I just had an incorrect path in /server/main.js
I have the following problem with getting the current user using an instance initializer, a service and ember-simple-auth library.
More specifically, I have managed to get all the attributes of my service "user" but only when I refresh the page after login.
I would like the promise that my service returns to be immediately accessible right after login.
My code snippets are available below:
app/instance-initializers/current-user.js
export function initialize(appInstance) {
appInstance.inject('route', 'account', 'service:account');
appInstance.inject('controller', 'account', 'service:account');
appInstance.inject('template', 'account', 'service:account');
}
export default {
name: 'account',
initialize: initialize
};
app/services/account.js
import Ember from 'ember';
export default Ember.Service.extend({
session: Ember.inject.service('session'),
store: Ember.inject.service('store'),
loadCurrentUser() {
const accountId = this.get('session.data.authenticated.auth_token');
console.log("This is my account", accountId);
if (!Ember.isEmpty(accountId)) {
this.get('store').findRecord('profile', accountId).then((profile) => {
this.set('user', profile);
console.log("account_group",this.get('user.user_group'));
console.log("account_username",this.get('user.username'));
});
}
return this.get('user')
}
});
Then I call the loadCurrentUser function in my application route:
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin,{
beforeModel(){
console.log("This is my currentUser", this.get("account").loadCurrentUser());
},
});
And finally in my template I can access the username of my user like this:
Logged in as: {{account.user.username}}
but only when I refresh my page.
I assume that I call my function "loadCurrentUser()" in the wrong place but I can't find anywhere a proper solution.
Thanks in advance.
I think you should call loadCurrentUser() when the authenticator resolves the promise. Something like:
that.get('session').authenticate('authenticator:xxxx',{}).then(() => {
this.get("account").loadCurrentUser();
});
I have a model, let's call it Task, that has a property assignee. I have another model Admin, that's a set of admins. On task, I want to add a property, admin, that looks up the assignee from the admins by email and returns that admin.
The primary key on Admin is not email, and in Ember Model, it doesn't look like it's possible to create a belongsTo association on any key other than the primary key. The reason I send over email rather than an id is that the admin doesn't always exist.
The Task model looks something like this:
import Em from 'ember';
import Admin from 'project/models/admin';
import PromiseObject from 'project/lib/promise-object';
var Task = Em.Model.extend({
id: Em.attr(),
name: Em.attr(),
assignee: Em.attr(),
admin: function() {
return PromiseObject.create({
promise: Admin.fetch({ email: this.get('assignee') })
}).then(function(json) {
return Admin.create(json);
}, function() {
return null;
});
}.property('assignee'),
adminName: Em.computed.oneWay('admin.name')
});
export default Task;
PromiseObject is just extending the PromiseProxyMixin, and looks like this:
import Em from 'ember';
export default Em.ObjectProxy.extend(Em.PromiseProxyMixin);
When I try to access the property, I can see the network requests for the admins going across the wire, and I can see the successful response with the correct details included. However, null is returned for the promise.
I'm looking to include {{task.adminName}} in my templates. I'm a little stumped at this point on the best way of resolving the admin promise correctly in my model.
You aren't returning the PromiseObject, you're returning a chained promise. You should just return the PromiseObject.
admin: function() {
var promise = $.getJSON("/admin").then(function(json) {
return Admin.create(json);
}, function() {
return null;
});
return PromiseObject.create({
promise: promise
});
}.property('assignee'),
Example: http://emberjs.jsbin.com/nobima/12/edit
Using Ember Model fetch with an object returns a collection, not a model (unless of course you're fetch isn't Ember Model). So json isn't what's being returned at that point. You probably want to do something along these lines.
admin: function() {
var promise = Admin.fetch({ email: this.get('assignee') }).then(function(collection){
return collection.get('firstObject');
}, function() {
return null;
});
return PromiseObject.create({
promise: promise
});
}.property('assignee'),