3

I'm trying to wrap my head around dynamic segments and I want to be able to use a slug or other property instead of the id. When I can get things working it feels like a fluke. (I'm using ember 2.7+)

I've been looking at the following sources/threads for ideas: https://guides.emberjs.com/v1.10.0/cookbook/ember_data/linking_to_slugs (1.10) http://discuss.emberjs.com/t/slugs-for-replacing-id-path-for-dynamic-segments

I plan on using ember-data, but I want to ensure I'm in control - and so I don't want to use the :post_slug / underscore style that has some built in magic that I want to avoid.

Here is an ember-twiddle Here are step-by-step commits in a github repo



My thought process


1. Conceptually, lets say I need a list of cats - so I need to describe the model for what a 'cat' is.

models/cat.js

import Model from "ember-data/model";
import attr from "ember-data/attr";

export default Model.extend({
  name: attr('string'),
  slug: attr('string')
});


2. define where the dynamic segment will be in the url. I'm going to use catId to prove a point instead of cat_id or :id like most of the tutorials I've seen. For this example, I'm also writing an actual app structure instead of the smallest router possible - to test the edges of this. + what if I needed something like this later: animals.com/cats/:catId/best-friend/:dogId

router.js

Router.map(function() {
  this.route('index', { path: '/' });
  this.route('cats', { path: '/cats' }, function() {
    this.route('index', { path: '/' }); // list of cats
    this.route('cat', { path: '/:catId' }); // cat spotlight
  });
});


3. pull in the catData into the store ~ in the /cats route

routes/cats.js

import Ember from 'ember';

const catData = [
  {
    id: 1,
    name: 'Dolly',
    slug: 'dolly'
  },
  {
    id: 2,
    name: 'kitty cat',
    slug: 'kitty-cat'
  },
  {
    id: 3,
    name: 'Cleopatra',
    slug: 'cleo'
  }
];

export default Ember.Route.extend({
  model() {
    return catData;
    // return this.get('store').findAll('cat'); // with mirage or live api
  }
});


Update from comments:

I do not believe that you can use queryRecord with your test data. Ember data plays dumb with query and queryRecord; it doesn't assume anything about your data and just passes the call on to your server.

~ @xcskier56

So this kinda blows my twiddle as is. The git repo example is Mirage.


4. create the templates... + set up the 'cat' route. The records are in the store... right? so I should be able to 'peek' at them based on id. The docs use params - but -

Ember will extract the value of the dynamic segment from the URL for you - and pass them as a hash to the model hook as the first argument

: ~ and so the params object name isn't special and could really be anything you wanted... and is just replaced with a hash - so to that point / I'm using 'passedInThing' just to assert control over the confusing conventions (many tutorials use param instead of params)

routes/cats/cat.js

model( passedInThing ) {
  return this.store.peekRecord('cat', passedInThing.catId );
} // not going to happen - but in theory...

model( passedInThing ) {
  return this.store.queryRecord('cat', { slug: passedInThing.catId } );
}


5. At this point, I should be able to navigate to the url /cats/2 - and the 2 should get passed through the model hook - to the query. "Go get a 'cat' with an id of 2" --- right??? ((the twiddle example uses a hard-coded set of catData - but in my other attempts I'm using mirage with a combination of fixtures and dynamic slugs: https://github.com/sheriffderek/dynamic-segments-tests/commits/queryRecord


6. Typing in the segment works - but for link-to helpers I need to pass in the explicit cat.id

{{#link-to 'cats.cat' cat.id}}
  <span>{{cat.name}}</span>
{{/link-to}}


7. I can get all that working - but I don't want an ID in the URL. I want cats/cleo with the 'slug' ~ in theory I can just switch catId for catSlug and cat.id to cat.slug etc - but that is not the case. I've seen many tutorials outlining this but they are outdated. I've tried passing in { slug: params.slug } and every combo of find query and peek etc. Sometimes the url will work but the model wont render - or the opposite.


8. This seems like 101 stuff. Can anyone explain this to me? Oh wise emberinos - come to my aid!


UPDATES

sheriffderek
  • 8,848
  • 6
  • 43
  • 70

1 Answers1

2

I've struggled with this same issue, and AFIK there is not a way to handle this without querying the server. While this is old and the code is a little out dated the logic still stands. http://discuss.emberjs.com/t/use-slug-for-page-routes-id-for-store-find/6443/12

What I have done is to use store.queryRecord() and then my server is able to return a record fetched via a slug.

This is what the route would look like:

model: function (params) {
  return this.store.queryRecord('cat', {slug: params.catSlug})
}

This will enable you to not expose the ID in the url, but it will issue a query to the store every single time that the model gets hit. There seems to be some discussion of caching query and queryRecord in ember-data, but nothing working yet.

Other helpful resources:

Community
  • 1
  • 1
xcskier56
  • 611
  • 3
  • 11
  • Regarding the 'peek' - Here is a twiddle a kind soul put together to show a way to get a peek-like queryRecord: ember-twiddle.com/538e9b1437442c2fd34f226c710777bd – sheriffderek Jul 01 '16 at 06:08
  • Regarding the slug in general - (I'm totally OK hitting the server as long as it works) 1. this.route('cat', { path: '/:catSlug' }); 2. {{#link-to 'cats.cat' cat.slug}} 3. return this.store.queryRecord('cat', {slug: params.catSlug}) These three things are the only moving parts right? Is the problem that I can't do this with static data for my test purpose? - because even when I use queryRecord things aren't as I expect. – sheriffderek Jul 01 '16 at 06:09
  • Correct I do not believe that you can use queryRecord with your test data. Ember data plays dumb with `query` and `queryRecord`; it doesn't assume anything about your data and just passes the call on to your server. If you want to keep testing this with `queryRecord` I'd recomend looking into http://www.ember-cli-mirage.com/. It will allow you to create a simple server in ember that you can then respond to the server query with – xcskier56 Jul 01 '16 at 12:34
  • OK. Thanks. I have a test setup with mirage already. I'll try what you suggest there and see if it works. I also have a live API setup too - but was trying to get the test scenario as lean as possible. – sheriffderek Jul 01 '16 at 22:23
  • Shoot! I just checked my mirage version and I had it setup with queryRecord but I still get this: "Error while processing route: cats.cat Assertion Failed: Expected the primary data returned by the serializer for a `queryRecord` response to be a single object but instead it was an array" – sheriffderek Jul 01 '16 at 22:37
  • "Successful request: GET /cats?{"slug":"mr-butterkins"}" – sheriffderek Jul 01 '16 at 22:52
  • @sheriffderek, you are returning an array when you should return an object. To solve this you can use `queryRecord` and do: `... then((res) function() {res.get('firstObjtect') ...})`. That will work. – xcskier56 Jul 02 '16 at 13:35
  • I'm unsure why it's returning an array. When I log params I get: params: Object {catSlug: "mr-butterkins"} --- that being said, the array seems to be all of the cats... so grabbing firstObject works - but only pulls the model data of the firstObject for all of them. EDIT... nm - I was playing with just query in that case to try something... : / – sheriffderek Jul 02 '16 at 17:59
  • This seems to be turning into a question of its own. If you can ask this as another question and post your `mirage/config.js` file I'd be happy to help. – xcskier56 Jul 02 '16 at 21:54