I will do my best to answer this question in the spirit of StackOverflow, pointing out some important technical differences between Ember and Rails. I will leave a more philosophical side for someone else at programers.stackexchange.com .
You can find all the code examples below in working jsFiddle if this helps you visualize how everything fits together.
Separate routes for collections and objects
One of the main differences between Ember and Rails is the relationship between collection routes (which control a list of objects) and element routes (which control a single object). In Rails, they are handled by a single resource controller. In Ember, they are usually handled by two separate routes, since they manage two different data structures:
App.Router.map(function () { this.route("posts", { path: "posts" }); this.route("post", { path: "post/:post_id" }); }); App.PostsRoute = Ember.Route.extend({ model: function (params) { return App.Post.find(); } }); App.PostRoute = Ember.Route.extend({ model: function (params) { return App.Post.find(params.post_id); } });
Routes versus controllers versus views versus patterns
In Rails, your code is split between three main class groups:
- Models: object-oriented abstraction on a database row.
- Views: Templates that can be displayed by the controller.
- Controllers: receive HTTP requests, load and manage models, visualize views.
At Amber, the distribution of responsibilities is significantly different.
Models. Ember models work just like Rails models.
App.Post = DS.Model.extend({ title: DS.attr("string"), body: DS.attr("string"), comments: DS.hasMany("App.Comment") });
Routes Routes represent user-visible locations in your application, and they correspond to URLs such as /post/7 or /about . As you can see in the above code examples, routes do much more in Ember. Most importantly, they are looking for models matching this URL. They are also responsible for connecting the corresponding controllers and views, as you will see in a second.
Controllers Controllers are not like Rails! The two most important things you need to understand about Ember controllers are as follows: (1) they are mostly smart proxies around model objects and (2) they are usually single point. Thus, you will have only one PostController that will be connected to any page that you are looking at right now.
Generally speaking, you use Ember controllers to manage a transition state that does not belong to a URL or database. Here is an example:
App.PostController = Ember.ObjectController.extend({
Because Ember controllers are proxies around models, they also tend to accumulate logic that almost belongs to the model, but it is too closely related to a specific screen in your application.
Views and templates. Smart presentations and templates work together. Itβs best to think of them as GUI widgets.
App.PostView = Ember.View.extend({ // This can be omitted when we're created by a route. templateName: 'post' // Any HTML event handlers would go here if we needed them. Our job is to // map between HTML events and events understood by the controller. //doubleClick: function (evt) { // // We'll actually bind this to a specific button, not a click event. // this.get("controller").send("showLowRatedComments"); //} });
Our post template freely mixes the fields defined by the model and the fields defined by the controller:
<script type="text/x-handlebars" data-template-name="post"> <h2>{{title}}</h2> <div class="body">{{body}}</div> {{#if lowRatedCommentsShown}} <button {{action 'hideLowRatedComments'}}>Hide Low-Rated Comments</button> {{else}} <button {{action 'showLowRatedComments'}}>Show Low-Rated Comments</button> {{/if}} {{partial "comments"}} </script>
Please note that as the fields on our model or controller change, the view will automatically overwrite only those parts of HTML that need to be updated!
Asynchronous behavior, computed properties, and bindings
Since Ember.js runs in a browser, many operations are asynchronous. Most of the fundamental design of Ember is based on making asynchronous updates pleasant and easy. One of the key consequences of this situation is that objects are loaded asynchronously. When you call find , you will return an unloaded object:
post = App.Post.find(params.post_id) post.get("isLoaded"); // -> false post.get("title"); // -> not yet available
When the server sends you the data, you will see:
post.get("isLoaded"); // -> true post.get("title"); // -> "Post #1"
To make this painless, Ember relies heavily on calculated properties , observers and bindings . In each of these cases, the key idea is that changes in the data should automatically pulse through the system. For example, we can use the computed property to ensure that isLowRated updated whenever the rating comment changes:
App.Comment = DS.Model.extend({ post: DS.belongsTo("App.Post"), body: DS.attr("string"), rating: DS.attr("number"), isLowRated: function () { return this.get("rating") < 2; }.property("rating")
Please note that Ember Handlebars templates are deeply integrated with this system. When you write {{title}} in the template, you set a binding that will automatically update the DOM when the title changes. In the case of edit fields, this binding works in both directions! Changes to the displayed value will be returned back to the model (although transactions can be used to return it).
It is also worth remembering that many dynamic updates, in particular bindings, are performed asynchronously at the end of the current startup cycle. This is why you often see Ember.run calls in test Ember.run :
Ember.run(function () {
In practice, Ember feels much more asynchronous than Rails, but less asynchronous than a system with an event like Node.js. This is because most asynchronous updates are automatically controlled by bindings.
Surviving Ember Data
This is the only place where I am going to deviate from strictly technical details and mention some practical tips. Ember Data provides DS.Model as shown above. This is not the only model layer for Ember.js-check ember-rest , ember-resource and similar libraries for alternatives. There is currently no official release of Ember Data, but it can be used very carefully in production applications if you like living on the brink of bleeding. Some tips:
- A few key weaknesses include validation, lazy loading, and several open transactions. Before moving on to Ember Data, write some small test programs to make sure that they really can do what you need.
- Do not select fights with RESTAdapter. Insert it exactly the JSON that it wants , even if it means creating a proxy. In particular, this currently means serializing a complete list of identifiers for each
hasMany relationship in Rails when serializing an object. See active_model_serializers if you use Rails. - Do not go in cycles in a certain design. Instead, be prepared to sometimes circumvent restrictions and compromise.
You can get very good results with Ember Data. But it is significantly less mature than ActiveModel, and should be considered as such.