Looking for help with properly calling json using Ember Data

I’m sorry man, I been faking it till I make it for the past 5 hours and no matter what I try I end up back at:

Error while processing route: words documentHash is null _normalizeDocumentHelper

It feels like the answer is probably a straightforward dasherize update to mirage/config.js. I’ve tried all sorts of combinations pulling in things I learned from @balint in Rock & Rock w/ Ember.js, but can’t find the right way to return the result in the URL.

The good news is the JSON response shows roughly what we’re looking for… it’s just in the Console instead of the address bar.

At the same time, depending on when you ask I might make a case for customizing the adapter at app/adapters/application.js , creating a new custom serializer at app/serializers/application.js, specifying the query parameters in the app/pods/words/controller.js file, or heck maybe even using the BuildURLMixin… All of which I’ve tried a dozen different ways (among other things like normalize().

All of this is to say I don’t expect you or anyone else to write all my code… and having done the homework to the best of my ability for several hours I can’t find the answer. :weary:

Sorry for slow reply… weekend. If you’re still working on this I’d say the most straightforward way is to use query params instead of a dynamic segment. That takes away the whole need to mess with dasherization, etc (you’d probably actually want to URI encode the word as well). It also uses the Ember constructs “as intended” a little more e.g. dynamic segments generally contain an id and ids are unique and usually derived from url-safe characters.

So in that case your router.js would look like:

Router.map(function() {
  this.route("home", { path: "/" });
  this.route("words");
});

and your word route would look like:

import Route from '@ember/routing/route';

export default Route.extend({
  queryParams: {
    word: {
      refreshModel: true
    }
  },

  model(params) {
    return this.store.queryRecord('word', params);
  }
});

Your mirage config would remain the same:

export default function() {
  this.namespace = "/api";

  this.get('/words', function({ words }, { queryParams }) {
    let { word } = queryParams;
    if (!word) {
      return words.all();
    } else {
      word = word.toLowerCase();
      return words.findBy({ word });
    }
  });
}

And then anywhere you define a link-to or transitionTo instead of {{link-to 'words' whatever}} you’d use {{link-to 'words' (query-params word=whatever)}} and of course the url would look like /words?word=some%20word (query params are automatically URI encoded and decoded so you don’t have to worry about spaces or special characters)

If you were to continue with the dynamic segment approach there are a number of different ways you could probably go, but most the simplest is probably to URI-encode the word before making a link or transition, and then in the model hook you’d decode the word before sending it to queryRecord. To do that nicely in a link-to you’d probably want to write a custom helper (i’ll it url-safe which isn’t a great name but whatever) that either dasherizes or URI encodes the word and then use it in a link to like this: {{link-to "words" (url-safe word)}}. Then in the model hook you’d want to do the exact opposite of the helper before sending the queryRecord request (because queryRecord will correctly encode the params before sending the AJAX request to the backend

Oil and gas people don’t like change so I’d like to stick to dynamic segments as the resulting URL is what most closely resembles the current site. For example:

and

To that end, I changed all the language in my code back to terms and term and pushed those changes to Github… Was using word/words to try and understand what is going on here.

At any rate, I’m really just looking for the easiest way to serve the dasherized URL /terms/snubbing-force when you type in /terms/snubbing force. Apologies if this is unnecessarily complex. Just trying to clone the current behavior as closely as possible.

Since adding dashes to the term itself screws up the way it looks on the page, I thought about adding an additional parameter "url": in the json with the dasherized term and then making that my query param. Could that be a solution?

Ok that all makes sense. There are a lot of things you could do… and it kinda depends on your long term use case here but if it were me I’d probably make a “slug” for each record (basically the dasherized/sanitized term) and instead of putting it on the record as “url” just use it as the id. We tossed around the idea of using the term itself as the id above but that’s not ideal, a slug however is (if you enforce this) guaranteed to be unique and to share a limited character set (ideally just lowercase alpha and ‘-’). So each record in the fixtures (or eventually server if you choose) would look like:

        {
            "id": "octahedral-layer",
            "url": "https://www.glossary.oilfield.slb.com/en/Terms/o/octahedral_layer.aspx",
            "word": "octahedral layer",
            "letter": "o",
            "definitions": [
              {
                "speech_type": "1. n.",
                "category": "Drilling Fluids",
                "definition": "<div class=\"definition-text\">\n One of the layers that constitute the atomic\n <a href=\"/en/Terms/s/structure.aspx\">\n  structure\n </a>\n of the\n <a href=\"/en/Terms/c/clay.aspx\">\n  clay\n </a>\n <a href=\"/en/Terms/g/group.aspx\">\n  group\n </a>\n of layered\n <a href=\"/en/Terms/s/silicate.aspx\">\n  silicate\n </a>\n minerals. The structure of these minerals can consist of two, three or four layers. The octahedral\n <a href=\"/en/Terms/l/layer.aspx\">\n  layer\n </a>\n is a plane of aluminum hydroxide octahedra (aluminum at the center and hydroxides at all six corners). Another\n <a href=\"/en/Terms/s/structural.aspx\">\n  structural\n </a>\n layer is a plane of silicon dioxide tetrahedra (silicon at the center and oxygen at all four corners of the tetrahedron). The tetrahedral and octahedral layers fit one on top of the other, with oxygen atoms being shared as oxide and hydroxide groups.\n</div>\n",
                "see": [
                  {
                    "title": "bentonite",
                    "link": "https://www.glossary.oilfield.slb.com/en/Terms/b/bentonite.aspx"
                  },
                  {
                    "title": "silica layer",
                    "link": "https://www.glossary.oilfield.slb.com/en/Terms/s/silica_layer.aspx"
                  },
                  {
                    "title": "tetrahedral layer",
                    "link": "https://www.glossary.oilfield.slb.com/en/Terms/t/tetrahedral_layer.aspx"
                  }
                ],
                "more_details": [

                ],
                "synonyms": [

                ],
                "antonyms": [

                ]
              }
            ]
          }

The really important thing here is that you not just dasherize the term but also make sure it is lowercase and wiped of any special characters (apostrophe, dots, etc). But once you do that the code becomes really simple. You can just use find-record in your model hook, and the mirage config can be as simple as:

export default function() {
  this.namespace = "/api";

  this.get('/words', function({ words }, { queryParams }) {
    let { word } = queryParams;
    if (!word) {
      return words.all();
    } else {
      word = word.toLowerCase();
      return words.findBy({ word });
    }
  });
  // shorthand route handler will return a single record based on id, perfect for findRecord!
  this.get('/words/:id');
}

Hmmm… A bad HTTP response code (404) was received when fetching the script.

glossary-app/mirage/config.js

export default function() {
  this.namespace = "/api";

this.get("/terms", function({ terms }, { queryParams }) {
  let { term } = queryParams;
  if (!term) {
    return terms.all();
  } else {
    term = term.toLowerCase();
    return terms.findBy({ term });
  }
});
  this.get("/terms/:id");
}

app/router.js

import EmberRouter from "@ember/routing/router";
import config from "./config/environment";

const Router = EmberRouter.extend({
  location: config.locationType,
  rootURL: config.rootURL
});

Router.map(function() {
  this.route("home", { path: "/" });
  this.route("terms", { path: "/terms/:id" });
});

export default Router;

app/pods/terms/route.js

import Route from "@ember/routing/route";

export default Route.extend({
});

app/models/term.js

import DS from "ember-data";

export default DS.Model.extend({
  term: DS.attr("string"),
  id: DS.attr("string"),
  definitions: DS.attr()
});

When I use findRecord() at app/pods/terms/route.js it looks like this.

import Route from "@ember/routing/route";

export default Route.extend({
  model(params) {
    return this.store.findRecord("term", params.term_id);
  }
});

Which throws Error while processing route: terms Assertion Failed: id passed to findRecord() has to be non-empty string or number.

What am I missing?

Found the problem.

You may not set id as an attribute on your model.

Removed id: DS.attr("string"), from app/models/term.js and we’re back up and running.

Still not sure where this A bad HTTP response code (404) was received when fetching the script. error is coming from, but I’m guessing I can ignore it like the manifest.json 404.

Thank you!!

Nice! Yeah id is always implicit on the model so it doesn’t let you add it. The script error is weird, any more details as to what script generates the 404? Can’t imagine it’s anything related to the Ember app if things are working…

Found that too. I’m trying to use ember-concurrency to build a searchbox but haven’t properly directed it to query the mirage fixtures yet. Gonna get to that soon. For now, FINALLY building ALL the conditionals for these definitions.