Mirage server GETs data but POST fails - ember.js

I have the mirage models:
// mirage/models/country.js
import { Model, belongsTo, hasMany } from 'miragejs';
export default Model.extend({
name: '',
iso3166_1_alpha3: '',
capitol_city: belongsTo('city', {inverse: null}),
cities: hasMany('city', {inverse: 'country'})
});
and:
// mirage/models/city.js
import { Model, belongsTo } from 'miragejs';
export default Model.extend({
name: '',
country: belongsTo('country', {inverse: 'cities'})
});
and the serializer:
// mirage/serializers/application.js
import { camelize, capitalize, underscore } from '#ember/string';
import { JSONAPISerializer } from 'miragejs';
export default class ApplicationSerializer extends JSONAPISerializer
{
alwaysIncludeLinkageData = true;
keyForAttribute(attr) {
return underscore(attr);
};
keyForRelationship(modelName) {
return underscore(modelName);
};
typeKeyForModel(model) {
return capitalize(camelize(model.modelName));
};
};
When I run the tests:
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Mirage | mirage models', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
test('it retrieves the country', async function (assert) {
const server = this.server;
let city = server.create('city', { id: '1', name: 'Paris' });
server.create(
'country',
{
id: 'FR',
name: 'France',
iso3166_1_alpha3: 'FRA',
capitol_city: city
}
);
let response = await fetch('/api/countries')
assert.strictEqual(response.status, 200, "Should have created the model");
let json = await response.json();
assert.deepEqual(
json,
{
data: [
{
type: 'Country',
id: 'FR',
attributes: {
name: 'France',
iso3166_1_alpha3: 'FRA',
},
relationships: {
capitol_city: {data: {type: 'City', id: '1'}},
cities: {data: []},
}
}
]
}
)
});
test('it creates the country', async function (assert) {
const server = this.server;
server.create('city', { id: '1', name: 'Paris' });
let response = await fetch(
'/api/countries',
{
method: 'POST',
headers: {'Countent-Type': 'application/json'},
body: JSON.stringify(
{
data: {
id: 'FR',
type: 'Country',
attributes: {
iso3166_1_alpha3: 'FRA',
name: 'France',
},
relationships: {
capitol_city: { data: { type: 'City', id: '1'} },
cities: { data: [{ type: 'City', id: '1'}] }
}
}
}
)
}
);
console.log((await response.json()).message);
assert.strictEqual(response.status, 201, "Should have created the model");
});
});
The first one passes and the second one fails with the message:
Mirage: You're passing the relationship 'capitol_city' to the 'country' model via a POST to '/api/countries', but you did not define the 'capitol_city' association on the 'country' model.
How can I get Mirage to recognise the capitol_city attribute on the model?

Mirage is opinionated with regards to the format of attributes and expects the attributes to be in camelCase (and not snake_case).
Unfortunately the Ember CLI Mirage model relationships documentation does not mention this expectation and all the examples use single-word attributes. Even more unfortunately, Mirage will work with snake_case attributes for simple GET requests and when directly creating models through the API; it is only when you make a request to POST/PUT/PATCH a model into the server that it fails and the message will (confusingly) refer to the snake case attribute which has been defined. (See the Mirage source code for where it fails.)
To solve it, convert the attributes to camel case:
// mirage/models/country.js
import { Model, belongsTo, hasMany } from 'miragejs';
export default Model.extend({
name: '',
iso31661Alpha3: 0,
capitolCity: belongsTo('city', {inverse: null}),
cities: hasMany('city', {inverse: 'country'})
});
and change it in the tests as well:
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Mirage | mirage models', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
test('it retrieves the country', async function (assert) {
const server = (this as any).server;
let city = server.create('city', { id: '1', name: 'Paris' });
server.create(
'country',
{
id: 'FR',
name: 'France',
iso31661Alpha3: 'FRA',
capitolCity: city
}
);
let response = await fetch('/api/countries')
assert.strictEqual(response.status, 200, "Should have created the model");
let json = await response.json();
console.log(JSON.stringify(json));
assert.deepEqual(
json,
{
data: [
{
type: 'Country',
id: 'FR',
attributes: {
name: 'France',
iso3166_1_alpha3: 'FRA',
},
relationships: {
capitol_city: {data: {type: 'City', id: '1'}},
cities: {data: []},
}
}
]
}
)
});
test('it creates the country', async function (assert) {
const server = (this as any).server;
let city = server.create('city', { id: '1', name: 'Paris' });
let response = await fetch(
'/api/countries',
{
method: 'POST',
headers: {'Countent-Type': 'application/json'},
body: JSON.stringify(
{
data: {
id: 'FR',
type: 'Country',
attributes: {
iso3166_1_alpha3: 'FRA',
name: 'France',
},
relationships: {
capitol_city: { data: { type: 'City', id: '1'} },
cities: { data: [{ type: 'City', id: '1'}] }
}
}
}
)
}
);
console.log((await response.json()).message);
assert.strictEqual(response.status, 201, "Should have created the model");
});
});
However, once you convert it to camel case then the attribute iso31661Alpha3 does not get formatted correctly in the output so you have to manually change the serializer for the country model:
// mirage/serializers/country.js
import ApplicationSerializer from './application';
export default class CountrySerializer extends ApplicationSerializer
{
keyForAttribute(attr: string) {
switch(attr)
{
case 'iso31661Alpha3': return 'iso3166_1_alpha3';
default: return super.keyForAttribute(attr);
}
};
};
Once the attributes are in the correct case then it will work.

Related

passing labels in echart line graph

I have a line chart that gets data from the back end. I am able to plot the data but not the labels from the back end. This is my code:
import {Line} from 'vue-chartjs'
import axios from 'axios';
export default {
extends: Line,
props: ["data"],
methods: {
getScore() {
axios({
method: 'get',
url: 'http://localhost:5000/time',
}).then((response) => {
this.renderChart(
{
labels: [],
datasets: [
{
labels: response.data.score,
label: 'Stream',
backgroundColor: "#42c9d5",
data: response.data.score
}
]
},
{responsive: true, maintainApsectRatio: false}
)
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
});
}
},
mounted() {
this.getScore();
}
}
because I am getting the data from the getScore method. how Can I get the labels from another method? Or do I need to send two json responses? Also how do I loop through the json responses inside the this.renderchart?
I figured out how to do it. For those interested I passed a list of of dictionaries from the back end:
#app.route('/time')
def timeData():
response_object = {'status': 'success'}
SCORES = []
score = {}
score["value"] = [23,38,12]
score["date"] = ["Jan", "Feb", "Mar"]
SCORES.append(score)
response_object['score'] = SCORES
return jsonify(response_object)
I then added ... before the date key value pair:
import {Line} from 'vue-chartjs'
import axios from 'axios';
export default {
extends: Line,
props: ["data"],
methods: {
getScore() {
axios({
method: 'get',
url: 'http://localhost:5000/time',
}).then((response) => {
this.renderChart(
{
labels: [...response.data.score[0].date],
datasets: [
{
label: 'Stream',
backgroundColor: "#42c9d5",
data: response.data.score[0].value
}
]
},
{responsive: true, maintainApsectRatio: false}
)
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
});
}
},
mounted() {
this.getScore();
}

Qunit serializer test is returning “Cannot read property ‘push’ of null”

Ember serializer test below is failing with “Cannot read property ‘push’ of null”.
I am using Pretender mock server library. The test is failing when I'm calling a store.findRecord()
Note how there are no relationships in the assignment model/serializer, which is why it's confusing that it's throwing the following error:
Click here to see the error that's getting returned
assignment serializer:
import DS from 'ember-data';
const { JSONAPISerializer } = DS;
export default JSONAPISerializer.extend({
attrs: {
autoPassReviewerNote: 'autoPassReviewerNote',
dateOfCreation: 'date_of_creation',
displayType: 'displayType',
lastUpdate: 'last_update',
moduleItem: 'moduleItem',
submissionType: 'submissionType'
}
});
assignment model:
import DS from 'ember-data';
const {
Model,
attr
} = DS;
export default Model.extend({
title: attr('string'),
submissionType: attr('string'),
description: attr('string'),
completed: attr('boolean'),
displayType: attr('string'),
dateOfCreation: attr('string'),
lastUpdate: attr('string'),
autopass: attr('boolean'),
moduleItem: attr('object')
});
serializer test (which is failing):
import { moduleForModel, test } from 'ember-qunit';
import Pretender from 'pretender';
import Ember from 'ember';
const {
run
} = Ember;
var server;
moduleForModel('assignment', 'Unit | Serializer | assignment', {
needs: [
'serializer:assignment'
],
beforeEach: function() {
server = new Pretender(function() {
// eslint-disable-next-line ember/use-ember-get-and-set
this.get('/assignments/:id', function() {
data: {
type: 'assignment',
id: 98,
attributes: {
title: 'dfgdfg',
submissionType: 'dfgdf',
displayType: 'dfgdfg',
date_of_creation: 'sdfgsdfg',
last_update: 'fgdgd'
}
}
};
return [ 200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ];
});
});
},
afterEach: function() {
server.shutdown();
}
});
test('testing assignment serializer', function(assert) {
var checkAttrSerialization = (assignment) => {
assert.equal(assignment, true);
}
let store = this.store();
run(() => {
return store.findRecord('assignment', 98).then((assignment) => checkAttrSerialization(assignment));
});
});

How do I serialize a has-many-through relationship with Ember's JSONAPIAdapter?

I have three models: User, Group, and Membership. A User has many Groups through Memberships. I have a form to invite a new user and assign them to zero or more groups all at once.
What I Want
The JSON I expect to send to the server for POST /users looks like
{
data: {
type: 'user',
id: null,
attributes: { name: 'Sam Sample' }
},
relationships: {
memberships: {
data: [
{
type: 'membership',
id: null,
relationships: {
group: {
data: {
type: 'group',
id: 12345
}
}
}
},
{
type: 'membership',
id: null,
relationships: {
group: {
data: {
type: 'group',
id: 67890
}
}
}
}
]
}
}
}
What I Tried
I tried adding serialize: true to the relevant serializers:
// serializer:user
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
attrs: {
memberships: { serialize: true }
}
})
// serializer:membership
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
attrs: {
group: { serialize: true }
}
})
That gets me some of the JSON I expect, but not all of it. Specifically, I get the membership objects, but not the groups within them
{
data: {
type: 'user',
id: null,
attributes: { name: 'Sam Sample' }
},
relationships: {
memberships: {
data: [
{
type: 'membership',
id: null
},
{
type: 'membership',
id: null
}
]
}
}
}

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": []
}

Structuring models with ember data

I've started using ember data and I'm having some issues getting started. If my json structure for ingredients is:
[
{
"name":"flax seed",
"retailer":"www.retailer.com",
"nutrient_info":[
{
"type":"vitamin A",
"amount":"50mg"
},
{
"type":"calcium",
"amount":"30mg"
}
]
},
{
"name":"soy milk",
"retailer":"www.retailer-two.com",
"nutrient_info":[
{
"type":"vitamin D",
"amount":"500mg"
},
{
"type":"niacin",
"amount":"5000mg"
}
]
},
{ other ingredients... }
]
I think this is how I would define my models:
var attr = DS.attr,
hasMany = DS.hasMany,
belongsTo = DS.belongsTo
App.Ingredients = DS.Model.extend({
// id: attr('number'), // don't include id in model?
name: attr('string'),
retailer: attr('string'),
nutrientinfo: hasMany('nutrients')
})
App.Nutrients = DS.Model.extend({
type: attr('string'),
amount: attr('string'),
ingredient: belongsTo('ingredients')
})
What should the server payload look like, and would I need to customize the REST adapter? Do I need to define the ingredient id: attr() in the model?
Any help in clarifying some of these concepts is appreciated.
Generally model definitions are singular (additionally I changed nutrientinfo to nutrient_info):
App.Ingredient = DS.Model.extend({
// id: attr('number'), // don't include id in model?
name: attr('string'),
retailer: attr('string'),
nutrient_info: hasMany('nutrient')
})
App.Nutrient = DS.Model.extend({
type: attr('string'),
amount: attr('string'),
ingredient: belongsTo('ingredient')
})
The format would need to be as follows (from the endpoint, or using a serializer)
{
// Ingredient records
ingredients:[
{
id:1,
"name":"flax seed",
"retailer":"www.retailer.com",
"nutrient_info":[1,2]
},
{
id:2,
"name":"soy milk",
"retailer":"www.retailer-two.com",
"nutrient_info":[3,4]
},
{ other ingredients... }
],
// Nutrient records
nutrients: [
{
id:1,
"type":"vitamin A",
"amount":"50mg",
ingredient:1
},
{
id:2,
"type":"calcium",
"amount":"30mg",
ingredient:1
},
{
id:3,
"type":"vitamin D",
"amount":"500mg",
ingredient:2
},
{
id:4,
"type":"niacin",
"amount":"5000mg",
ingredient:2
}
]
}
Here's an example using a serializer and your json, I've had to manually assign ids (despite this being invalid, you should send down ids, or use UUIDs), but this should give you an idea of how to use the serializer:
App.IngredientSerializer = DS.RESTSerializer.extend({
extractArray: function(store, type, payload, id, requestType) {
var ingredients = payload,
nutrientId = 0,
ingredientId = 0,
ids = [],
nutrients = [];
ingredients.forEach(function(ing) {
ing.id = ingredientId++;
var nInfo = ing.nutrient_info,
nIds = [];
nInfo.forEach(function(n){
n.id = nutrientId++;
n.ingredient = ing.id;
nIds.push(n.id);
nutrients.push(n);
});
ing.nutrient_info = nIds;
});
payload = {ingredients:ingredients, nutrients:nutrients};
return this._super(store, type, payload, id, requestType);
}
});
http://emberjs.jsbin.com/OxIDiVU/537/edit