I have the following afterRemote method setup for the depot model:
Depot.afterRemote('find', function(context, depots, next) {
context.result.forEach(depot => {
console.log(depot.drivers);
depot.address = depot.street_name + ' ' + depot.door_number;
depot.driver_count = depot.drivers.length;
});
next()
})
In the json response depot.drivers contains proper data.
However when I try to access depot.drivers in the above method I get some weird output:
{ [Function: f]
_receiver: { postal_code: '1216BS',
door_number: '3',
street_name: 'Straat naam',
city: 'Hilversum',
lat: null,
lng: null,
created: 2018-04-03T01:49:12.000Z,
id: 23,
distributor_id: 2,
distributor: { sys_id: 1,
name: 'distributeur naam',
contact_person: 'Persoon B',
phone: '000-000000',
email: 'info#d.nl',
created: 2018-03-06T00:22:33.000Z,
id: 2 },
drivers: List [] },
_scope: { where: { hub_id: 23 } },
_targetClass: 'driver',
find: [Function],
getAsync: [Function],
build: [Function: bound method],
create: [Function: bound method],
updateAll: [Function: updateAll],
destroyAll: [Function: destroyAll],
findById: [Function: bound method],
findOne: [Function: findOne],
count: [Function: count],
destroy: [Function: bound method],
updateById: [Function: bound method],
exists: [Function: bound method] }
I would eventually like to add a depot.drivers_count property so I can display the amount of drivers connected to a specific depot in a front-end table.
Anyone has an idea how to do this?
Looks like the datas are not converted to JSON.
I would try this:
context.result.forEach(depot => {
depot = depot.toJSON();
console.log(depot.drivers);
depot.address = depot.street_name + ' ' + depot.door_number;
depot.driver_count = depot.drivers.length;
});
In Loopback every model relationship can be accessed using a callback function because i/o is async. Do like this:
depot.drivers((err, drivers) => {
// handle the error
// do something with the drivers
})
You can also use the Driver model operation hooks to track data changes and update the related Depot.drivers_count property:
Driver.observe('after save', function(ctx, next) {
// ctx.instance is the created or updated object
if (ctx.isNewInstance) {
// A new record was saved
// Implementation depends on your models relationships
} else {
// A record was updated
// Implementation depends on your models relationships
}
next()
})
Driver.observe('after delete', (ctx, next) => {
// Implementation depends on your models relationships
next()
})
More information here Loopback Operation hooks - After Save
Related
I'm using Ember CLI Storybook to create a story of a component than internally relies upon services that communicate to the internet, to fetch and post information to the backend. The way I'm doing that is using ember-ajax.
I see how to mock an ember model from this section but wondering if there is a workaround for ember ajax service.
I like to use mswjs.io for mocking remote requests. It uses a service worker so you can still use your network log as if you still used your real API.
I have an example repo here showing how to set it up: https://github.com/NullVoxPopuli/ember-data-resources/
But I'll copy the code, in case I change something.
Now, in tests, you'd want something like this: https://github.com/NullVoxPopuli/ember-data-resources/blob/main/tests/unit/find-record-test.ts#L17
module('findRecord', function (hooks) {
setupMockData(hooks);
But since you're using storybook, you'd instead want the contents of that function. (And without the setup/teardown hooks unique to tests)
https://github.com/NullVoxPopuli/ember-data-resources/blob/main/tests/unit/-mock-data.ts#L22
import { rest, setupWorker } from 'msw';
let worker;
export async function setupMockData() {
if (!worker) {
worker = setupWorker();
await worker.start();
// artificial timeout "just in case" worker takes a bit to boot
await new Promise((resolve) => setTimeout(resolve, 1000));
worker.printHandlers();
}
let data = [
{ id: '1', type: 'blogs', attributes: { name: `name:1` } },
{ id: '2', type: 'blogs', attributes: { name: `name:2` } },
{ id: '3', type: 'blogs', attributes: { name: `name:3` } },
];
worker.use(
rest.get('/blogs', (req, res, ctx) => {
let id = req.url.searchParams.get('q[id]');
if (id) {
let record = data.find((datum) => datum.id === id);
return res(ctx.json({ data: record }));
}
return res(ctx.json({ data }));
}),
rest.get('/blogs/:id', (req, res, ctx) => {
let { id } = req.params;
let record = data.find((datum) => datum.id === id);
if (record) {
return res(ctx.json({ data: record }));
}
return res(
ctx.status(404),
ctx.json({ errors: [{ status: '404', detail: 'Blog not found' }] })
);
})
);
}
Docs for msw: https://mswjs.io/
I'm recording this to document the answer to a problem that took me several hours to solve. Scenario:
I'm using two mutation queries on a single component in React Apollo-Client. This is a component wrapped into a larger component to form a page. Something like this (this is not the actual code, but it should give the idea):
import { compose } from 'react-apollo';
// submitNewUser contains
// postedBy
// createdAt
// content
// submitRepository contains
// otherProp
const thisProps1 = {
name: 'mutation1',
props: ({ ownProps, mutation1 }) => ({
submit: ({ repoFullName, commentContent }) => mutation1({
variables: { repoFullName, commentContent },
optimisticResponse: {
__typename: 'Mutation',
submitNewUser: {
__typename: 'Comment',
postedBy: ownProps.currentUser,
content: commentContent,
},
},
}),
}),
};
const thisProps2 = {
name: 'mutation2',
props: ({ ownProps, mutation2 }) => ({
submit: ({ repoFullName, commentContent }) => mutation2({
variables: { repoFullName, commentContent },
optimisticResponse: {
__typename: 'Mutation',
submitRepository: {
__typename: 'Comment',
otherProp: 'foobar',
},
},
}),
}),
};
const ComponentWithMutations = compose(
graphql(submitNewUser, thisProps1),
graphql(submitRepository, thisProps2)
)(Component);
Whenever the optimistic response fires, only the second result is fed back to into the query-response in the outer component. In other words, the first query gives an 'undefined' response (but no error), while the second returns an object as expect.
Why??
The property "createdAt" is not included in the optimistic reply.
__typename: 'Comment',
postedBy: ownProps.currentUser,
content: commentContent,
Should be:
__typename: 'Comment',
postedBy: ownProps.currentUser,
createdAt: Date(),
content: commentContent,
A missing field in an optimistic reply will silently fail to return anything to any queries that call that data.
So, I'm trying to access my model properties in controller.
Controller:
dashobards: [
{ id: 12, name: 'test' },
{ id: 17, name: 'test2' },
];
In route I have model named dashboards
return Ember.RSVP.hash({
dashboards: this.store.findAll('dashboard'),
}).then((hash) => {
return Ember.RSVP.hash({
dashboards: hash.dashboards
});
}, self);
I wanna have result in controller like this:
dashboards: [
{ id: 12, name: 'test' },
{ id: 17, name: 'test2' },
{ id: 17, name: 'test1' },
{ id: 20, name: 'test20' },
];
In controller I am trying to access this model like this:
this.dashborads = this.get(model.dashobards)
And it's not working, is there any other way of doing that?
Another update How to access complex object which we get it from server in ember data model attibute,
Created twiddle to demonstrate
define attribute with DS.attr(),
export default Model.extend({
permissions:DS.attr()
});
route file,
model(){
return this.store.findAll('dashboard');
}
Your server response should be like,
data: [{
type: 'dashboard',
id: 1,
attributes: {
permissions: {'name':'role1','desc':'description'}
}
}]
hbs file,
{{#each model as |row| }}
Name: {{row.permissions.name}} <br/>
Desc: {{row.permissions.desc}} <br />
{{/each}}
Update:
Still I am not sure about the requirement, Your twiddle should be minimalized working twiddle for better understanding..anyway I will provide my observation,
1.
model(params) {
this.set('id', params.userID);
const self = this;
return Ember.RSVP.hash({
dashboards: this.store.findAll('dashboard'),
user: this.store.findRecord('user', params.userID)
}).then((hash) => {
return Ember.RSVP.hash({
user: hash.user,
dashboards: hash.dashboards
});
}, self);
}
The above code can be simply written like
model(params) {
this.set('id', params.userID);
return Ember.RSVP.hash({
dashboards: this.store.findAll('dashboard'),
user: this.store.findRecord('user', params.userID)
});
}
Its good to always initialize array properties inside init method. refer https://guides.emberjs.com/v2.13.0/object-model/classes-and-instances/
For removing entry from array,
this.dashboard.pushObject({ 'identifier': '', 'role': '' }); try this this.get('dashboard').pushObject({ 'identifier': '', 'role': '' });.
if possible instead of plain object you can use Ember.Object like
this.get('dashboard').pushObject(Ember.Object.create({ 'identifier': '', 'role': '' }));
For removing entry.
removeDashboard(i) {
let dashboard = Ember.get(this, 'dashboard');
Ember.set(this, 'dashboard', dashboard.removeObject(dashboard[i]));
}
The above code can be written like, since i is an index
removeDashboard(i) {
this.get('dashboard').removeAt(i)
}
Just do return this.store.findAll('dashboard'); in route model hook, and dont override setupController hook, then in hbs you should be able to access model that will represent RecordArray. you can have a look at this answer for how to work with this.
I have Model "user" and I have a model that includes an array of objects:
{users: [
{
id: 1,
name: user1,
status: new
},
{
id: 2,
name: user2,
status: new
},
...
]}
But, I have to change the status of a single user. I try:
actions: {
checkAccept(id) {
this.store.findRecord('user', id).then((record) => {
record.set('status', 'accepted');
record.save();
});
}
With the same method to remove the recording works if used record.DestroyRecord. Why instead PUT method sent GET ???
UPDATED
I'm using:
actions: {
checkAccept(record) {
this.store.adapterFor('userNetwork').updateRecord(this.store, this.userNetwork.type, record);
}
}
but get this ERROR:
Uncaught Error: Assertion Failed: The `attr` method is not available on DS.Model, a DS.Snapshot was probably expected. Are you passing a DS.Model instead of a DS.Snapshot to your serializer
So, I think I am in over my head here. I am trying to get fullcalendar to work with backbone and django. I am still learning all of this but I can now make new events and save them via django-tastypie, and they do show up in the calendar, wohoo! However, I can not edit or drag them. Uncaught TypeError: Cannot call method 'isNew' of undefined line 96, is what I get when events are clicked and Uncaught TypeError: Cannot call method 'save' of undefined line 78. I have done my best to figure this out, but no success. Why is this.model.isnew() undefined in render and the same for save? I do not fully understand all of this so I have probably made some stupid mistake somewhere or misunderstood how everything works. I would be grateful if anyone could give me a hint.
I added console.log(fcEvent); to eventClick, and when I inspect it, it says start and end dates are invalid. Does anyone know what that means? If I inspect the same object just after it was added with addOne, the dates are valid. I am also using https://github.com/PaulUithol/backbone-tastypie if that matters.
edit: Tried this to make sure events.fetch() succeeded, this resulted in no events in the calendar whatsoever. Does that mean that events.fetch() never succeeds? I can see "GET /api/event/ HTTP/1.1" 200 6079 in the log.
alternative fetch:
events.fetch({
success: function(){
new EventsView({el: $("#calendar"), collection: events}).render();
}});
Original program:
$(function(){
var Event = Backbone.Model.extend();
var Events = Backbone.Collection.extend({
model: Event,
url: '/api/event/'
});
var EventsView = Backbone.View.extend({
initialize: function(){
_.bindAll(this);
this.collection.bind('reset', this.addAll);
this.collection.bind('add', this.addOne);
this.collection.bind('change', this.change);
this.collection.bind('destroy', this.destroy);
this.eventView = new EventView();
},
render: function() {
this.$el.fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,basicDay'
},
selectable: true,
selectHelper: true,
editable: true,
ignoreTimezone: false,
disableResizing:false,
select: this.select,
defaultView: 'agendaWeek',
eventClick: this.eventClick,
eventDrop: this.eventDropOrResize,
eventResize: this.eventDropOrResize,
events: 'events'
});
},
addAll: function() {
this.$el.fullCalendar('addEventSource', this.collection.toJSON());
},
addOne: function(event) {
this.$el.fullCalendar('renderEvent', event.toJSON(), true);
},
select: function(start, end, allDay) {
var eventView = new EventView();
console.log('select');
eventView.collection = this.collection;
eventView.model = new Event({start: start, end: end, allDay: allDay});
eventView.render();
},
eventClick: function(fcEvent) {
console.log('click');
this.eventView.collection = this.collection;
this.eventView.model = this.collection.at(fcEvent.id);
console.log(fcEvent);
this.eventView.render();
},
change: function(event) {
// Look up the underlying event in the calendar and update its details from the model
var fcEvent = this.$el.fullCalendar('clientEvents', event.get('id'))[0];
fcEvent.title = event.get('title');
fcEvent.color = event.get('color');
console.log('change');
this.$el.fullCalendar('updateEvent', fcEvent);
},
eventDropOrResize: function(fcEvent) {
console.log(fcEvent);
// Lookup the model that has the ID of the event and update its attributes
this.eventView.collection.at(fcEvent.id).save({start: fcEvent.start, end: fcEvent.end});
},
destroy: function(event) {
this.$el.fullCalendar('removeEvents', event.id);
}
});
var EventView = Backbone.View.extend({
el: $('#eventDialog'),
initialize: function() {
_.bindAll(this);
},
render: function() {
var buttons = {'Ok': this.save};
if (!this.model.isNew()) {
_.extend(buttons, {'Delete': this.destroy});
}
_.extend(buttons, {'Cancel': this.close});
this.$el.dialog({
modal: true,
title: (this.model.isNew() ? 'New' : 'Edit') + ' Event',
buttons: buttons,
open: this.open
});
return this;
},
open: function() {
this.$('#title').val(this.model.get('title'));
this.$('#color').val(this.model.get('color'));
},
save: function() {
this.model.set({'title': this.$('#title').val(), 'color': this.$('#color').val(),});
console.log('save');
if (this.model.isNew()) {
this.collection.create(this.model, {success: this.close,wait: true });
} else {
this.model.save({}, {success: this.close});
}
},
close: function() {
this.$el.dialog('close');
},
destroy: function() {
this.model.destroy({success: this.close});
}
});
var events = new Events();
new EventsView({el: $("#calendar"), collection: events}).render();
events.fetch();
});
You can try this. The issue comes down to an undefined Model.
On this line.
select: function(start, end, allDay) {
var eventView = new EventView();
eventView.collection = this.collection;
eventView.model = new Event({start: start, end: end, allDay: allDay});
eventView.render();
},...
Change it to. I have a feeling that your Model is not being instantiated properly.
select: function(start, end, allDay) {
var eventView = new EventView({
collection: this.collection,
model: new Event({start: start, end: end, allDay: allDay})
});
eventView.render();
},
Another issue is, you're instantiating the EventsView and passing in the collection and models before the fetch call.
var events = new Events();
$.when(events.fetch()).then(function() {
// Create the Events view when the Events asynchronous operation completes.
new EventsView({el: $("#calendar"), collection: events}).render();
});
Edit: here's a JS Bin for additional changes.
I got this working except for events not actually disappearing from the calendar, even though they are deleted in the database. I dont know if this is the correct way to do this, probably not, but it worked. Model was undefined in EventView so I had to get the model from the collection and pass it on to EventView.
eventClick: function(fcEvent) {
console.log(fcEvent.id);
this.mymodel = this.collection.where({'id':fcEvent.id})[0];
this.eventView = new EventView({collection: this.collection, model: this.mymodel})
this.eventView.render();
},
change: function(event) {
// Look up the underlying event in the calendar and update its details from the model
var fcEvent = this.$el.fullCalendar('clientEvents', event.get('id'))[0];
fcEvent.title = event.get('title');
fcEvent.color = event.get('color');
console.log('change');
this.$el.fullCalendar('updateEvent', fcEvent);
},
eventDropOrResize: function(fcEvent) {
console.log(fcEvent);
// Lookup the model that has the ID of the event and update its attributes
this.collection.where({'id':fcEvent.id})[0].save({start: fcEvent.start, end: fcEvent.end});
},