Simple ember application with JSON model


#1

Hi. I am building a simple application with Ember-Cli. I have a list of projects, I am displaying a list of projects on the index route, and on click I want to display the details of each project.

I ended up using mirage, and it is returning the list of projects fine, but I am having problems returning an specific project.

this is how my mirage confi,js looks like:

 _this.namespace = '/api';
this.get('/projects', function() {
    return {
      data: [{ id:'p1', type:"project", attributes: {image:"project1.jpg", description:"project 1 description"}},
        { id:"p2",type:"project", attributes: {image: "project2.jpg", description: "project 2 description"}}
    };
  });
   this.get('projects/:id', function(){
      //what should i return here?
   });_

My question is more about Ember architecture. Seems like Mirage should be used just for mocking, not sure if using it as a final solution is fine. I don’t plan to connect the app to any database, as the data is simple enough.

  1. If my site model data is read only, what will be the best way to set it up so the data will be loaded from a read only json (on an external file, or hardcoded)? Is mirage the best option or is therea quicker way to do it?

  2. If mirage is the solution, how can I make a central repository (json) that I can use to define dynamic paths?

Thanks!


#2

hey @david9922, using mirage in a deployed app would work fine if it fits your needs. We’ve actually considered using it as a demo version of our app so customers could play around with it. If you wanted to do this you’ll need to set up a few things. Mirage is enabled by default in the development environment, but if you’re going to deploy it with the production builds you’d want to enable mirage in your production environment too.

// config/environment.js
...
if (environment === 'production') {
  // enable mirage
  ENV['ember-cli-mirage'].enabled = true;
  ..
}
...

Essentially what you’re doing is loading fixture data. Mirage is still a great choice for this, for a number of reasons, but depending on how complex your data needs, it may be overkill. The real power of mirage comes from easily generating or mocking lots of data, and being able to query it in various ways just like a backend server.

If you want to set up Mirage the quick and dirty way to accomplish what you want I’d try something like this:

1. Set up a mirage model for ‘project’ (note that currently this is different from your ember data model). I don’t even think you need to fill out any attributes in your mirage model:

    // mirage/models/project.js
    import { Model } from 'ember-cli-mirage';
    export default Model.extend({
    });

2. Edit your scenario to create the fixtures. Your mirage scenario is, more or less, where you put your mirage database setup code when NOT in testing mode (aka running in development environment). In your mirage/scenarios/default.js you can create some fixture data like this:

    // mirage/scenarios/default.js
    export default function( server ) {
      // create some projects
      var project1 = server.create('project', { id:'p1', image:"project1.jpg", description:"project 1 description"});
      var project2 = server.create('project', { id:'p2', image:"project2.jpg", description:"project 2 description"});
    }

3. If your data looks like what you already posted, you may want to make a mirage factory for the ‘project’ model:

    // mirage/factories/project.js
import Mirage, {faker}  from 'ember-cli-mirage';

export default Mirage.Factory.extend({
  // mirage automatically passed the index of the model in, so you can use it to generate ids and such
  id(i){return 'p'+i;},
  image(i){return `project${i}.jpg`;},
  description(i){return "project " + i + " description";},

  // you can define static strings as well
  // extra: "static string",

  // you can also generate fake data like this:
  // firstName: faker.name.firstName,       // using faker
  // lastName: faker.name.firstName,
  // zipCode: faker.address.zipCode
});

and then in your scenario you can do this instead:

    // mirage/scenarios/default.js
    export default function( server ) {
      // create the same two projects as the example above, but with one line, and you can make any amount by changing the number
      var projects = server.createList('project', 2);
      // you can also specify some data here, and the rest in the factory:
      var projects = server.createList('project', 4, {category: "technical"}); // all four of these will have category attribute with "technical" value
    }

4. Finally, in your mirage config, you can just use the shortcut definitions instead of having to do the longform ones like you were:

  // mirage/config.js
  ...
  this.get('/projects');     // returns all projects in the mirage database
  this.get('/projects/:id'); // returns a specific project with the given id
  ...

Anyway, hope that helps. Let me know if you have any more mirage related questions and I may just be able to answer them.


#3

Hi @dknutsen. Thanks a lot for the detailed response. I was also thinking Mirage might be too much for what I want. Basically, I will display a list of projects on the index, each one with a {{#link-to “project” project.name}}

Project 1 Project 2 Project 3

Each project will have a main image, maybe a series of additional images and a description. They don’t follow naming conventions (like project[x].jpg) it was just to give an example of the model. Here’s how the project detail page will look like:

Project Title [Main image] description of project one [image1] [image 2] [image x]

I will keep adding projects, so the ideal would be to mantain an external json file, instead of creating the fixtures as in your example

var project1 = server.create('project', { id:'p1', image:"project1.jpg", description:"project 1 description"});

so it will be something like this

 [{ id:'c1', type:"project", attributes: {image:"test.jpg", description:"lorem ipsum"}},
        { id:"w1",type:"project", attributes: {image: "x.jpg", description: "lorem"}},
        { id:"cr1",type:"project",attributes: {image: "y.jpg", description: "lorem"}},
        { id:"i85",type:"project",attributes: {image: "m.jpg", description: "lorem", extraimages: ["one,jpg", "two.jpg"]}} 

So what would be a good alternative to mirage?

I tried hardcoding the model on the index route, but then the project/:id route will not have access to it.

 model() {
    var jsonmodel = ["//json structure with model//"]
    return jsonmodel; 
  }

So how will I be able to say on the project route to ember to return an specific record?

Thanks in advance!


#4

You could still use Mirage which may be the easiest way honestly. I’ve never wired up a JSON file fixture before because it’s a pain. What I would do, if you really want to keep the JSON data in a totally separate pure-json file, is to create that file somewhere in your project and then reference it in the mirage scenario file, something like this:

// mirage/scenarios/default.js

// load your json file here (in this case it's loading a file called jsondb.js from <project-root>
import jsonDB from '../../jsondb.js';

export default function( server ) {
  // create some projects
  jsonDB.projects.forEach(project => {
    server.create('project', project);
  });
}

And then your jsondb file could look like this:

export default {
  projects: [
    { id:'c1', image:"test.jpg", description:"lorem ipsum"},
    { id:"w1", image: "x.jpg", description: "lorem"},
    { id:"cr1", image: "y.jpg", description: "lorem"},
    { id:"i85", image: "m.jpg", description: "lorem", extraimages: ["one,jpg", "two.jpg"]}
  ]
};

Then create the project model and mirage config as I described previously.


#5

If instead you wanted to load the JSON from somewhere other than your project, like say an S3 bucket or static file server somewhere, you could put an AJAX request in the model hook of your application route so when the application loads it fetches the latest JSON file from said remote location and push it into the store or something. Just tossing out some ideas…


#6

Can I hardcode the JSON fixture on mirage instead of loading it from an external file?

export default function( server ) {
var jsonDB = [{id, description,..},{}] 

 // create some projects
  jsonDB.projects.forEach(project => {
    server.create('project', project);
  });
}

#7

Oh yeah that should totally work. Just make sure you’re not referencing jsonDB.projects.forEach like I did if you’re just making jsonDB a straight array like above. EDIT: All I mean is that you’d want this:

export default function( server ) {
var jsonDB = [{id, description,..},{}] 

 // create some projects
  jsonDB.forEach(project => {
    server.create('project', project);
  });
}

Instead of this:

export default function( server ) {
var jsonDB = [{id, description,..},{}] 

 // create some projects
  jsonDB.projects.forEach(project => {
    server.create('project', project);
  });
}

EDIT 2:

This is also a dumb nitpick but you I think could also declare the jsonDB outside the function like this:

var jsonDB = [{id, description,..},{}] 

export default function( server ) {
  // create some projects
  jsonDB.forEach(project => {
    server.create('project', project);
  });
}

#8

I’ll give it a try to this mixed with your initial mirage suggestion. Thanks a lot for your help! :clap:


#9

Anytime. Hope you can come up with a good solution that works well for you!


#10

Here’s my working solution in case someone needs something similar. Thanks @dknutsen for your help:

  1. Create a mirage model for the project item

     import { Model } from 'ember-cli-mirage';
     export default Model.extend({
     });
    
  2. Create the database

     // file mirage/scenarios/default.js
     // define json object with project items 
     export default function(server) {
     var jsonDB =  
     {data: [
     	{ id:'pr1', description : "Project Description one" ,image:"/assets/images/myproject.jpg"},
             { id:"wh1", description: "What are those?", image: "/assets/images/projectwht.jpg"},
             { id:"sr1", description: "What are those?", image: "/assets/images/projectsr.jpg"}
             ]
         };
       jsonDB.data.forEach(project => {
         server.create('project', project);
       });
     }
    
  3. Set mirage routes

     export default function() {
       this.namespace = '/api';
       this.get('/projects');     // returns all projects in the mirage database
       this.get('/projects/:id'); //dynamic route for individual projects
     }
    
  4. Create routes

     // File routes/index.js
     import Ember from 'ember';
     export default Ember.Route.extend({
     	 model() {
         	return this.get("store").findAll('project'); 
       }
     });
    
     // File routes/project.js
     import Ember from 'ember';
     export default Ember.Route.extend({
     	 model(params) {
         	return this.store.findRecord('project', params.project_id); 
      }
     });
    
  5. Create a model for project

     import DS from 'ember-data';
     //file models/project.js
         export default DS.Model.extend({
           image: DS.attr(),
           description: DS.attr()
         });
    
  6. Display items on template with link to dynamic route and display item detail.

     //file templates/index.hbs
     <div class="home">
     	<ul>
     		 {{#each model as |project|}}
         		<li>{{#link-to "project" project.id}}{{project.description}}{{/link-to}}</li>
       		{{/each}}
     	</ul>
     </div>
    
     //file templates/project.hbs
     <div>{{model.description}}</div>
     <div>
     	<img src="{{model.image}}">
     </div>
    

Still have a few questions, but it works with this setup.


#11

Awesome! Sharing your complete solution can be really helpful for other people. If you have any more questions feel free to ask.


#12

We still use the old https://github.com/emberjs/ember-data-fixture-adapter for something similar. It works with the newest ember-data. See for example https://www.martinic.com/


#13

I figured there was something like that out there but didn’t know the name. Nice.