Ember get hasMany - ember.js

I have Model
EmberApp.Card = DS.Model.extend({
balance: DS.attr('number'),
operations: DS.hasMany('operation', { async: true })
});
Than I make
self.store.find('card', 'me')
response
{
"card": {
"id": "53620486168e3e581cb5851a",
"balance": 20
}
}
getting card Model but without operations and setting it to controller prop "currentCard"
Then I want to find operations throw url /cards/me/operations
response
{"operation": [{ "id": 1, "type": 1 }] }
How can I do it from this.controllerFor('*').get('currentCard')... ?

You actually need to return your operations as a list of ID's in your card response like this:
{
"card": {
"id": "53620486168e3e581cb5851a",
"balance": 20,
"operations": [1, 2, 3]
}
}
This will enable you to do this in another route:
this.modelFor('card').get('operations');
or this in your cards controller:
this.get('content.operations');
This will execute the following call to your API:
/api/operations?ids[]=1&ids[]=2&ids[]=3

First of all you should add links to response from cards/me
EmberApp.ApplicationSerializer = DS.RESTSerializer.extend({
normalizePayload: function(type, payload) {
if (type.toString() === 'EmberApp.Card') {
payload.links = { 'operations': '/cards/me/operations' };
return { card: payload };
} else if (type.toString() === 'EmberApp.Operation') {
return { operations: payload };
}
}
});
Then in route
EmberApp.CardOperationsRoute = Ember.Route.extend({
model: function() {
return this.controllerFor('sessions.new')
.get('currentCard')
.get('operations');
}
});

Related

Ember Data Serializer & Adapter with Supabase returns empty (Proxy { })

Struggling to create my customised adapter & serializer to integrate Supabase, how I'm stuck why no data in Ember Data.
Trying out with a simple findAll() method. See below:
Service ⬇️:
export default class SupabaseService extends Service {
client;
constructor() {
super(...arguments);
const { url, key } = ENV.supabase;
const supabase = createClient(url, key);
this.client = supabase;
}
}
Model ⬇️:
export default class CourseModel extends Model {
#attr('string') name;
#attr('date') date_added;
}
Adapter ⬇️:
export default class ApplicationAdapter extends RESTAdapter {
#service supabase;
async findAll(store, type, neverSet, snapshotRecordArray) {
return new Promise(async (resolve, reject) => {
try {
const { data, error, status } = await this.supabase.client
.from(pluralize(type.modelName))
.select('*');
if (error) {
reject(error);
} else {
resolve(data);
}
} catch (error) {
reject(error);
}
});
}
}
Serializer ⬇️:
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
// parse the response data from the server and return it in the format that Ember Data expects
let newPayload = {
data: payload.map(item => {
let attributes = JSON.parse(JSON.stringify(item));
delete attributes.id;
return {
id: item.id,
type: primaryModelClass.modelName,
attributes: attributes
}
})
}
return super.normalizeResponse(store, primaryModelClass, newPayload, id, requestType);
}
✅ The service works fine. The adapter manage to get data and returns as follows:
[
{
"id": "259f46fd-3321-4cc9-ad5e-6d6ec880f7f1",
"date_added": "2022-12-31T00:03:14.618585+00:00",
"name": "Science"
},
{
"id": "62a6a085-604b-4600-8cc4-59a8c9af284a",
"date_added": "2022-12-31T00:03:30.010963+00:00",
"name": "Physics"
}
]
The serializer newPayload to follow JSON API schema, returns:
{
"data": [
{
"id": "259f46fd-3321-4cc9-ad5e-6d6ec880f7f1",
"type": "course",
"attributes": {
"name": "Science",
"date_added": "2022-12-31T00:03:14.618585+00:00"
}
},
{
"id": "62a6a085-604b-4600-8cc4-59a8c9af284a",
"type": "course",
"attributes": {
"name": "Physics",
"date_added": "2022-12-31T00:03:30.010963+00:00"
}
}
]
}
But the problem is no data in store. Logging model in template shows empty Proxy {}.
I have no idea why. Ember Inspector shows no model in Data.
Any suggestions?

Ember sideload data not linked

I'm new to using Ember and was assigned to an ongoing project and need to resolve the following:
export default class OrderModel extends Model.extend(LoadableModel) {
#attr('string') status;
#attr('number') total;
#hasMany('order-item', { async: true }) orderItems;
}
export default class OrderItemModel extends Model.extend(LoadableModel) {
#attr('number', { defaultValue: 0 }) discount;
#attr('number', { defaultValue: 0 }) price;
#hasMany('item-fix', { async: false }) fixes;
}
export default class ItemFixModel extends Model.extend(LoadableModel) {
#attr('number', { defaultValue: 0 }) price;
}
and when I do let order = await this.store.findRecord('order', order_id, { reload: true });
the json response is:
data: {
type: "orders",
id: "1584",
attributes: {
status: "in_progress",
total: 1300
},
relationships: {
order-items: {
data: [
{
type: "order-items",
id: "1801
}
]
}
}
},
included: [
{
type: "order-items"
id: "1801",
attributes: {
discount: 0,
price: 1200
},
relationships: {
item-fixes: {
data: [
{
type: "item-fixes",
id: "335"
}
]
}
},
{
type: "item-fixes",
id: "335",
attributes: {
price: 100
}
}
]
but when I inspect the orderItem inside the order variable, the itemFixes are empty, but the is in the sideload of the response.
¿How can I link this nested relationship?
Also, here is the serializer.
export default DS.JSONAPISerializer.extend({
serialize(snapshot) {
let serialized = this._super(...arguments);
let { adapterOptions } = snapshot;
if (adapterOptions && adapterOptions.addPromotionCode) {
return { code: serialized.data.attributes.code }
}
serialized.included = A([]);
snapshot.eachRelationship((key, relationship) => {
if (relationship.kind === 'belongsTo') {
if (!isBlank(snapshot.belongsTo(key))) {
let node = snapshot.belongsTo(key).record.serialize({ includeId: true }).data;
delete node.relationships;
serialized.included.pushObject(node);
}
} else if (relationship.kind === 'hasMany') {
if (!isBlank(snapshot.hasMany(key))) {
snapshot.hasMany(key).forEach(ele => {
let node = ele.record.serialize({ includeId: true }).data;
delete node.relationships;
serialized.included.pushObject(node);
});
}
}
});
return serialized;
}
});
In your response the relationship name is item-fixes but in your model it's just fixes. It must be the same.

serializer in unit test doesn't process json

the serializer in unit test is not processing json at all, but it works as expected in the application. Yeah, I wrote it afterwards, but the question is - why it's not working? I tried also to create it in place, inherit from RESTSerializer, create models in place, but none of that worked.
Maybe someone can give a clue?
update
looks like everything begins in the
isPrimaryType: function (store, typeName, primaryTypeClass) {
var typeClass = store.modelFor(typeName);
return typeClass.modelName === primaryTypeClass.modelName;
},
last string returns false, because of primaryTypeClass.modelName is undefined
Serializer unit test
import DS from 'ember-data';
import { moduleForModel, test } from 'ember-qunit';
import setupStore from 'app/tests/helpers/setup-store';
import Profile from 'app/models/profile';
import Email from 'app/models/email';
import Address from 'app/models/address';
import ProfileSerializer from 'app/serializers/profile';
var env;
moduleForModel('profile', 'Unit | Serializer | profile', {
needs: ['serializer:profile', 'serializer:email', 'serializer:address', 'model:contactable', 'model:email', 'model:address'],
beforeEach: function () {
env = setupStore({
profile: Profile,
email: Email,
address: Address
});
env.registry.register('serializer:profile', ProfileSerializer);
env.profileSerializer = env.container.lookup('serializer:profile');
},
teardown: function() {
Ember.run(env.store, 'destroy');
}
});
test('it converts embedded records attributes', function(assert) {
// expect(3);
let payload = {
id: 1,
first_name: "Carlo",
last_name: "Schuppe",
company: "Metz-Witting",
birthday: "01-10-1985",
photo: null,
emails: [{address: "foo#bar.baz", id: 1, type: "main"}],
addresses: [{city: "Brooklyn", id: 1, type: "main"}]
},
parsed = {
"data":
{
"id":"1",
"type":"profile",
"attributes": { "firstName":"Carlo","lastName":"Schuppe","company":"Metz-Witting","birthday":"01-10-1985","photo":null },
"relationships": {
"emails": { "data": [{"id":"1","type":"email"}] },
"addresses": { "data": [{"id":"1","type":"address"}] }
}
},
"included":[
{"id":"1","type":"email","attributes":{"address":"foo#bar.baz", "kind": "main"},"relationships":{"contactable":{"data":{"type":"profile","id":"1"}}}},
{"id":"1","type":"address","attributes":{"city":"Brooklyn", "kind": "main"},"relationships":{"contactable":{"data":{"type":"profile","id":"1"}}}}
]
},
find, update, findAllRecordsJSON;
Ember.run(function() {
find = env.profileSerializer.normalizeResponse(env.store, Profile, payload, '1', 'findRecord');
// update = env.profileSerializer.normalizeResponse(env.store, Profile, payload, '1', 'updateRecord');
// findAllRecordsJSON = env.profileSerializer.normalizeResponse(env.store, Profile, payload, '1', 'findAll');
});
assert.deepEqual(find, parsed);
// assert.deepEqual(update, parsed);
// assert.deepEqual(findAllRecordsJSON, parsed);
});
setup_store.js
import Ember from 'ember';
import DS from 'ember-data';
// import ActiveModelAdapter from 'active-model-adapter';
// import ActiveModelSerializer from 'active-model-adapter/active-model-serializer';
export default function setupStore(options) {
var container, registry;
var env = {};
options = options || {};
if (Ember.Registry) {
registry = env.registry = new Ember.Registry();
container = env.container = registry.container();
} else {
container = env.container = new Ember.Container();
registry = env.registry = container;
}
env.replaceContainerNormalize = function replaceContainerNormalize(fn) {
if (env.registry) {
env.registry.normalize = fn;
} else {
env.container.normalize = fn;
}
};
var adapter = env.adapter = (options.adapter || '-default');
delete options.adapter;
if (typeof adapter !== 'string') {
env.registry.register('adapter:-ember-data-test-custom', adapter);
adapter = '-ember-data-test-custom';
}
for (var prop in options) {
registry.register('model:' + Ember.String.dasherize(prop), options[prop]);
}
registry.register('store:main', DS.Store.extend({
adapter: adapter
}));
registry.optionsForType('serializer', { singleton: false });
registry.optionsForType('adapter', { singleton: false });
registry.register('adapter:-default', DS.Adapter);
registry.register('serializer:-default', DS.JSONSerializer);
registry.register('serializer:-rest', DS.RESTSerializer);
registry.register('serializer:-rest-new', DS.RESTSerializer.extend({ isNewSerializerAPI: true }));
registry.register('adapter:-active-model', DS.ActiveModelAdapter);
registry.register('serializer:-active-model', DS.ActiveModelSerializer.extend({isNewSerializerAPI: true}));
registry.register('adapter:-rest', DS.RESTAdapter);
registry.injection('serializer', 'store', 'store:main');
registry.register('transform:string', DS.StringTransform);
registry.register('transform:number', DS.NumberTransform);
registry.register('transform:date', DS.DateTransform);
registry.register('transform:main', DS.Transform);
env.serializer = container.lookup('serializer:-default');
env.restSerializer = container.lookup('serializer:-rest');
env.restNewSerializer = container.lookup('serializer:-rest-new');
env.store = container.lookup('store:main');
env.adapter = env.store.get('defaultAdapter');
env.registry.register('serializer:-active-model', DS.ActiveModelSerializer.extend({isNewSerializerAPI: true}));
env.registry.register('adapter:-active-model', DS.ActiveModelAdapter);
env.registry.register('serializer:application', DS.ActiveModelSerializer.extend({isNewSerializerAPI: true}));
return env;
}
output
{
"data": null,
"included": []
}

Ember.js and API pagination

I'm using Ember.js (v1.2.0) with an API which returns paginated JSON data like this:
{
"count": 5,
"next": "http://127.0.0.1:8000/some/resource/?page=2",
"previous": null,
"results": [
{
"id": 37,
"title": "Some title",
"description": "Some description",
},
{
"id": 35,
"title": "Sdflskdf",
"description": "sdfkdsjf",
},
{
"id": 34,
"title": "Some other title",
"description": "Dsdlfksdf",
},
]
}
I'm not using ember-data, so I'm using a plain ember object as my model and loading the data like this:
App.SomeResource = Ember.Object.extend({});
App.SomeResource.reopenClass({
find: function () {
return $.getJSON('/some/resource/').then(function (response) {
return response.results.map(function (data) {
return App.SomeResource.create(data);
});
});
},
});
The find method on my model class returns a promise which resolves to an array of objects. While creates SomeResource objects, all the pagination data is lost.
Is there a way to store count, next and previous page urls somewhere when the promise resolves?
I am assigning them to global object but you should do better.
App.SomeResource = Ember.Object.extend({});
App.SomeResource.reopenClass({
find: function () {
return $.getJSON('/some/resource/').then(function (response) {
return RSVP.all(response.results.map(function (data) {
return App.SomeResource.create(data);
})).then(function(createdResources) {
window.count = response.count;
window.next = response.next;
window.previous = response.previous;
return createdResources;
});
});
}
});
Rather than storing this metadata on the global window object, I came up with this:
App.SomeResource.reopenClass({
find: function () {
var url = '/some/resource/';
return Ember.$.getJSON(url).then(function (response) {
response.results = response.results.map(function (resource) {
return App.SomeResource.create(resource);
});
return response;
});
},
});
SomeResource.find() just instantiates Ember objects from the results array and then returns the response with the rest of the data untouched. The route then receives the response and sets up the pagination data and the model in the setupController function:
App.SomeResourceRoute = Ember.Route.extend({
model: function () {
return App.SomeResource.find();
},
setupController: function(controller, model) {
controller.setProperties({count: model.count,
pageCount: model.page_count,
currentPage: model.current_page,
pageRange: model.page_range,
previous: model.previous,
next: model.next,
model: model.results,});
},
});
It works, but maybe there is a better way.

How can I see my response from server in Ember.js

My code is quite simple (Client Side):
Record.Router.map(function () {
this.resource('main', { path: '/' });
});
Record.MainRoute = Ember.Route.extend({
model: function () {
var response = Record.Rank.find();
console.log(response.get('name'));
console.log(response);
return Record.Rank.find();
}
});
My model:
Record.Rank = DS.Model.extend({
id: DS.attr('integer'),
rank: DS.attr('integer'),
content: DS.attr('string')
});
I use RESTadapter:
Record.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.reopen({
namespace: 'recordApp'
})
});
My Server side code (PHP):
<?php
namespace RecordContainer;
echo '{"rank":
{
"id": "1",
"rank": "2",
"content": "walla"
}
}';
I expect to something after I issue Record.Rank.find() but my console.log(response.get('name')) logs undefined and the second console.log(response) show the following, no information about echo from server inside:
How do I see the response from the server, in Ember?
1st: Calling find on a DS.Model without any parameters, i.e. Record.Rank.find(), is equivalent to sending a findAll() request to your server. In other words, it should fetch all Record.Rank. Therefore ember-data expects an array in the response of the format:
{
"ranks":[
{
"id": "1",
"rank": "2",
"content": "walla"
},
{
"id": "2",
"rank": "5",
"content": "foo"
}
]
}
2nd: Even if the response from the PHP was correct (as described above), console.log(response.get('name')); would probably return undefined since the request is not yet completed and the record(s) are not available. If you really want to access the records loaded into the store you need to place your code into a Promise resolve callback:
Record.MainRoute = Ember.Route.extend({
model: function () {
var response = Record.Rank.find();
response.then(function(ranks) {
console.log(ranks.getEach('name'));
});
return response;
}
});