Fetching records and nested relations - ember.js

I have a route in my app (repo) where i fetch bookings belonging to a user, I also include rentals and rentals.rental-ratings in the request. Now, a rental (which belongsTo a Booking) hasMany rentalRatings, and rentalRatings belongsTo Users. In the template I have a component that accepts booking and a rentalRating as arguements. How can I get a rating that belongsTo the booking and the currentUser?
Bookings route:
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
sessionAccount: Ember.inject.service(),
model() {
const currentUser = this.get('sessionAccount.currentUser');
return this.get('store').query('booking', {
filter: {
'user_id': currentUser.id
},
include: 'rental,rental.rental-ratings'
});
}
});
Bookings template:
<h1>Your bookings</h1>
{{#each model as |booking|}}
{{booking-card booking=booking rating="??? what goes here???"}}
{{/each}}

Easiest way to achieve this, is to use the ember-composable-helpers.
Move the sessionAccount service into the controller. In your template you can just use the filter-by helper:
{{#each model as |booking|}}
{{booking-card booking=booking rating=(get (filter-by "user" sessionAccount.user booking.rental.rentalRatings) "firstObject")}}
{{/each}}

Related

set alias for `model` hook

HELP
If there is a model hook in app/routes/post.js say
model() {
return this.store.query('post');
}
in template the returned promised is accessed using
{{#each model as |post|}}
...
{{/each}}
Is there any way to set alias for the model? Something like this in route or controller?
posts: alias('model')
So I can access the returned promise in the template as
{{#each posts as |post|}}
...
{{/each}}
Is this something which is already present or something that got missed from ember documentation?
you can create alias for model property in your controller,
import Controller from '#ember/controller';
import { alias } from '#ember/object/computed';
export default Controller.extend({
posts: alias('model')
})
or using setupController in your route,
export default Route.extend({
setupController(controller, model) {
controller.set('posts', model);
},
});
Reference:
alias api documentation - alias computed property
alias your model - alias-model-rule

No access of model inside the template

I have a post route post.index inside the route,
import Ember from 'ember';
export default Ember.Route.extend({
model() {
this.get('store').findAll('post');
}
});
Inside the post hbs,
{{debugger}}
<ul>
{{#each model as |post|}}
{{post/post-item
post=post
}}
{{/each}}
</ul>
However, when I inspect the context using debugger I see null for model.
Why?
Your model should return the result.
model() {
return this.get('store').findAll('post');
}

ember - loading model data in route for power sort?

I have a very simple set up right now. I have a book model that has a name and author. I'm trying to create a simple form that will create a new book. For the author I'm using power select to load the authors from the author model. The form set up looks like this:
<form {{action "save" on="submit"}}>
{{input value=model.title placeholder="Title"}}<br>
{{#power-select class="select"
selected=model.author
options=authors
onchange=(action (mut model.author)) as |author|}}
{{author.name}}
{{/power-select}}
<input type="submit" value="Save">
</form>
However I'm having trouble setting up the route to get this working. So far no authors show up in the select, even though there are authors stored in my database. My route looks like this:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.createRecord('book');
},
actions: {
save() {
this.modelFor(this.routeName).save();
}
},
store: Ember.inject.service(),
authors: Ember.computed({
get() {
return this.get('store').findAll('author');
}
}).readOnly()
});
First of all, how should I properly load data from the author model in the route for the books/new route? Secondly, should I be doing this in the route? From what I have read, and what people have told me, loading model data should be done in the route.
Move authors property to corresponding controller.
Also you don't need to add readonly.
So in controller :
authors: Ember.computed(function(){
return this.get('store').findAll('author');
})
And for loading model in route. Yes you should load that model which is to be a resource manipulating, in route. So now you're doing it right.
1) Using Ember.RSVP.hash inside route model hook
your route file-> I assume books/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
newBook : this.store.createRecord('book'),
authors : this.store.findAll('author')
});
},
actions: {
save() {
this.modelFor(this.routeName).newBook.save();
}
}
});
and inside template you can access authors by using model.authors. and title by using model.newBook.title
<form {{action "save" on="submit"}}>
{{input value=model.newBook.title placeholder="Title"}}<br>
{{#power-select class="select"
selected=model.newBook.author
options=model.authors
onchange=(action (mut model.newBook.author)) as |author|}}
{{author.name}}
{{/power-select}}
<input type="submit" value="Save">
</form>
2) Like ebrahim suggested, you can have the below code in required controller,
authors: Ember.computed(function(){
return this.store.findAll('author');
})
3)As author model data is going to be shared data model for authors,books,books.new routes. so you can keep it in service and access it from all the required routes.
authors-service.js -> In service
import Ember from 'ember';
export default Ember.Service.extend({
store:Ember.inject.service(),
authors: undefined,
init(){
this._super(...arguments);
this.get('store').findAll('author', { reload: true }).then(function(results){
this.set('authors',results); //As this is going to be Ember.enumerables, you can iterate and get the data.
});
}
});
You can access authors from authors-service.js in any where by injecting it
authorsService:Ember.inject.service(). I guess in your case, you need to create controller books/new.js for books/new.hbs template,
books/new.js -> controller file
import Ember from 'ember';
export default Ember.Controller.extend({
authorsService:Ember.inject.service(),
authors: Ember.computed.alias('authorsService.authors');
});
Inside books/new.hbs template you can access authors property.

Calling a relationship in template returns a DS.PromiseObject

I'm using firebase in combination with Ember CLI. I have the following setup:
ember.debug.js:6401 DEBUG: Ember : 2.4.5
ember.debug.js:6401 DEBUG: Ember Data : 2.5.1
ember.debug.js:6401 DEBUG: Firebase : 2.4.2
ember.debug.js:6401 DEBUG: EmberFire : 1.6.6
ember.debug.js:6401 DEBUG: jQuery : 2.2.3
I have two simple models
<!-- app/models/user.js -->
import Model from 'ember-data/model';
export default Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
email: DS.attr('string'),
profile: DS.belongsTo('profile', {async: true})
});
And a second model for my profile
<!-- app/models/profile.js -->
import Model from 'ember-data/model';
export default Model.extend({
companyName: DS.attr('string'),
user: DS.belongsTo('user', {async: true})
});
I have the following profile route:
<!-- app/routes/profile.js -->
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.query('user', { orderBy: 'email', equalTo: this.get('session.currentUser.email')}).then(function(user){
console.log(user);
return user;
});
}
});
I check if the current session email address is equalTo a email address in the database. And return the user objet. This is working. (don't know if this is the right way to do this?)
In my profile handlebars template i have the following code.
<!-- app/templates/profile.hbs -->
{{#each model as |user|}}
{{user.firstName}}
{{user.lastName}}
{{user.profile}}
{{/each}}
This returns the following on screen:
frank spin <DS.PromiseObject:ember545>
My guess is that the relationship data has not yet been received. I don't know how to solve this issue. And second question: Is my checking for the current logged in user the right way?
You're correct that the relationship data has not been received. But if you write your template like this, you should still see the profile information when it loads:
{{#each model as |user|}}
{{user.firstName}}
{{user.lastName}}
{{user.profile.companyName}}
{{/each}}
PromiseObjects in templates
The goal of promise objects (and promise arrays) is to allow you to bind data in Ember before it's loaded, and have those bindings update once the promise resolves. This is great for secondary-importance information, which can safely be rendered after the rest of the page loads.
If you try to render your profile model properties when the promise has not resolved, you'll get a blank space. You can display loading state information using the isPending property:
{{#each model as |user|}}
{{user.firstName}}
{{user.lastName}}
{{#if user.profile.isPending}}
<span class="spinner">…</span>
{{else}}
{{user.profile.companyName}}
{{/if}}
{{/each}}
And you can also use the isRejected property to detect API failures, and allow users to retry.
Load before render
If this asynchronous behaviour is not what you want, you can force the relationship promise to resolve before rendering the template in the afterModel hook of your route:
export default Ember.Route.extend({
model() {
return this.store.query('user', { orderBy: 'email',
equalTo: this.get('session.currentUser.email')});
},
afterModel(users) {
return Ember.RSVP.all(users.invoke('get', 'profile');
}
});
(With a simpler single model, you could just write return model.get('profile') in the afterModel hook.)
Any promise returned from afterModel will block loading of the route until it resolves. Then your template will always have the profile of the user available when rendering.

Ember identifying current user

I'm trying to build a simple chat app using Ember CLI with a rails API backend, using simple-auth-devise. I am very new to Ember. I have the following setup:
A conversation model which has_many messages. Each message belongs_to a user.
A conversations/show route which loads a conversation and displays all messages
The problem I have is figuring out which messages were written by the current user. I can identify the current user in the route and pass this to the controller, but I'm not sure how to do something in the template which is conditional on this. Here is the code:
Route for conversations/show:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return this.store.find('conversation', params.conversation_id);
},
// Get the current user
setupController: function(controller, model){
this._super(controller, model);
var currentUser = this.get('session').get('currentUser');
controller.set("currentUser", currentUser);
}
});
Then in the template I want to do something like this:
{{#each message in messages}}
{{message.body}}
{{#if message.isByCurrentUser }}
Written by me!
{{/if}}
{{/each}}
I don't know how or where to define that message.isByCurrentUser method to use the currentUser variable defined in the route.
Many thanks in advance.
I would recommend you to separate message resource and let it have its own template and controller.
//messages.hbs
{{#each message in messages}}
{{render "message" message}}
{{/each}}
//message.hbs
{{message.body}}
{{#if isByCurrentUser }}
Written by me!
{{/if}}
//message controller
import Ember from 'ember';
export default Ember.Controller.extend({
currentUser: Ember.computed.alias('session.currentUser'),
isByCurrentUser: function() {
return this.get('model.creator.id') === this.get('currentUser.id');
}.property('model.creator', 'currentUser')
});
P.S: I assumed your message model has attribute named creator. change it due to your model
simple-auth injects session into controllers and routes so you don't have to set it explicitly.