Ember-data: Saving parent to child via a select box - ember.js

I've went over a lot of examples both here on SO and in some guides/blogs. Nothing seems to work.
I have a customer that hasMany loads
currently the code is:
route
export default Ember.Route.extend({
setupController: function(controller, model) {
controller.setProperties(model);
},
model: function() {
return Ember.RSVP.hash({
content: this.store.createRecord('truck-load'),
customerList: this.store.findAll('customer'),
equipmentList: this.store.findAll('equipment-list')
});
},
resetController(controller, isExisting) {
if (isExisting) {
var model = controller.get('model');
if (model.get('isNew')) {
model.destroyRecord();
}
}
}
});
select box in the template - materialize add on for ember-cli
{{md-select content=customerList
value=model.customer
label="Customer"
prompt="Please Choose a Customer..."
optionLabelPath='content.name'
optionValuePath='content.id'}}
Current controller - I've tried this many ways
export default Ember.Controller.extend({
actions: {
save() {
var truckload = this.get('model');
this.get('model.customer').then((customer) => {
truckload.set('customer', customer);
truckload.save().then((load) => {
this.get('notify').success('Truck Load Created');
this.transitionToRoute('truck-loads.show', load.id);
});
});
JSON for my JSON-API server running Elixir/Phoenix
Parameters: %{"data" => %{"attributes" => %{"pro_number" => "423432", "special" => nil, "status" => nil},
"relationships" => %{"customer" => %{"data" => nil},
"equipment_list" => %{"data" => nil}}} }
customer (and equipment-list) are both coming over nil.

This fixed it.
1) Settings the drop down result as a controller property
2) Accessing this to lookup the model and set it.
selectedCustomer: null,
selectedEquipment: null,
actions: {
save() {
var truckload = this.get('model');
var customer_id = this.get('selectedCustomer');
var equipment_id = this.get('selectedEquipment')
this.store.findRecord('customer', customer_id).then((customer) => {
truckload.set('customer', customer);
this.store.findRecord('equipmentList',equipment_id).then((equipment) => {
truckload.set('equipmentList', equipment);
truckload.save().then((load) => {
this.get('notify').success('Truck Load Created');
this.transitionToRoute('truck-loads.show', load.id);
});
});
});
return false;
},
I doubt this is the best way to do it - but - it DOES work.

Related

How to fix 'service injection' for EmberJS integration test?

I have a tasks-table component that uses 'current-user' service. I inject the service into the component as displayed below
import { inject as service } from '#ember/service';
export default Component.extend({
currentUser: service(),
showBatchAction: computed('currentUser.user.directRoles.#each.title', function() {
return this.get('currentUser.user.directRoles').toArray().some((role) => {
return (role.id == 13) || (role.id == 15)
});
}),
});
The code works fine however, the integration test fails.
Test:
module('Integration | Component | tasks-table export csv button', function(hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function() {
run(() => {
this.owner.unregister('service:current-user');
});
this.owner.register('serivce:current-user', Service.extend({
user: EmberObject.create({
first_name: 'Bob',
last_name: 'Newby',
role: 'client',
directRoles: {title: 'employee', id: 1}
})
}));
});
test('it renders', async function(assert) {
var component = this.subject({
authManager: stubMyService.create()
});
await render(hbs `{{tasks-table}}`);
assert.equal(this.$('.export-csv').text().trim(), 'Export as CSV');
});
});
The error I'm getting is: TypeError: undefined is not an object (evaluating 'this.get('currentUser.user.directRoles').toArray')
directRoles is not an array? How come you loop through (some method) over an object. By saying object.toArray() does not work.

Property in Ember Service undefined

I have a service for a business calendar where a property is not available to a computed property but is available in a method.
In the code below the property calendars is not defined when the computed businessYear property is accessed. On the other hand, calendars is defined when the year() method is called.
The currentYear property is used elsewhere so init() is called and the store promise is resolved before businessYear is accessed.
I've debugged this and developer tools shows that this.calenders is defined in watch variables but is still undefined in the computed property. It's almost like the computed property is being executed in another context. I'm probably missing something really basic but just don't see it.
app/services/business-calendar.js
import Ember from 'ember';
import moment from 'moment';
const { computed, inject: { service }, get, set } = Ember;
const findCalendar = (date, calendars) => {
let p1, p13
return calendars.find(function (item, index, enumerable) {
p1 = moment(get(item, 'p1'));
p13 = moment(get(item, 'p13'));
return moment.range(p1, p13).contains(date);
})
}
export default Ember.Service.extend({
store: service(),
calendars: [],
currentDate: computed(() => {
return moment();
}),
currentYear: computed('currentDate', () => {
return moment(get(this, 'currentDate')).year();
}),
businessYear: computed(() => {
let calendar = findCalendar(moment(), get(this, 'calendars'));
return get(this.calendar, 'year');
}),
init() {
this._super(...arguments);
get(this, 'store').findAll('calendar').then((recs) => {
set(this, 'calendars', recs);
})
},
year(date) {
let d = moment(date);
var calendar = findCalendar(d, get(this, 'calendars’));
return get(calendar, 'year');
}
});
Thanks to feedback on the Ember Slack feed the issue is the use of arrow functions in the computed properties. This creates a different 'this' or context. The working code is below. Note even thought the currentDate and currentYear computed properties were working, all arrow functions were removed for code consistency.
import Ember from 'ember';
import moment from 'moment';
const { computed, inject: { service }, get, set } = Ember;
const findCalendar = (date, calendars) => {
let p1, p13
return calendars.find(function (item, index, enumerable) {
p1 = moment(get(item, 'p1'));
p13 = moment(get(item, 'p13'));
return moment.range(p1, p13).contains(date);
})
}
export default Ember.Service.extend({
store: service(),
calendars: [],
currentDate: computed(function() {
return moment();
}),
currentYear: computed('currentDate', function() {
return moment(get(this, 'currentDate')).year();
}),
businessYear: computed(function() {
let calendar = findCalendar(moment(), get(this, 'calendars'));
return get(this.calendar, 'year');
}),
init() {
this._super(...arguments);
get(this, 'store').findAll('calendar').then((recs) => {
set(this, 'calendars', recs);
})
},
year(date) {
let d = moment(date);
var calendar = findCalendar(d, get(this, 'calendars’));
return get(calendar, 'year');
}
});

Any easy way to test Ember loading substates?

Is there a easy way in the route so I can test loading substates in Ember 2
I have tried this but it doesn't work. In the originating route, I have:
actions: {
willTransition(transition) {
this.sleep(5000);
}
},
sleep(time) {
return new RSVP.Promise((resolve) => setTimeout(resolve, time));
}
To see loading substate,
model() {
return new RSVP.Promise((resolve, reject) => {
Ember.run.later(() => {
resolve("failed to load application model");
}, 500);
});
}
To see error loading substate,
model() {
return new RSVP.Promise((resolve, reject) => {
Ember.run.later(() => {
reject(
new Error('failed to load application model')
);
}, 500);
});
}
You should have corresponding hbs file to show. refer ember official guide. https://guides.emberjs.com/v2.8.0/routing/loading-and-error-substates/

Is it normal to invoke route.refresh() in willTransition hook?

This question is related to EmberJS best practice. I have two routes:
/diners
/diners/register
Now that I want to reload the model of /diners after transitioning from /diners/register (as a result of a successful diner registration, using transitionToRoute).
The questions are:
I use this.refresh() in actions: willTranstion; Is it normal?
Should I do it this way?
What are other ways to refresh the model of
parent routes after transitioning from a child route?
How can I can
maintain the data consistency on client side when POSTing a new
record to back end?
Ember version info:
DEBUG: Ember : 1.13.11
DEBUG: Ember Data : 1.13.15
DEBUG: jQuery : 1.11.3
UPDATED with code
Diner Register Controller:
export default Ember.Controller.extend({
actions: {
save () {
var data = this.model;
var record = this.store.createRecord('diner', data);
record.save().then(onFulfill, onFail);
var self = this;
function onFulfill () {
Ember.$('.ui.modal').modal('destroy');
self.transitionToRoute('diners');
}
function onFail (e) {
console.error(e.message || 'Unknown error');
}
},
cancel () {
Ember.$('.ui.modal').modal('close');
this.transitionTo('diners');
}
}
});
Diner List Route:
export default Ember.Route.extend({
queryParams: {
page: {
refreshModel: true
}
},
setupController (controller, model) {
let pageSize = 10;
let page = this.paramsFor('diners').page;
let total = model.meta.total;
controller.set('model', model);
if (page > 1) {
controller.set('prevPage', page - 1);
}
else {
controller.set('prevPage', undefined);
}
if (total > pageSize * page) {
controller.set('nextPage', page + 1);
}
else {
controller.set('nextPage', undefined);
}
},
model (params) {
let page = params.page || 1;
return this.store.find('diner', {
page
});
},
actions: {
willTransition () {
this.refresh();
}
}
});

Acceptance tests aren't resetting

I have some acceptance tests that test a component. If I run each test separately, they pass just fine. However, when I run the tests together, they fail because they're retaining the values from the previous tests.
Here is my code:
filter-test.js
module('Integration - Filter', {
beforeEach: function() {
App = startApp();
server = setupPretender();
authenticateSession();
},
afterEach: function() {
Ember.run(App, 'destroy');
server.shutdown();
}
});
test('filters can be saved and selected via the dropdown', function(assert) {
visit('/status');
fillIn('.filter-status', 'Not Completed');
fillIn('.filter-id', '444');
andThen(function() {
assert.ok(find('.status-title').text().includes('2 of 7'), 'the new filter filters the results');
});
});
test('only saved filters can be edited', function(assert) {
visit('/status');
fillIn('.filter-id', 'not an id');
click('.update-filter');
andThen(function() {
assert.equal(find('.alert').text(), 'Not a Saved FilterĂ—');
});
});
test('filter values can be cleared', function(assert) {
visit('/status');
fillIn('.filter-id', '444');
fillIn('.filter-status', 'Completed');
click('.clear-filters');
andThen(function() {
// this fails because `.filter-id` is set to 'not an id':
assert.equal(find('.filter-id').val(), '', 'filter for was reset to its initial value');
// this also fails because `.filter-status` is set to 'Not Completed':
assert.equal(find('.filter-status').val(), 'Everything', 'status dropdown was reset to its initial value');
});
});
ps-filter/component.js
export default Ember.Component.extend({
classNames: ['panel', 'panel-default', 'filter-panel'],
currentFilter: null,
initialValues: null,
didInsertElement: function() {
this.set('initialValues', Ember.copy(this.get('filterValues')));
},
actions: {
saveFilter: function(name) {
var filters = this._getFilterList();
var filterValues = this.get('filterValues');
if (!Ember.isEmpty(name)) {
filters[name] = filterValues;
this.sendAction('updateFilter', filters);
this.set('currentFilter', name);
}
},
updateFilter: function() {
var filterValues = this.get('filterValues');
var currentFilter = this.get('currentFilter')
var filters = this.get('userFilters');
filters[currentFilter] = filterValues;
this.sendAction('updateFilter', filters);
},
clearFilters: function() {
this.set('currentFilter', null);
this.set('filterValues', Ember.copy(this.get('initialValues')));
}
}
});
status/controller.js
export default Ember.ArrayController.extend({
filterValues: {
filterStatus: 'Everything',
filterId: 'id',
},
userFilters: Ember.computed.alias('currentUser.content.preferences.filters')
});
status/template.hbs
<div class="row">
{{ps-filter
filterValues=filterValues
userFilters=userFilters
updateFilter='updateFilter'
}}
</div>
From what I gathered, it seems that it sets the initialValues to the filterValues left over from the previous test. However, I thought that the afterEach was supposed to reset it to its original state. Is there a reason why it doesn't reset it to the values in the controller?
Note that the component works normally when I run it in development.
Ember versions listed in the Ember Inspector:
Ember : 1.11.3
Ember Data : 1.0.0-beta.18
I'm running Ember CLI 0.2.7.
Edit
I don't think this is the issue at all, but here is my pretender setup:
tests/helpers/setup-pretender.js
export default function setupPretender(attrs) {
var users = [
{
id: 1,
name: 'ttest',
preferences: null
}
];
var activities = [
{
id: 36874,
activity_identifier: '18291',
status: 'Complete'
}, {
id: 36873,
activity_identifier: '82012',
status: 'In Progress'
}, {
id: 35847,
activity_identifier: '189190',
status: 'In Progress'
}, {
id: 35858,
activity_identifier: '189076',
status: 'Not Started'
}, {
id: 382901,
activity_identifier: '182730',
status: 'Not Started'
}, {
id: 400293,
activity_identifier: '88392',
status: 'Complete'
}, {
id: 400402,
activity_identifier: '88547',
status: 'Complete'
}
];
return new Pretender(function() {
this.get('api/v1/users/:id', function(request) {
var user = users.find(function(user) {
if (user.id === parseInt(request.params.id, 10)) {
return user;
}
});
return [200, {"Content-Type": "application/json"}, JSON.stringify({user: user})];
});
this.get('api/v1/activities', function(request) {
return [200, {"Content-Type": "application/json"}, JSON.stringify({
activities: activities
})];
});
this.put('api/v1/users/:id', function(request) {
var response = Ember.$.parseJSON(request.requestBody);
response.user.id = parseInt(request.params.id, 10);
var oldUser = users.find(function(user) {
if (user.id === parseInt(request.params.id, 10)) {
return user;
}
});
var oldUserIndex = users.indexOf(oldUser);
if (oldUserIndex > -1) {
users.splice(oldUserIndex, 1);
users.push(response.user);
}
return [200, {"Content-Type": "application/json"}, JSON.stringify(response)];
});
});
}
When I run the tests, it fails because it reset the value to the one in the previous test. For example, when I run 'filter values can be cleared', the .filter-id input has the same .filter-id value from 'only saved filter can be edited. If I change the value in 'only saved filters can be edited'back to '', the 'filter values can be cleared' test passes.
Basically, the component sets the initialValues property when it first inserts the element. It's set to a copy of the filterValues property, so it should be set to the controller's filterValues property, and shouldn't change. However, it seems that the modified filterValues property is carried over to the next test, which means that initialValues is set to that modified property when it rerenders. So, the test rerenders the templates, but retains the modified values in the controller and component.
I can make the tests pass by creating an initialValues property in the controller and passing that into the component, but that'd mean having duplicate properties in the controller (since filterValues and initialValues would have the same values).
I could modify the user record in the component, but I thought we're supposed to only modify records in the controller or router. Besides, isn't the afterEach hook supposed to reset the app?