ember error this.store.findAll is not a function - ember.js

hey all im going through the emberjs tutorial and i've run into this issue that i'm unable to resolve. when i try to call the findAll function from store it throws a typeerror and says that findAll is not a function. when i use The this.get() method it says itss a classic ember object method, and can't be used in octane classes. does anybody have any idea how to fix this?
thanks in advance for your time!
app/route/rental.js
import Route from '#ember/routing/route';
export default class RentalsRoute extends Route {
model() {
return this.store.findAll('rental');
}
}
app/models/rental.js
import Model, { attr } from '#ember-data/model';
export default class RentalModel extends Model {
#attr title;
#attr owner;
#attr city;
#attr propertyType;
#attr image;
#attr bedrooms;
#attr description;
}
mirage/config.js
export default function () {
this.namespace = '/api';
this.get('/rentals', function () {
return {
data: [
{
type: 'rentals',
id: 'grand-old-mansion',
attributes: {
title: 'Grand Old Mansion',
owner: 'Veruca Salt',
city: 'San Francisco',
'property-type': 'Estate',
bedrooms: 15,
image:
'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg',
},
},
{
type: 'rentals',
id: 'urban-living',
attributes: {
title: 'Urban Living',
owner: 'Mike Teavee',
city: 'Seattle',
'property-type': 'Condo',
bedrooms: 1,
image:
'https://upload.wikimedia.org/wikipedia/commons/0/0e/Alfonso_13_Highrise_Tegucigalpa.jpg',
},
},
{
type: 'rentals',
id: 'downtown-charm',
attributes: {
title: 'Downtown Charm',
owner: 'Violet Beauregarde',
city: 'Portland',
'property-type': 'Apartment',
bedrooms: 3,
image:
'https://upload.wikimedia.org/wikipedia/commons/f/f7/Wheeldon_Apartment_Building_-_Portland_Oregon.jpg',
},
},
],
};
});
}

The reason why you see it is that starting from the v4 Ember doesn't allow some implicit service injections which were there up recently. The store injection in routes was one of them. It was first added as a deprecation in 3.26 but now as of v4 it's removed and apparently they haven't updated the documentation.
What you should do is to inject it explicitly, i.e. in your app/route/rental.js make
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class RentalsRoute extends Route {
#service store;
model() {
return this.store.findAll('rental');
}
}

Related

Mirage server GETs data but POST fails

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.

Loading model on param route

I've successfully build out my app to have a proper child route and it works as it should when the user navigates through the site, but on load straight to the show route (exhibitions/slug) ember data seems to break; however, I'm still getting the proper response from the API. Here is my code, hope it makes sense..
routes/exhibitions
import Route from '#ember/routing/route';
const { set } = Ember;
export default Route.extend({
model() {
return this.store.findAll('exhibition');
},
setupController(controller, model) {
set(controller, 'exhibitions', model);
}
});
routes/exhibition
import Ember from 'ember';
const { get, set } = Ember;
export default Ember.Route.extend({
model(params) {
return this.store.queryRecord('exhibition', {
'fields.slug': params.exhibition_slug
});
},
serialize(model) {
return { exhibition_slug: get(model, 'slug') };
},
setupController(controller, model) {
set(controller, 'exhibition', model);
}
});
model for exhibition
import Contentful from 'ember-data-contentful/models/contentful';
import attr from 'ember-data/attr';
import { belongsTo, hasMany } from 'ember-data/relationships';
export default Contentful.extend({
title: attr('string'),
body: attr('string'),
slug: attr('string')
});
router.js
import EmberRouter from '#ember/routing/router';
import config from './config/environment';
const Router = EmberRouter.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('homepage', { path: '/' });
this.route('exhibitions');
this.route('exhibition', { path: 'exhibitions/:exhibition_slug'});
});
export default Router;
Here is the JSON response (apologies for poor formatting)
{sys: {type: "Array"}, total: 1, skip: 0, limit: 1, items: [{,…}]}
items
:
[{,…}]
0
:
{,…}
fields
:
{title: "Hello", body: "This is the body.", slug: "hello"}
body
:
"This is the body."
slug
:
"hello"
title
:
"Hello"
sys
:
{space: {sys: {type: "Link", linkType: "Space", id: "kw98h7kialqf"}}, id: "5rllAHKFygCOma2wgy6WuI",…}
contentType
:
{sys: {type: "Link", linkType: "ContentType", id: "exhibition"}}
createdAt
:
"2018-02-20T15:48:08.093Z"
id
:
"5rllAHKFygCOma2wgy6WuI"
locale
:
"en-US"
revision
:
2
space
:
{sys: {type: "Link", linkType: "Space", id: "kw98h7kialqf"}}
type
:
"Entry"
updatedAt
:
"2018-02-20T15:52:19.063Z"
limit
:
1
skip
:
0
sys
:
{type: "Array"}
type
:
"Array"
total
:
1
The error I'm getting in the console is:
Error while processing route: exhibition Cannot read property 'Entry' of undefined TypeError: Cannot read property 'Entry' of undefined
Let me know if anything else is needed!! Thank you!

Modifying model in Ember

I'm putting together an app that displays a list of stores (with add/edit/delete options), and clicking on a store name takes you to the list of items in that store (again with add/edit/delete).
The model:
// app/models/shop.js
import DS from 'ember-data';
export default DS.Model.extend({
shopName: DS.attr('string'),
shopDetails: DS.attr('string'),
shopStock: DS.attr('array', {
defaultValue() {
return [];
}
})
});
Basically model should be as:
{
"shopName": "someName",
"shopDetails": "someDetails",
"shopStock": [
{
"name": "foo",
"description": "bar",
"price": "555"
}
]
}
For each shop the route is dynamical:
// app.router.js
Router.map(function() {
this.route('shop', function() {
this.route('stock', { path: '/:shop_id/stock' });
this.route('edit', { path: '/:shop_id/edit' });
});
});
And in the controller I have:
actions: {
saveItem() {
const newItem = {
name: this.get('itemName'),
description: this.get('itemDescription'),
price: this.get('itemPrice')
};
}
}
The question is, how do I push the newItem object into model's shopStock array?
Since you want to create/edit/save/delete the related child records, you should create a new model for the child (shopStock) that belongsTo the parent (shop).
// app/models/shop-stock.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
price: DS.attr('string'),
shop: DS.belongsTo('shop')
})
});
Your shop model should also have another field added, shopStocks: DS.hasMany('shop-stock').
When you want to add child records to the parent, you will use the .pushObject() method. See the Model Relationships section of the Guides for more details.

Accessing model properties in Controller - Ember

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.

Ember-data error: "Assertion Failed: You need to pass a model name to the store's modelFor method"

I'm getting this error when trying to consume the json from the backend.
I'm using Ember CLI version 2.5.0 and the RestAdapter.
Here's my routes/products/index.js looks like:
export default Ember.Route.extend({
actions: {
[...]
},
model: function() {
return this.store.findAll('product');
}
});
And here's what my json looks like:
{
"products":[
{
"id":9,
"name":"Product A",
"price_cents":1500,
"margin_cents":0,
"commission":0,
"expiration":null,
"track_stock":false,
"stock_amount":5,
"brand":{
"id":2,
"name":"SuperPet"
},
"group":{
"id":1,
"name":"Group A"
}
},
{
"id":8,
"name":"Product B",
"price_cents":1500,
"margin_cents":0,
"commission":0,
"expiration":null,
"track_stock":false,
"stock_amount":5,
"brand":{
"id":1,
"name":"Whiskas"
},
"group":{
"id":1,
"name":"Group B"
}
}
],
"meta":{
"pagination":{
"per_page":null,
"total_pages":4,
"total_objects":10
}
}
}
And by request, here's the model:
import DS from 'ember-data';
const { attr, belongsTo } = DS;
export default DS.Model.extend({
name: attr('string'),
priceCents: attr('number'),
marginCents: attr('number'),
comission: attr('number'),
expiration: attr('date'),
trackStock: attr('boolean'),
stockAmount: attr('number'),
brand: belongsTo('brand')
});
I was having the same issue. This worked for me:
//app/serializers/product.js
import DS from 'ember-data';
import Ember from 'ember';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin,{
attrs: {
brand: { embedded: 'always' },
group: { embedded: 'always'}
}
});
You have the brand: belongsTo('brand')
but you forgot the group: belongsTo('group')
also .. these two ( brand, group ) have to be declared as embedded with the DS.EmbeddedRecordsMixin in the product serializer if you are going to embed them this way