13

Ember data is still not at version 1.0 and thus I decided to use Ember without Data models.

I have my own models, and those are created by the route model function.

However maintaining state between the frontend objects and the backend objects is a nightmare. Especially when one route uses another routes models.

  • How can this be achieved, should I write my own store and model find method?
  • Should I use Ember Data (even though it's not at version 1.0 ?) perhaps an ETA on Ember Data 1.0 ?
  • write code to update the models on the frontend each time I change a model?
  • Another method?

Is what I'm doing best practices or should I be doing it differently? My gut feeling is that without using Ember Data I should write my own store. I'd love to get feedback from some of you guys.

Example of a model:

App.Person = Ember.Object.extend(App.Serializable,Em.Copyable,{
  user_email : null //used in routing dynamic segements and as old email when making changes to email
  ,first_name: null
  , last_name: null
  , fullname : function () {
    return this.first_name + ' ' + this.last_name;
  }.property('first_name','last_name').cacheable()
};

App.Person.reopenClass({
  createRecord: function(data) {
    return App.Person.create({
      user_email : data.email
      , first_name: data.first_name
      , last_name : data.last_name
}});

Example of how I load the class models:

App.UsersRoute = App.AuthenticatedRoute.extend( {
  model : function () {
    return new Ember.RSVP.Promise(function(resolve, reject) {
      $.getJSON('/users').then(function(usersData) {
        var userObjects = [];
          usersData.forEach(function (data) {
            userObjects.pushObject(App.Person.createRecord(data));
          });
        resolve( userObjects);
        },function(error) {
          reject(error);
      });
    })
  },

Subroutes use the model:

App.UsersAvailableRoute = App.AuthenticatedRoute.extend( {
     model : function () {
        return {
          allUsers :  Ember.ArrayController.create({
            content : this.modelFor('users').filter( function (user) {
                      return user.availablity === 100
                      }),

Example of how I update the model in a controller:

App.UsersAvailableController = Ember.ArrayController.extend({
needs : ['users']
    ,applyPersonAssign : function (person,need,project) {
          need.set('allocated',person);
          var updateObject = Ember.copy(project,true);
          if (Ember.isEmpty(need.get('inProject'))) {
            updateObject.projectNeeds.pushObject(need);
          }

          return $.ajax({
            url: '/projects/' + updateObject.get('codename'),
            "type": "PUT",
            "dataType": "json",
            data: updateObject.serialize()
          })
csano
  • 13,266
  • 2
  • 28
  • 45
Dory Zidon
  • 10,497
  • 2
  • 25
  • 39

3 Answers3

22

You don't necessarily need to recreate the Ember Data store. Ember works just fine with POJOs, you can also wrap your POJOs in an Ember Object to give you some fun built in features.

That being said creating a custom adapter which caches results could be convenient.

Here's an example where I create an adapter that supports caching. You can slowly build on the concept for all of the basic things you need.

App.FooAdapter = Ember.Object.extend({
  cache:{},
  find: function(id){
    var self = this,
        record;
    if(record = this.cache[id]){
      return Ember.RSVP.cast(record);
    }
    else
    {
      return new Ember.RSVP.Promise(function(resolve){
        resolve($.getJSON('http://www.foolandia.com/foooo/' + id));
      }).then(function(result){
        record = self.cache[id] = App.Foo.create(result);
        return record;
      });
    }
  }
});

In the examples below, I use the container to register the adapter on all of my routes/controllers so I had lazy easy access to it.

http://emberjs.jsbin.com/OxIDiVU/742/edit

If you always want it to be a promise:

http://emberjs.jsbin.com/OxIDiVU/740/edit

Reusability

The example above may make it look like you'd have to do a ton of work, but don't forget, Ember is super reusable, take advantage of the magic.

App.MyRestAdapter = Em.Object.extend({
  type: undefined,
  find: function(id){
    $.getJSON('http://www.foolandia.com/' + this.get('type') + '/' + id
  }
});

App.FooAdapter = App.MyRestAdapter.extend({
  type: 'foo' // this would create calls to: http://www.foolandia.com/foo/1
});

App.BarAdapter = App.MyRestAdapter.extend({
  type: 'bar' // this would create calls to: http://www.foolandia.com/bar/1
});

This is the basic idea of what Ember Data/Ember Model is. They've tried to create a ton of defaults and built in coolness, but sometimes it's overkill, especially if you are just consuming data and not doing CRUD.

Example: http://emberjs.jsbin.com/OxIDiVU/744/edit

Also you can read this (says the same stuff):

How do you create a custom adapter for ember.js?

Community
  • 1
  • 1
Kingpin2k
  • 47,277
  • 10
  • 78
  • 96
  • 3
    just to confirm that this works.... we use this approach quite extensively, with success having complex object graphs and all kind of crazy web services :) – melc Jun 25 '14 at 15:35
  • 2
    or you could also follow this guide http://eviltrout.com/2013/03/23/ember-without-data.html – fanta Jun 25 '14 at 17:47
  • @fanta thanks, I saw that article but it uses ES6 modules (which I didn't use and it uses ember cli which I do not use :).. – Dory Zidon Jun 26 '14 at 13:07
  • @kingpin2k how do you use this with a FindAll if you already had find for individual elements? do you iterate to make sure you do not load the same object twice? – Dory Zidon Jun 26 '14 at 13:08
  • @melc how do you use this with a FindAll if you already had find for individual elements? do you iterate to make sure you do not load the same object twice? – Dory Zidon Jun 26 '14 at 13:09
  • @DoryZidon yes i would do that, however i usually know if a view is going to need the whole bunch or not before going into the detail of a single element. So usually it goes the other way around, from all the objects (or a smaller set of them if paging used) to a single object. If the objects are heavy/complex then i create separate web service that will only populate the properties required for the specific view and request for the full object when chosen and navigated to the detailed view. – melc Jun 26 '14 at 18:03
  • @melc I think though that loading individual elements based on the view needs might not be the perfect approach, as what you should be doing in separating the view from the model, hence if you have complex models, load them up in chunks, but in general that models should make sense by themselves and load themselves at the right time when they are needed..My thoughts.. – Dory Zidon Jun 27 '14 at 14:24
  • @DoryZidon i agree as what i tried to describe is not coupling of view with the model in any way.When at the server side model there is a complex class,it is common practice to enable lazy loading on relationships with other classes.In order to do the same thing from the (web) services layer,it is required to have different services that do just that.So when it comes to `ember` the view that will need to display a grid of all your complex entities will actually ask for the partially initialised data from the server and gradually add more data to the selected one. – melc Jun 28 '14 at 14:36
  • Ditto, it's very common practice for the view to reach out to the controller/route and say do you have more records, if so please fetch them now. It's the entire concept of the never ending reddit, never ending discource etc. http://discuss.emberjs.com/ The view knows where the user is in the page (such as at the bottom of the list and can inform the controller/route to populate if available) – Kingpin2k Jun 28 '14 at 15:09
  • @melc and how would you go about updating records using such a pattern? for example I start making change to a model but wish to discard the changes? you'd reload that particular model from the server? – Dory Zidon Jun 29 '14 at 15:04
  • @kingpin2k and how would you go about updating records using such a pattern? for example I start making change to a model but wish to discard the changes? you'd reload that particular model from the server? – Dory Zidon Jun 29 '14 at 15:04
  • Pick your poison, create a copy, edit the copy, merge the copy and the original, or refetch the model. I'd probably copy it if I wanted to create rollback functionality, unless I needed data binding to cross between different views. – Kingpin2k Jun 29 '14 at 15:08
  • @DoryZidon what kingpin2k is describing is actually what we do to support commit/rollback functionality to objects and values of objects through a mixin. A copy is created and displayed for modification and when commit is called all validations (if any) are executed and if everything is ok then the values are written back to the original object/value. If rollback action is called then the values are reverted based on the values of the original object. – melc Jun 30 '14 at 07:21
  • @melc I agree, this was was I've been doing in my code thus far, I'm just trying to get a second opinion to make sure I'm picking the best path, and I believe I am :) – Dory Zidon Jun 30 '14 at 10:23
  • @kingpin2k 2 questions : 1) why do you warp the ajax in a promise, it returns a promise by default, wouldn't it be better just to return the ajax call (or does ajax here return a different kind of promise? 2) I'm doing various CRUD operations, would you suggest I not write my own but use data-objects? I'm worried about it not being 1.0 and writing my own allows me to customize various things (i.e. fetch with REST / do updates with sockets etc)..would love ot hear your thoughts. – Dory Zidon Jun 30 '14 at 10:25
  • 1
    I copied this from a different piece of code where I expected some of the functionality of the RSVP.Promise (finally or something, I don't remember). Really I'd go with the right tool for the right job, Ember Data makes easy things easy, and hard things really hard (in its current state). If you feel comfortable programming, using your own isn't that large of a leap, and can be easily customized for your need. This is especially true with the great XHR functionality already available with jquery (which is in your app anyway). – Kingpin2k Jun 30 '14 at 14:11
2

Where I work we are using Ember Data and Ember CLI despite them being rather unstable. So far Ember Data hasn't caused too much pain and suffering here. The store is pretty easy to understand, and the documentation on Ember for that facet of the framework is rather good. The one issue that I have been having has to do with dynamically sorting models, and as I modify the content of them, they reshuffle according to the changes I make, and somewhere along the road some really weird stuff happens, not sure if that's Ember Data's fault though.

In short, we've found some success using Ember Data, and can't complain about it if that's the route you wish to go.

Unome
  • 6,750
  • 7
  • 45
  • 87
2

If you're familiar with Ruby, Rails is a great solution for a backend.

The ember community has support for rails with the ember-rails gem which lets you use rails as a means to serve JSON.

Getting started

  • Add the gem to your application Gemfile:

    gem 'ember-rails' gem 'ember-source', '~> 1.9.0' # or the version you need

  • Run bundle install

  • Next, generate the application structure:

    rails generate ember:bootstrap

  • Restart your server (if it's running)

Building a new project from scratch

Rails supports the ability to build projects from a template source ruby file.

To build an Ember centric Rails project you can simply type the following into your command line:

rails new my_app -m http://emberjs.com/edge_template.rb

To install the latest builds of ember and ember-data. It should be noted that the examples in the getting started guide have been designed to use the released version of ember:

rails generate ember:install

Then all you need to do is render json in your controllers, like this

class ProjectsController < ApplicationController
    def index
        respond_to do |format|
            format.json { render json: Project.all }
        end
    end

    def show
        respond_to do |format|
            format.json { render json: Project.where(name: params[:name])}
        end
    end
end

make sure to update your serializers

class ProjectSerializer < ApplicationSerializer
  attributes :id, :name, :description, :imgUrl, :deployUrl
end 

setup your routes

EmberRailsBlog.ProjectsRoute = Ember.Route.extend({
    model: function(){
        return this.store.find('project');
    }
});

and finally your model

var attr = DS.attr;

EmberRailsBlog.Project = DS.Model.extend({
    name: attr(),
    description: attr(),
    imgUrl: attr(),
    deployUrl: attr()
});
Yeysides
  • 1,262
  • 1
  • 17
  • 27