Modifying the ActiveModelAdapter


#1

I am switching an ember-model application to ember-data (beta 3) to take advantage of the store. Most of the transition was straightforward, but I’m trying to find documentation on how to modify the ActiveModelAdapter (which extends the default RESTAdapter). I need to find records by “code,” which is unique, but not the model’s ID. The server api is /mymodel/code:the-unique-code. The JSON returned is identical to a normal request.

In the ember inspector data pane the id listed is code:123 instead of the model’s ID. when I execute this.get('store').find('model', 'code:123). The promise seems to resolves correctly, though I’d like to get a better understanding of the right way to handle this.


#2

The source for ActiveModelAdapter is here https://github.com/emberjs/data/tree/master/packages/activemodel-adapter

The ActiveModelAdapter is designed to work out of the box with ActiveModelSerializer gem in rails projects.

One way to use it is to create an “ApplicationAddapter” in your store.js file and extend the Adapter

App.ApplicationAdapter = DS.ActiveModelAdapter.extend({});

You could conceivably over write some defaults and implement some custom behaviors here.

That being said, given your description it sounds like you have two fields in your model?

model ID = some numeric id

code = code:123, etc

Do you have control on the server? Why not emit the ids in your specific format and make code and id the same thing? There is nothing that says the id has to be a number I don’t think.

id = code:123

id = code:456

etc

The id ultimately gets set on the server when you save the records so you can control the format of the id.

You may also want to have a look at the private method normalizeId https://github.com/emberjs/data/blob/master/packages/ember-data/lib/serializers/rest_serializer.js#L162

There is a notion of a primaryKey attribute. The DS.JSONSerializer sets this to be id but it could possibly be set to some other attribute besides id. Not sure all the implications of that though. https://github.com/emberjs/data/blob/master/packages/ember-data/lib/serializers/json_serializer.js#L11

It sounds like you might want to create your own adapter though to deal with the specific use case of your app.

Hope that helps


#3

Yep, I have two fields. The id is an automatically assigned primary key used for relationships and assumed never to change. Code is a unique alphanumeric string used somewhat like a barcode. I’d prefer not to make the id and code the same value, since during the workflow of creating the model, the code is often set after the model is created, or changed at a later point.

I assume I have to add some logic to the serializer or adapter since with since I used to do something like this for ember-model:

Library.RESTAdapter = Ember.RESTAdapter.extend
  didFind: (record, id, data)->
    rootKey = Ember.get(record.constructor, 'rootKey')
    dataToLoad = if rootKey then data[rootKey] else data

    if /^code:/.test(id)
      id = dataToLoad.id

    record.load(id, dataToLoad)

Library.CheckoutAdapter = Ember.RESTAdapter.extend()

My understanding was the serializer was responsible for turning a received JSON into a model and vice versa, while the adapter handled making requests on behalf of the store.


#4

In that case you could try passing your model hook the code id to get the model by code id instead. I think the syntax would be something along these lines:

  model: function(params) {
    return this.get('store').find('post', {code: params.code_id} );
  }

This should create a querystring passed to your backend that you can then filter on. So you would still need a way on the rails side to filter based on code id.

example.com/model?code=code:123

The open question is where on the client side are you needing to use the normal model id.

Perhaps you want to create two routes, one that displays data based by code id and another that displays data based on regular model id. e.g.

  model: function(params) {
    return this.get('store').find('post', {code: params.model_id} );
  }

Your index route could continue to list results by model id and your client side identity map can work as normal.

Hope that makes sense.


#5

Retrieving the model via params was my fallback plan. What I’m doing is querying for an alternate key. It didn’t seem RESTful to make a filtering query when I know there should only be a single record returned. Plus, I’ll have to add a bit of inelegance since the returned record will be in an array of length 1, rather than by itself.


#6

It’s looking like I want to add a findByCode method to the store, which means subclassing the store, and probably overriding a bunch of methods. Is there any support for overriding the default store? My understanding is that with Beta 3, the store is created in the background and made available automatically, which makes me feel like I’m going down the wrong path.

However, the following works fine for me, other than leaving false records in the store with the id “code:123” and no other data.

      @get('store').find('patron', "code:123").then (the_model)=>
        ... do stuff with returned model ...

They don’t seem to hurt anything, but it seems unclean.


#7

On the surface of it I personally don’t see too much wrong with what you got going on here.

Also, I think you could follow two possible paths here.

1.) Override the existing store.

App.Store = DS.Store.extend({
    // specific implementation
});

2.) Create an alternate store that has the behavior you want. This might be create some extra work but each model could then reference the different stores depending on what your model needs. Depending on the complexity this could get messy. Because you would have two backing stores to think about. But perhaps it makes sense for your use case, because it keeps that specific implementation separate from the default one. One way to isolate some of the behavior.

What I am not sure about is moving models between stores. This is probably some work that has to be done in the individual router or controller.

If you want to register a different store, you could try creating an initializer to do that work.

Have a look at how the default store gets initialized.

The ActiveModelAdapter initializes its adapter and serializer here https://github.com/emberjs/data/blob/master/packages/activemodel-adapter/lib/initializers.js

The default initializer for ember data:

https://github.com/emberjs/data/blob/master/packages/ember-data/lib/initializers.js#L46


#8

Ok. I’m going to leave it as is for now, and try extending the store when I have a better understanding of Ember.

Thanks, Louis