0
votes

How to call a nested Rails route from Ember app if I don't need nested templates ? I have the following routes in Rails router:

# routes.rb
resources :shops do
  resources :shop_languages
end

So to get a list of shop languages the shops/:shop_id/shop_languages should be hit.

Here is ShopsLanguagesController:

# controllers/shop_languages_controller.rb
class ShopLanguagesController < ApplicationController
  before_action :find_shop

  def index
    json_response @shop.shop_languages, :ok, include: 'language'
  end

  private

    def find_shop
      @shop = Shop.find(params[:shop_id])
    end
end

In Ember app I have the routes defined as follows:

# router.js
Router.map(function() {
...
  this.route('languages', { path: '/shops/:shop_id/shop_languages'});
});

In Ember application.hbs template the languages link is defined as follows

# application.hbs
{{#link-to 'languages' currentUser.user.shop.id class="nav-link"}}
..
{{/link-to}}

In Ember languages.js route handler, I'm trying to load shop languages:

# routes/languages.js
model(params) {
  return this.store.query('shop-language', { shop_id: params.shop_id })
}

Ember hits /shop-languages end-point instead of the nested one shops/:shop_id/shop_languages.

Of course, I've defined the corresponding models on Ember side:

# models/shop-language.js
import DS from 'ember-data';

export default DS.Model.extend({
  shop: DS.belongsTo('shop'),
  language: DS.belongsTo('language'),
  modified_by:  DS.attr('string')
});

What is wrong with that and how to get it work? Thank you

2

2 Answers

3
votes

The short answer; requests made to the server do not match your route structure, but are based on your model and the adapter you're using e.g. REST vs JSON API based on that the URL called at your server may differ.

So to get your /shop/:id/ added as a prefix for your shop-language query you'll have to override the default behavior of your adapter.

To get started generate an adapter for your model ember g adapter shop-language

In your new adapter you'll probably have to override 2 functions

1) query (https://github.com/emberjs/data/blob/master/addon/adapters/rest.js#L549)

2) urlForQuery (https://github.com/emberjs/data/blob/master/addon/-private/adapters/build-url-mixin.js#L188)

Also look at buildURL (https://github.com/emberjs/data/blob/master/addon/-private/adapters/build-url-mixin.js#L55) which handles all different scenarios.

In 1) you probably want to remove the shop_id from the query params so you don't end up with an URL like /shops/1/shop-languages?shop_id=1

In 2) you'll have to add /shops/1 to your URL based on the passed query params

0
votes

Thanks to @dguettler here is a solution I came to to get it work.

On Ember side

Declare the languages route as follows:

#router.js
Router.map(function() {
  this.route('languages', { path: 'shops/:shopId/languages'});
  ..
});

Modify/create languages.js route handler as follows:

#routes/languages.js

import Route from '@ember/routing/route';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

export default Route.extend(AuthenticatedRouteMixin, {
  model(params) {
    return this.store.query('shop-language', { shopId: params.shopId});
  }
});

Create/modify shop-language.js adapter as follows:

#adapters/shop-language.js

export default ApplicationAdapter.extend({
  urlForQuery (query) {
    return this._buildShopUrl(query.shop_id)
  },

urlForCreateRecord(modelName, snapshot) {
  return this._buildShopUrl(snapshot.belongsTo('shop').id);
},

urlForDeleteRecord(id, modelName, snapshot) {
  return this._buildShopUrl(snapshot.id, id);
},

_buildShopUrl(shop_id, id) {
  if (id) {
    return `shops/${shop_id}/languages/${id}`;
  }
  return `shops/${shop_id}/languages`;
 }
});

Add a link to load shop languages to your template:

#templates/application.hbs

...
{{#link-to 'languages' currentUser.user.shop.id class="nav-link"}}
...
{{/link-to}}

Display shop languages in languages.hbs template:

#templates/languages.hbs

<ul class="list-group list-group-flush">
  {{#each model as |lang|}}
    <li class="list-group-item">{{lang.tag}}</li>
  {{/each}}
</ul>

On Rails API side

Declare a shop_languages resource as nested route:

#routes.rb
...
resources :shops do
  resources :shop_languages, path: '/languages'
end

Load shop languages in ShopLanguagesController:

#controllers/shop_languages_controller.rb

class ShopLanguagesController < ApplicationController
  before_action :find_shop

  def index
    json_response @shop.languages, :ok
  end

  private

  def find_shop
    @shop = Shop.find(params[:shop_id])
  end
end

I'll have one thing to do - remove ?shopId=613 from the URL and fix the params hash that contains a kind of double values:

Started GET "/shops/613/languages?shopId=613" for 127.0.0.1 at 2018-03-23 10:05:53 +0100
Processing by ShopLanguagesController#index as JSONAPI
  Parameters: {"shopId"=>"613", "shop_id"=>"613"}