How to retrieve the same model from another REST url in EmberJS - ember.js

The models tree of my emberJS app match the tree of my API however, I have 2 different routes returning the same type of data :
/products/ and /users/:id/supported_products/ both return products data.
When I need to have the products of the app there is no problem :
this.store.query('product',params);
However I am not sure how to query products from the user path. The place to do so would be the adapter, but I need to define a secondary adapter that I would call when I need supported products,and I have no idea how to do so.

I think if it were me I would create a virtual query parameter that would instruct a custom adapter on how to change the endpoint on the fly.
For example I might have a supportedByUser flag. Then in my app/adapters/product.js do something like this:
import JSONAPIAdapter from 'ember-data/adapters/json-api';
export default JSONAPIAdapter.extend({
urlForQuery(query, modelName) {
let userId = query.supportedByUser;
delete query.supportedByUser;
return userId
? `${this.namespace || ''}/users/${userId}/supported_products`
: this._super(...arguments);
}
});
Here is an example twiddle demoing this: https://ember-twiddle.com/b406391e98ed4fda30bc227a894fa7c9

Related

Losing tons of ember-data class attributes when assigning query response to model field

In my EmberJS app, I have two models, Book and Page. Book has a hasMany relationship to Page. In my controller to edit a single book, I am making a query to get all pages related to that book, and then assigning the response from the query back to the Book's page field.
I am using a third party EmberJS library ember-models-table to make paginated tables. I pass in book.pages to the paginated table component, and all works fine, except when I want to actually move between pages of results. The version of ember-models-table I'm using expects certain fields on book.pages to be present when paginating results: meta, query, store, and type.modelName. I can explicitly set those fields onto book.pages, but I have a feeling there has got to be a better way. Why would EmberJS strip fields like meta and store, from an ember-data query response, when you set that response onto another object?
Controller
this.get('store').query('page', { bookId }).then((res) => {
set(book, 'pages', res);
// Third Party Paginated Table Component Requires These Fields
set(book, 'pages.meta', res.get('meta'));
set(book, 'pages.query', res.get('query'));
set(book, 'pages.store', res.get('store'));
set(book, 'pages.type.modelName', res.get('type.modelName'));
});
HBS - Pages table
{{models-table-paginated
data=book.pages
columns=columns
customClasses=(hash table='table table-condensed')
showGlobalFilter=false
showColumnsDropdown=false
selectRowOnClick=false
pageSize=10
}}
You need to edit your serializer too. For example, i use models-table too but in backend, i use Django adapter. So i had to edit the serializer for each model to calculate the remaining pages (totalPages) and form that number to make the pagination based on your pageSize. Also at query on your route, you need to set pageSize and page as default. Now if you don't use meta like Django you should check your adapter documentation
for example, in Django the serializer for book model will be like this
import DRFSerializer from './drf';
import DS from 'ember-data';
import { isNone } from '#ember/utils';
export default DRFSerializer.extend(DS.EmbeddedRecordsMixin,{
extractMeta: function(store, type, payload) {
let meta = this._super(store, type, payload);
if (!isNone(meta)) {
// Add totalPages to metadata.
let totalPages = 1;
if (!isNone(meta.next)) {
// Any page that is not the last page.
totalPages = Math.ceil(meta.count / payload[type.modelName].length);
} else if (isNone(meta.next) && !isNone(meta.previous)) {
// The last page when there is more than one page.
totalPages = meta.previous + 1;
}
meta['totalPages'] = totalPages;
}
return meta;
}

Find the class name of a relation from the instance of a model in ember JS

I have foo an instance of the ember-data model thing. thing.js has the following property :
owner: DS.belongsTo('user')
If I have foo with an empty owner, how can I, with only foo and the 'owner' string, retrieve the value 'user' representing the model of the owner relation?
EDIT: I want to allow my select-relation component to works with relations where the name is different from the class name
It sounds like you have some work to do to finish setting up your relationships. Have a read through this page of the guides.
If the relationships are set up correctly, to get the associated user, you should be able to do foo.owner. This assumes that users are already present in the store. I recommend using the Ember Inspector browser plugin to debug the relationships.
This looks like a use case for typeForRelationship.
In your example you should be able to do something like
store.modelFor('thing').typeForRelationship('owner', store);
If you don't like that approach you can use the belongsTo reference API, where you use the meta data from the relationship to get the type
foo.belongsTo('owner').type
The only thing with that approach is that the type property may not be public API and possible (though unlikely) to change at some point.
It seems I can do the following :
this.get('model').get('_internalModel._relationships.initializedRelationships.'+this.get('relation')+'.relationshipMeta.type')
model being an instance and relation the string of the relation name, it correctly return the model of the relation.
EDIT : a better solution not using private API courtesy from the ember discord :
function getRelatedModelName(record, relationName){
let ParentModelClass = record.constructor;
let meta = get(ParentModelClass, 'relationshipsByName').get(relationName);
return meta.type;
}

Can i hide the query params from the URL?

In my Ember.js Application, I am dealing with query params for list updates. I have one strange use case, in which I don’t the URL to be updated with certain query params. How can I achieve this?
I assume you want to reload your model with parameters that are different than the ones in your application route? And you keep your application route parameters synced using queryParams?
In your route's model function you can filter your model data by the same query params (that appear in the address bar) but you can add some logic that extracts additional parameters either from the controller or other place and these parameters the data fetching query. Example:
model: function(queryParams) {
var params = queryParams;
params.additional_filter = this.controllerFor('mycontroller').get('additional_filter');
return this.store.find('mymodel', params);
}
Also if you want to explicitly reload the model you will need to call Router.refresh() function.

Custom Model URL

Is there currently (in the latest builds) a way of specifying a URL on a model-by-model basis? in Ember Data 1.0 beta? I have found some questions on SO and issues on Github around this, but most are out-dated.
For example, I have a model that's called App.PaymentSearchResult and rather than having the request go to /payment_search_results I would like it to go to /payments/search. Where would I override the URL used for a given model (rather than overriding buildURL on the RESTAdapter)?
You can override the the find adapter
but it's kind of hackish, i think however i would take another approach. Idealy you want your Ember models to reflect your backend's models, so why would you need a PaymentSearchResult? When you probably already have a Payment model?
If you need to search in your payment records, why not handle it using query params?
http://emberjs.com/guides/models/finding-records/#toc_querying-for-records
this.store.find('payment', { total: "22" });
Then you want to answer accordingly on the server.
If you want to do a search which returns multiple models, you do this with a manual ajax request.
var self = this;
$.get( "/search", { name: "John", time: "2pm" }, function(result) {
self.store.pushMany(result);
});
PushMany assumes a sane JSON structure.
http://emberjs.com/api/data/classes/DS.Store.html#method_pushMany

Get server URL for Ember DS.Model Class

In using Ember Data for my models, there are some cases where I need to work around the data limitations and access other quasi-restful URLs on my server.
For example, I have a Feed object that records a stream of data. For accessing the models I have a RESTful endpoint:
/feeds/:feed_id
In order to start and stop recording a feed, I need to send a PATCH to a url like:
/feeds/:feed_id?update_action=start
Subsequently I can reload my model and see the changes reflected therein.
In this case, I need to access $.ajax and the URL is the same as the one Ember would use. However, I can't figure out how to eke this information out of Ember.
So far, the best I can do is:
DS.Model.reopen
rootForModel: Ember.computed( ->
#.store.adapterForType(#).serializer.rootForType(#.constructor)
)
pluralRootForModel: Ember.computed( ->
#.store.adapterForType(#).serializer.pluralize(#get("rootForModel"))
)
Such that for an instance of App.FeedItem I can do:
this.get("rootForModel") # feed_item
this.get("pluralRootForModel") # feed_items
And I'm guessing this would stay in sync with any settings made in the Adapter etc.
Subsequently, I can call like:
$.ajax
url: #get("pluralRootForModel") + "/" + #get("id")
data:
update_action: "start"
type: "PATCH"
Is this totally out in left field? Is there a more direct way to compose these URLs?
Another (related issue) is getting the underscored name for a given model.
App.MyModelController => my_model_controller
I've done something like:
Ember.Object.reopenClass
###*
* The underscored name for this.
* i.e. App.MyClass -> my_class
* From an instance, use this.constructor.underscored_class_name()
* #return {String} This classname, underscored.
###
underscored_class_name: ->
_.underscored("#{#}".replace(/^.*?\./g, ""))
Is this crazy? Are there any better ways?
Check out buildURL in DS.RESTAdapter.
If you want to use underscores in server paths and keys, check out DS.ActiveModelAdapter (and its default serializer, DS.ActiveModelSerializer). This adapter has its own implementation of buildURL.