Should Backbone Model represent a view or a server-side resource?

96 Views Asked by At

Assume a situation where I've a Rails AR models as below

class User
  has_one :profile
end

class Profile
  belongs_to user
  has_one :address
end

class Address
  belongs_to :profile
end

And have a User Profile view to be constructed at the client-side. Now, how should my Backbone model look like? Should it replicate the way it is in Rails/server-side domain model? Do we have a clear reason for the way it has to be that way, or is it just subjective?

Your experience sharing is appreciated!

2

There are 2 best solutions below

1
On BEST ANSWER

Usually your backbone models and collections should follow your REST API ( or any other client-side <-> server-side communication ).

  1. For example if these ruby models are passed to the frontend with :

    GET /api/user/:id
    

    And what you got as a response is

    [{ profile: { address: "21st str." } },{ profile: { address: "17th str." } }]
    

    You would need one model

    User = Backbone.Model
    Users = Backbone.Collection.extend({
       model: User,
       url: "/api/user"
    });
    
  2. But if you do something more complicated in your API and have more urls in your API you could choose the structure that best fits your interaction with the client on your frontend. For example if your website doesn't need a user api at all and you pass data to the frontend with those urls:

    GET /api/profile
    

    You can have only one model

    ProfileModel = Backbone.Model.extend({
        url: "/api/profile"
    })
    

    And you can easily set the address like

    profile = new ProfileModel();
    profile.set('address', '21st str.');
    

Bottom line

Backbone usually should follow your URL structure of your REST API. If you do this you will enjoy the full benefits of using it's REST Ajax calls ( GET, POST, DELETE, PUT ) properly autoconfigured.

Usually what I don't do is to make my Backbone app structure to follow the database schema. That may cause a headache, because you will need to create a way your backend ( Ruby app ) to be able to provide almost the same database access as the ORM that you are using.

1
On

To keep things simple, I believe the model should represent both the server-side model and the client-side view state, distinguishing the view state attributes by a preceding _. The view state attributes are ignored by the server when saving the model.

Here's an simplified example of the workflow I use:

var animal = new View({
  initialize: function(){
    // define the model and default view state when view is initialized
    this.model = new Model({id:3, _edit:false}, {url:'/animals'));
  }
  , render: function(){
    var self = this;
    this.undelegateEvents();
    this.delegateEvents({
      'click [data-trgger]': function(e){
        self[$(e.currentTarget).attr('data-trigger')].apply(this);
      }
    });
    var success = function(){
      // pass the model to the template engine
      self.$el.html( _.template('#edit-animals', {model: self.model}) );
    }
    // fetch the model from the server when view is rendered
    // you could check if the model is already fetched
    this.model.fetch({success:success});
  }
  , onSave: function(){
    // save the model then:
    this.model.set('_edit', false);
    this.render();
  }
  , onEdit: function(){
    this.model.set('_edit', true);
    this.render();
  }
});

And the template:

<% if(model.get('_edit')){ %>

  <!-- the view is in 'edit' state, show input and save button -->
  <div>
    <input type="text" name="breed" class="form-control">
  </div>
  <button class="btn btn-primary" data-trigger="onSave">Save</button>

<% }else{ %>

  <!-- the view is in 'read-only' state, show values and edit button -->
  <button class="btn btn-default" data-trigger="onEdit">Edit</button>
  <div>
    <%= model.get('breed') %>
  </div>

<% } %>