Can't find records from my model
Ember version:
DEBUG: -------------------------------
DEBUG: Ember : 1.0.0
DEBUG: Ember Data : 1.0.0-beta.2
DEBUG: Handlebars : 1.0.0 ember.js
DEBUG: jQuery : 1.9.1 ember.js
DEBUG: -------------------------------
Model:
App.Concert = DS.Model.extend({
tour_id: DS.attr(),
tickets: DS.attr()
});
Finding:
this.get("store").find("concert", {tour_id: 1}).then(function(result) {
console.log("--------------");
console.log(result.content.length);
console.log("--------------");
}, function(error) {
console.log("broken");
});
Console:
-------------
length: 0
--------------
I can't find records from my model by properties. Anybody can help?
I know what records with tour_id: 1 exist
Edu is actually partially correct (see below), but the real reason that you don't get back any records is most likely the format that your API returns. Ember Data is very strict about the type of data it expects and how it is structured. If you query your API for concerts with a tour_id of 1, then this is how the response should look like. Notice that the root object is the pluralized model name and the key is an array of concerts.
{
"concerts": [
{
"id": 1,
"tour_id": 1,
"name": "cool concert"
},
{
"id": 5,
"tour_id": 1,
"name": "awesome concert"
}
]
}
Regarding Edu's comment, if you invoke the store's find method, Ember Data doesn't perform a regular find. Under the hood, findQuery is invoked and it returns an instance of DS.RecordArray. This isn't a regular JavaScript array so you need to respect how Ember works and how it deals with arrays.
I recommend reading up on DS.RecordArray in the Ember guides. Also, if you plan on using Ember Data, you shouldn't be scared by the idea of exploring its source. Ember Data is a ambitious project and the documentation is still very much a work in progress. Ember Data's source is therefore your most important source of information.
try using getters...
console.log(result.get('length'));
Related
Background
I have decided to deploy Strapi as a headless CMS/backend for my project and have been quite happy with the ease of setup out of the box. However, a part of my project requires capturing user input through a simple form and I thought extending Strapi's REST API through writing custom plugin and utilizing the same backend is the way to go instead of spinning up another express server with its own DB. I was able to find documentation on generating a plugin and I have a good understanding of how the file structure and logic of the different plugin files work (model, controllers, policies, config, routes etc...). However, for the life of me, I have not been able to find the most basic resources or any adequate documentation on how to write logic allowing my controllers to hook into the basic CRUD methods that Strapi has created for my custom collection ('messages' in my case).
What I have done so far
Generated a plugin
Strapi generate:plugin contact-book
In plugins\contact-form\config\routes.json. Created a /postMessage route which I tested successfully after allowing public access to it from the Admin panel
{ "routes":
[
{
"method": "POST",
"path": "/postMessage",
"handler": "contact-form.postMessage"
}
]
}
In plugins\contact-form\models. I created an empty model file Message.js & Message.settings.json containing the definition of my model 'message' which defines a collectionType collection names 'message' with its fields as follows:
{
"kind": "collectionType",
"connection": "default",
"info": {
"name": "message",
"description": "This represents the Message Model"
},
"attributes": {
"name": {
"default": "",
"type": "string",
"required": true
},
"email": {
"default": "",
"type": "email",
"required": true
},
"message": {
"default": "",
"type": "text",
"required": true
}
}
}
Upon dev server restart, Strapi has already recognized my plugin and has reflected the 'message' model in its DB and I can see the collection from the admin panel correctly.
What I need help with
What I would like to do, is to extend the plugin's REST API endpoints to provide the same functionality as the out of the box end points that Strapi builds whenever a new collection/entity is created, while adding a custom layer of business logic to it.
I would appreciate any one pointing me towards an example or a resource that shows the methods or functions that Strapi exposes to plugins which can be hooked into or invoked to achieve this.
[Update] 14-June
So after hours of logging and inspecting 10s of objects that Strapi exposes to the controller, I was able to find that the strapi object exposes a query method which accepts 2 parameters, the first being the model and second is the plugin name. So simply speaking, the following enabled me to perform a write to my collection:
postMessage: async (ctx) => {
const testData = {
name: 'John Doe',
email: 'john#doe.com',
message: 'Hello World!'
}
result = await strapi.query("message","contact-form").create(data)
}
However, I still do not consider this an answer since I would like to find a more comprehensive approach through which built in policies and services can be used. Furthermore, I am still not sure if invoking this method bypasses any layers of middleware that Strapi sets up for default controllers and therefore exposes the app to security or stability risks.
The above can be achieved by utilizing the methods exposed by the built-in Strapi entityService. By examining how the stack handles create operations via the automatically build API endpoints, I was able to identify the entityService as the appropriate module for this functionality. However, when passing the plugin's model name to strapi.entityService.create({data} , {model: 'modelName') , the service was not able to locate the modelName. So I examined the service's source code and found out that it accepts the model UID instead. So in the case of a custom plugin, instead of just passing the modelName we need to pass the modelUID which is formatted as such:
plugins::plugin-name.modelName
In summary, for a create operation against the example in my question, this is how it would be:
const result = await strapi.entityService.create(
{ data: ctx.request.body },
{ model: "plugins::contact-form.message" }
)
all other CRUD operations that Strapi supports are also exposed by the entityService and can be accessed in a similar fashion (Create, Update, Find, FindOne, Etc...). You can find all these methods under the Strapi documentation > Concepts > Controllers > Core Controllers.
I have also created a YouTube video covering how this works:
https://www.youtube.com/watch?v=kIZHzbmnhnU
I have an API that returns one resource at a time.
As an example, say I have two related resources:
Topics
Posts
Topics hasMany Posts, and Posts belongsTo a Topic.
According to the documentation (and everything I've tried and have been reading about), Ember-Data expects the JSON out of the API to be similar to the following, where both the data for Topic and the data for Posts are provided simultaneously.
"topic": {
"id": ...,
"title": ...,
"author": ...
},
"posts": [{
"id": ...,
"commenter": ...,
"text": ...
}, {
"id": ...,
"commenter": ...,
"text": ...
}]
As I mentioned at the start of the post, my API does not do this. It would return the Topic data in one call, and the Posts data for that Topic in a second call. Changing my API to work exactly how Ember wants it is not an option.
My question is, how can I extend/override the RESTAdapter and RESTSerializer in Ember-Data to support loading data from multiple API calls? Is this even possible?
I tried following this example, but it looks like the data here is already loaded (it tries to sideload data that is already known to the system): http://mozmonkey.com/2013/12/loading-json-with-embedded-records-into-ember-data-1-0-0-beta/
I also tried to override the extractFindAll function of the ApplicationSerializer, but can't figure out how to get the rest (no pun intended) of the application to wait for all of the relationships to load. I iterate through all of the relationships on the model, and load their data, but I don't see a way to shuttle that data back into the original payload:
extractFindAll: function (store, type, payload, id, requestType) {
var promises = [];
type.eachRelationship(function (key, relationship) {
promise.push(Ember.RSVP.Promise(function (resolve, reject) {
this.store.find(key, payloadEntry[key]);
}));
});
// can't figure out how to get the relationship payloads
// back to the original payload
Ember.RSVP.addSettled(promises).then(function () {
return this._super(store, type, payload, id, requestType);
});
}
Any help is appreciated.
That exactly describes using the async property.
Models
App.Foo = DS.Model.extend({
bars: DS.hasMany('bar', {async: true});
})
App.Bar = DS.Model.extend({
baz: DS.attr()
})
Adapters
App.FooAdapter = DS.RESTAdapter.extend({
//anything custom
});
App.BarAdapter = DS.RESTAdapter.extend({
//anything custom
});
If you don't do anything custom, you should just define App.ApplicationAdapter = DS.RESTAdapter; to be a site wide adapter.
Serializer
App.FooSerializer = DS.RESTSerializer.extend({
//anything custom
});
App.BarSerializer = DS.RESTSerializer.extend({
//anything custom
});
If you don't do anything custom, you don't need to define the rest serializers, they will use their default serializer associated with whichever adapter they're using.
Example
Important to note, I was lazy and my mock response for bars always returns the same thing regardless of any call to /bar*
http://emberjs.jsbin.com/OxIDiVU/481/edit
I have an existing service written with the .NET Web API.
As an example, this service returns JSON in the following format:
[
{ "id": 1, "name": "John" },
{ "id": 2, "name": "Jane" }
]
However, as per the Ember.js Rest Adapter documentation, Ember would expect JSON in the following format:
{
"persons": [
{ "id": 1, "name": "John" },
{ "id": 2, "name": "Jane" }
]
}
Because of this, Ember is returning the following error:
Your server returned a hash with the key 0 but you have no mapping for it
By no means do I plan on changing my service API and how it returns data.
Would it be possible to get Ember.js (latest version) to work with the existing data my service is returning? And, if so, how can I implement that?
Ember is very flexible in that sense, giving the ability to extend the adapter and serializer in order to integrate your app with any backend API.
You should get the WebAPIAdapter, which is part of the Ember.js Template for Web API.
Additionally, you might wanna take a look into this project I wrote as an example, based on that same template (with some modifications I did on my own). It's still under development and it doesn't feature all the best practices (yet), but I'd say it's a valid example.
You should also take a look into this repo / library (You can install it via NuGet too), which allows you to pre-compile your Handlebars templates directly into Ember.TEMPLATES collection.
in the web api just return new { object }
var persons = _personbService.GetPeople;
return new { persons };
this will wrap your object in an object of the same name. If you need to call the object something else just do
new { SomeOtherName = persons; }
I was having this issue in ember and found the best solution for me was to build a new serializer and override the normalizePayload method. code below:
export default DS.RESTSerializer.extend({
normalizePayload: function(payload) {
var root = "posts";
var output = {};
output[root] = payload;
return output;
}
});
This wraps the initial response and adds the root to it, hope it helps!
Here what my json data look like:
[{"id":1,"iam":1,"youare":2,"lat":50.8275853,"lng":4.3809764,"msgbody":"Lorem ipsum lorem ipsum lorem ipsum"}]
Here's my Ember code:
window.Messages = Ember.Application.create();
Messages.Store = DS.Store.extend({
revision: 11
});
Messages.MessagesRoute = Ember.Route.extend({
setupControllers: function(controller) {
controller.set('content', Messages.Message.find());
}
});
Messages.Message = DS.Model.extend({
msgbody: DS.attr('string')
});
Messages.MessagesController = Ember.ArrayController.extend({
content: []
});
The thing is my json data live in /app_dev.php/messages not in /messages/ ...
I am just trying to do just a successful get request but I can't manage... Could you tell what I am doing wrong so I can get some grasp of the Ember syntax?
EDIT Thanks for your answers.Just to inform that after lots of effort to make something trivial, I tried Angular and it seems to do the job better,faster and easier. So I'm switching frameworks.
You'll want to use the namespace option on the adapter. This can be specified as followed and then when you create the store use the MyApp adapter.
MyApp.Adapter = DS.RESTAdapter.extend({
namespace: 'app_dev.php'
});
I think that it is better to specify file names via the url option:
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.create({
url: "/app_dev.php"
})
});
The structure of your JSON should be as follows:
{
messages: [
{"id":1,"iam":1,"youare":2,"lat":50.8275853,"lng":4.3809764,"msgbody":"ipsum "},
{"id":2,"iam":1,"youare":2,"lat":50.8275853,"lng":4.3809764,"msgbody":"Lorem "}
]
}
See ember guide for further reference.
Ember requires that you use what they call "type keys" in order to successfully read JSON, in other words you need to put the name of a model prior to the object itself for Ember to recognize that is of that type.
In my case, I had made a Java/Spring Backend and did not want to add this to my objects, to get around this in ember you can create a Serializer file, documentation for Ember Serializers, specifically the RESTSerializer can be found here.
To add the typeKey to incoming JSON overide the normalizePayload hook. Here is an example of how I did that:
normalizePayload: function(type, payload) {
var json = {};
json[type.typeKey] = payload;
return json;
}
If you hadn't put your typeKey's on the JSON on the way out, you'll probably experience similar problems when you PUT or POST data, so you will probably need to overload the serializeIntoHash hook to remove the typeKey's on outgoing data. Here is an example of what worked for me on that:
serializeIntoHash: function(hash, type, record, options){
Ember.merge(hash, this.serialize(record, options));
}
Hope that helps! I see you are switching to Angular (Great decision, I prefer Angular 100x over Ember but my work requires I use Ember currently, so I'm fighting through it.) But hopefully this can help someone else who is having similar issues and either wants or is forced to use Ember.
The question: The documentation is scarce, and I'm something of a noob -- can anyone confirm the proper (assuming there is one) way to bind Backbone.Views to instances of Backbone.RelationalModel (from backbone-relational.js) for updating/rendering to the dom? I've tried a handful of different approaches, based on the normal Model/View binding in Backbone, with little success.
The backstory (/more info):
I'm learning the ropes with Backbone.js, and have had to pick up a lot over the past week. If I'm missing something obvious (which is highly likely -- including the "right" way to handle my problem below), please call me out.
I'm dealing with a mongodb-backed REST interface (that I don't have full control over -- or I would be re-architecting behavior on the server-side) that takes heavy advantage of nested dictionaries, so I've been reading up on how to best represent that in Backbone (while not breaking the great save() + server sync stuff that Backbone provides).
I've seen two options: backbone-relational and ligament.js.
I've started with backbone-relational.js, and have RelationalModels (backbone-relational's replacement for Backbone's standard Model) created for the various dictionaries in the tree that gets handed back by REST interface. The relationships between them are defined, and console logging the JSON from each model (in their respective initialize functions) shows that they're all being called/loaded up correctly off the server on a fetch() command at the overall collection level.
So, that's all great.
Problem: I've got views "listening" for updates on each of those models (and bound functions that should render templates on the dom), and they never "fire" at all (let alone render...). The main view fires on fetch(), no problem, loading the "top level" model and rendering it on the dom -- but the views that represent the "foreign key" models within that "top level" model never do (even though the data is DEFINITELY getting loaded into each model, as evidenced by the console logging on each model mentioned above).
Any insights would be greatly, greatly appreciated.
In direct response to Raynos reply below (thanks Raynos!):
If I defined a base url for the UpperLevelCollection with the UpperLevelModels existing at (UpperLevelCollection url)/(UpperLevelModel id) on the server, how would I map those LowerLevelCollections to dictionary keys within the one JSON dump for each UpperLevelModel from the server-side? In other words, could using collections within models properly handle a data dump from the server like this (obviously very simplified, but gets at the issue) AND properly save/update/sync it back?
[{
"some_key": "Some string",
"labels": ["A","List","Of","Strings"],
"content": [{
"id": "12345"
"another_key": "Some string",
"list": ["A","list","of","strings"],
},{
"id": "67890"
"another_key": "Some string",
"list": ["A","list","of","strings"],
}],
}]
Generally for nested dictionaries I take the following approach
var UpperLevelCollection = Backbone.Collection.extend({
model: UpperLevelModel
}),
UpperLevelModel = Backbone.model.extend({
initialize: function() {
this.nested = new LowerLevelCollection;
}
}),
LowerLevelCollection = Backbone.Collection.extend({
model: LowerLevelModel
}),
LowerLevelModel = Backbone.Model.extend({});
Just nest those collections inside models all the way down.
The problem might be that as you load new data into you ParentModel, your child collection AFAIK is not actually fetched, it's wiped and replaced by a new collection (see Backbone.HasMany.OnChange on line 584 in backbone-relational.js). Thus your own bindings on the child collection are gone.
This is, in my opinion, a weakness with backbone-relational. This behavior should be configurable, with an option where a slower find-and-update-approach is used instead of wipe-and-replace.