Skip to content

Instantly share code, notes, and snippets.

@polotek
Last active March 30, 2017 05:37
Show Gist options
  • Save polotek/928a1ae8bbf401a11b8b to your computer and use it in GitHub Desktop.
Save polotek/928a1ae8bbf401a11b8b to your computer and use it in GitHub Desktop.
Hurdles getting started with Ember.js

This is a brain dump of my experience trying to get something going with Ember.js. My goal was to get to know the ins and outs of the framework by completing a pretty well defined task that I had lots of domain knowledge about. In this case reproducing a simple Yammer feed. As of this time, I have not been able to complete that task. So this is a subjective rundown of the things I think make it difficult to get a handle on Ember. NOTE: My comments are addressing the Ember team and giving suggestions on what they could do to improve the situation.

App setup

The new guides have pretty good explanation of the various parts of the framework; routers, models, templates, views. But it's not clear how they all get strapped together to make something that works. There are snippets of examples all over the place like:

App.Router.map(function() {
  match('/home').to('home');
});

But they're out of context. What is App.Router? Is it an instance of a Ember.Router or a subclass? In trying to set up my App, I got tripped up for hours on where it was appropriate to do extend() or create(). I thought I had an understanding of the difference between these (though I don't think you can count on that from all your users). But I found the behavior of these in Ember thoroughly confusing. I'll come back to that later. Suffice it to say it wasn't clear to me which one the framework required at any given time.

So App needs to be an instance, Ember.Application.create(). But then you need to set properties on that instance directly, App.ApplicationController = Ember.ObjectController.extend();. And that needs to use extend(). And you can't pass in an object to set the properties of the App instance.

// Doesn't work, even though it's in some of the docs
var App = Ember.Application.create({
  ApplicationController: Ember.ObjectController.extend()
});

I lost a lot of time trying to figure that out.

You need to talk about how to set up an Ember Application and what is actually happening. Here are some rough things I pieced together. You can see how I did based on how wrong and incomplete they are.

  • You must create an instance of an application.
    • It functions as a namespace, so if you don't title case it (app vs. App), you'll get a weird warning. It won't stop you from continuing, but the warning is disconcerting and unhelpful.
  • You must create Classes and assign to properties on the App instance
    • You need to use extend() on pretty much all of these.
    • I believe these will be "registered" with the App automatically. If they are not placed on the App object, things don't work. It's not clear if you can manually register Classes with the App.
    • Near as I can tell, Ember will look these up by naming convention and create instances of them at "the right time". It's not clear when that is or if the instances get reused.
  • The App will initialize and auto-start after an asynchronous turn. Some docs have an explicit call to App.initialize(), that doesn't seem to be necessary anymore. Definitely unexpected. Not sure if auto-start can be disabled.
  • You MUST create App.ApplicationController, App.ApplicationView, and App.Router. If you don't, you get more unhelpful errors.

Even with all of these things, I'm not sure how to get an Application that does nothing and doesn't error. It's difficult to start from a working no-op App, and then add functionality incrementally so you can learn.

Creating classes and instances

The Object Model section of the guide talks about Class and Instances. Classes are created using extend() and instances are created using create(). This seems fine, but once you get these objects, the javascript rules we've come to expect start breaking down.

var O = Ember.Object.extend({
  classProp: 'classProp'
  , classMethod: function classMethod() {}
});

// good
typeof O // "function"
O.prototype.constructor === O // true

// WAT
O.toString() // "(unknown mixin)".
             // Also displays this in the chrome console instead of
             // letting you inspect the Object or printing the
             // function body.
O.prototype.classProp // undefined
O.prototype.classMethod // undefined

var o = O.create({
  prop: "prop"
  , method: function method() {}
})

// this seems to behave normally
o instanceof O // true
o.prop // "prop"
typeof o.method // "function"
o.classProp // "classProp"
o.__proto__.classProp // "classProp"

// Except when you evaluate `o` in the Chrome console,
// it prints an object named "Class"

// WAT. now the constructor somehow behaves too
O.prototype.classProp // "classProp"

I can understand doing some trickery with your type system if it adds value. But you don't explain what that value is. And this weirdness is another thing that makes it hard to figure out what's going on by inspecting objects.

Router API

I have no idea how the router is supposed to work. Early on I was using the old router api and I understand you guys aren't proud of that one. But I didn't find any good tutorials on the api. Even others who are publishing working examples say in their docs that they don't really understand the magic encantation for the router. They just put it in and things worked.

It seems intuitive that connectOutlets allows you to fill the places that say {outlet} in your template. But the api for this method is thoroughly confusing. I kept finding slightly different examples around the web. Probably due to evolving apis. But some questions that still stick out:

  • What is the object context in this method? What does this refer to?
  • The first argument seems to be router. Is that an instance of router or the same as App.Router? Is it the same instance for the whole app?
  • Are there other arguments? If so, when are they passed and what's their significance?
  • router.get('applicationController'). How exactly do I know I can do this? What's the convention for what you can get out of the router?
  • applicationController.connectOutlet. Again, what's the method signature here? It seems to take the name of a template (or is it the name of a View?). The other argument seems to be a model or a controller? Still confused about what's happening there.

After a while of things kind of working, but being frustrated, I was fine discarding that for the new API. So I'm currently running master so I can use the new Router. I've read and watched videos about it. But it just spits errors for me. And there doesn't seem to be enough documentation on it yet to figure out what's going on. Would love to drop back to the old router which at least let me continue without really understanding. But that doesn't seem to be an option. I'm all about progress, but it feels like the api is currently in a gap of uncertainty between old Router and new.

Ember data

I've been intrigued by ember data since first learning about it. It's an attempt to create an abstract data layer that can represent models and relationships on the client. Essentially allowing to create a sensible data domain for client-side apps. We have a custom solution for this at Yammer, and it was really interesting drawing parallels. That said, I had a really hard time doing that and never actually got things working.

My exploratory project was a little app to display a simplified Yammer feed using test yammer data. We have custom api urls that don't follow rails conventions, and a custom data payload format. I was interested in attempting a really simple custom adapter/serializer combo that could load our data. I read lots about adapters and serializers and looked at your youtube videos explaining them. I even dove into the code and read the extensive comments there. But when it comes down to it, the architecture is just too opaque. There are so many open questions to answer in order to go from a call like app.Feed.find(), to an ajax call with the correct url and data, to a valid Model that will load data successfully. Here are a few:

When filling in YamAdapter#find, what's the significance of this stuff that I pulled from the built-in adapters?

Ember.run(this, function(){
  this.didFindRecord(store, type, json);
});

I know about the Ember run loop, and I'm assuming didFindRecord passes things off to the serializer among other things. But there's a lot to put together here.

The adapter methods don't return anything. I vaguely understand that they get called via the Store. But when you consider that the Model methods return those promise-like records, it's very unclear how these adapter methods connect back to those.

The adapter uses methods like rootForType() and buildURL() to figure out what ajax calls to make. I found that sometimes I didn't have enough context to build the url I needed. The only data that gets passed in for find() is a type and an id. If you need to put more parameters onto the url, the adapter has to get those from somewhere. I ended up adding a 4th data argument, but that means I'm firmly outside of the realm of compatibility.

Once I made it into the serializer, things got even more hairy. I read a lot about extracting vs. materialization. But I couldn't figure out the right combination of things to create my models and wire up the relationships properly. It looks like the core of extraction is the method loadValue which creates a Model instance in the store. I got that far, but then I don't really know how to hook up relationshiops. I see things like "prematerialized" and I'm not sure how that factors in. My promise-models never loaded and I got bogged down in the weeds.

I tried to take a step back. Because of my background with the Yammer paradigm, I understand what's supposed to be happening at a high level. I want to end up with a set of models created from the data payload and have their relationships hooked up. Nevermind the events that need to be fired for now. I tried to figure out how to just process the payload manually because I know exactly what's in it. But I don't know the right methods to do so. I'm thinking of something like the following:

var messages = json.messages.map(function(msg) {
  var message = App.Message.createRecord(msg)
    , thread
    , user;
  
  // look up references
  _.each(json.references, function(ref) {
    switch(ref.type) {
    case 'thread':
      if(ref.id == message.thread_id) {
        thread = ref;
      } else { return; }
      break;
    case 'user':
      if(ref.id == message.sender_id) {
        user = ref;
      } else { return; }
      break;
    }
  });
  
  if(thread) {
    // set the thread relationship on the message
    thread = App.Thread.createRecord(thread);
    message.setRelationship('thread', thread);
    console.log(message.get('thread'));
  }

  if(user) {
    // set the user relationship on the message
    user = App.Thread.createRecord(user);
    message.setRelationship('sender', user);
    console.log(message.get('sender'));
  }
});

All this is just to do a simple find(). There were a few other things I wanted to get going. I still don't exactly get how sinceQuery or extractMeta fit in. I think I get it conceptually, but I don't think it's flexible enough to fit my needs. There's a gap between looking at the RESTadapter/RESTSerializer, which are geared towards rails conventions, and looking at the base adapter/serializer which are pretty low level and don't give you much help in filling in gaps.

I still think ember data is an impressive undertaking. In it's current state, it's gonna be pretty difficult to take advantage of if you have non-trivial api requirements.

Tests

Where are the tests? I know they exists. There's a tests folder, but there aren't any readable tests there. Things are obfuscated, probably for the test harness. But this is a mistake. When trying to learn a system, tests are a great resource to see examples of usage and get a better understanding of components. But that avenue isn't available here. It also hampers contributions from folks who want to help improve things.

Also I can't run the tests without phantomjs? That's no good. Ember is for the browser first and foremost right? IMO, tests for browser javascript should always be available by just hitting a url. Chrome dev tools is the preferred tool for debugging. You can add options for running in a headless browser or node. But browser should be the default.

I know there are no good standards here yet. But I think you get a lot of mileage out of these suggestions.

Wrapup

My foray into Ember land was extremely enlightening. Make no mistake, I could write a document this size about the stuff I like as well. But the barriers to entry are really significant unless you have a rails background. It's okay to lean on familiarity with rails if that's the audience you want. But if you want to capture more of the larger js community, you've gotta pull back the covers a bit more. Or at least you need to give us a thorough introduction to Convention over Configuration in the context of Ember, because not all of us are going to learn rails just to learn Ember.

I hope this has been helpful, and I'd be happy to talk to you in more depth about any of it. I should be clear that this exercise was for my own education, and I was not evaluating Ember for use at Yammer. Yammer's just a great example for getting a non-trivial overview of what frameworks can do.

@polotek
Copy link
Author

polotek commented Jan 13, 2013

I had a great conversation with @tomdale about these issues. The Ember team is really interested in improving the story here. Lemme update a few items that are already addressed in a soon to be released build.

  • When setting up an Ember.Application, all of the default components are now created for you. Namely App.Router, App.ApplicationController and App.ApplicationView. You can just use these.
  • I think the point about the app auto-starting is relevant. But it's fair to note that Ember is designed this way. It can be initialized before everything is "ready" because it's meant to be lazy and respond to changes in application state.

It's much easier to get a "no-op" app now, and there's less setup boilerplate that contributes to a lot of the confusion I mentioned in the App setup section.

@polotek
Copy link
Author

polotek commented Jan 13, 2013

Also, after getting a partial introduction to the architecture of ember data from Tom, I was able to get much further with implementing a custom adapter/serializer system. The system still isn't very approachable, even with docs, and my solution isn't complete to my satisfaction yet. But ember data is definitely an interesting system that has lots of upside when it's working as expected.

I'll probably publish what I end up with.

@bsodmike
Copy link

I've done quite a bit with Backbone.js and wanted to give Ember ago, but never really got around to it due to some bad experiences with SproutCore.

Pretty fantastic write up and I look forward to an alternative take once you're ready, how to approach building an Ember app the 'right' way, without taking the try/fail approach you've done here. I think that would be quite beneficial to those looking to get to grips with Ember quickly, although that's not to take anything away from this fantastic gist :)

@tomdale
Copy link

tomdale commented Jan 13, 2013

Hey Marco,

Thanks for the feedback. This kind of stuff is literally the most valuable thing you can provide to library authors; if you're struggling with an open source project, things like this are a fantastic way to help make it easier to use. So, thank you.

The first thing I'd like to say is that I think a lot of the problems you hit were due to coming into the framework at a bit of a transition period. We were in the midst of making many changes that addressed most of the on-ramp problems that you had. Unfortunately, we needed to do a better job of communicating that on the website. If you had started with a copy of Ember built from master, I think a lot of your frustration would have been eliminated. We'll be doing a pre3 release shortly that incorporates the fixes to many of the problems that you hit.

But they're out of context. What is App.Router? Is it an instance of a Ember.Router or a subclass? In trying to set up my App, I got tripped up for hours on where it was appropriate to do extend() or create(). I thought I had an understanding of the difference between these (though I don't think you can count on that from all your users). But I found the behavior of these in Ember thoroughly confusing. I'll come back to that later. Suffice it to say it wasn't clear to me which one the framework required at any given time.

The TL;DR here is that App is a single instance, so you create() it. When in doubt, everything else should be a class. Views, controllers, routes, etc. should all be extend()ed. Ember.js is responsible for creating the instances for you and wiring them up, which makes it much easier to test pieces of your application in isolation. (Some people refer to this as dependency injection, but we find that that has too much Java flavor. :P)

I believe these will be "registered" with the App automatically. If they are not placed on the App object, things don't work. It's not clear if you can manually register Classes with the App.

You do need to set them as properties of the Ember.Application instance, yep. We are working on a coherent story for people that want to use modules. I am a fan of using modules to load dependencies, but treating each file in your application as a separate module introduces more complexity than it's worth, IMO. Still, we know people want to do this and we're working on it.

The current system uses a "container" under the hood that can be hooked into by any kind of loader. We even wrote it as a microlibrary: https://github.com/emberjs/ember.js/blob/master/packages/container/lib/main.js

Inside the application, we lookup classes and instances via this container API (https://github.com/emberjs/ember.js/blob/master/packages/ember-application/lib/system/application.js#L524). Currently this just roundtrips back to the Application instance, but in the future, you can imagine this plugging into an AMD loader. Like I said, this is still a work in progress, but the pieces are there, and people like Ryan Florence and Tim Branyen have been extremely helpful with assisting us in thinking through this problem.

The App will initialize and auto-start after an asynchronous turn. Some docs have an explicit call to App.initialize(), that doesn't seem to be necessary anymore. Definitely unexpected. Not sure if auto-start can be disabled.

I'm not sure what your objection to auto-starting the application is, but it's definitely the behavior that most people want. If you want to defer the application starting, just call App.deferReadiness(). When you want to release your "lock" on the application starting, call App.advanceReadiness().

You MUST create App.ApplicationController, App.ApplicationView, and App.Router. If you don't, you get more unhelpful errors.

Again, this is fixed on master, and will be fixed in the forthcoming pre3 release.

I can understand doing some trickery with your type system if it adds value. But you don't explain what that value is. And this weirdness is another thing that makes it hard to figure out what's going on by inspecting objects.

We document all of the value of the Ember object model at http://emberjs.com/guides/object-model/classes-and-instances/. Classes, instances, mixins, observers, computed properties, bindings, etc. I'm not sure what we could do to explain the value other than to make the documentation more "sales-y." Perhaps we need to do that for people who are primarily JavaScript developers, but people coming from other dynamic languages see the value immediately.

You're expecting to be able to use introspection tools from JavaScript with Ember.js objects, which obviously won't work. Ember objects are composed using those JavaScript primitives. We would be handicapping ourselves pretty severely if all we could to improve the development experience was do things that maintained JavaScript semantics precisely.

Ironically, the main introspection tool that you attempted to use (inspecting the prototype) does work, but for performance reasons, we wait until the first instance of a class is created to flatten the prototype. If you had done this after creating an instance, it would have worked as expected.

In terms of debugging, we provide lots of introspection tools into the object model. We should document those better, and we're always open to suggestions for improvement.

After a while of things kind of working, but being frustrated, I was fine discarding that for the new API. So I'm currently running master so I can use the new Router. I've read and watched videos about it. But it just spits errors for me. And there doesn't seem to be enough documentation on it yet to figure out what's going on. Would love to drop back to the old router which at least let me continue without really understanding. But that doesn't seem to be an option. I'm all about progress, but it feels like the api is currently in a gap of uncertainty between old Router and new.

The old router is dead. Conceptually there are similarities between the old router and new, but the API is completely different. I'm surprised you had trouble with the new router. The documentation on the website's routing section is up-to-date, and people have been reporting tremendous success compared to the old router. I'd be interested in knowing which errors you hit. We already fixed many of the exceptions that people were hitting, so we should figure out if you were hitting a different error, or if this has been fixed already.

My exploratory project was a little app to display a simplified Yammer feed using test yammer data. We have custom api urls that don't follow rails conventions, and a custom data payload format. I was interested in attempting a really simple custom adapter/serializer combo that could load our data. I read lots about adapters and serializers and looked at your youtube videos explaining them. I even dove into the code and read the extensive comments there. But when it comes down to it, the architecture is just too opaque. There are so many open questions to answer in order to go from a call like app.Feed.find(), to an ajax call with the correct url and data, to a valid Model that will load data successfully.

The more conventional your API, the better. If you have an extremely non-standard API, it may be easier to just write imperative code, rather than trying to override the hooks built-in to the default adapter. But, we did design the adapter API to be layered, so if it's easier to write imperative code, just override the top-level hooks (serialize, extract, materialize) and do what you need to do. Admittedly, we need to document this better, and explain precisely what is going on under the hood so that adapter authors have the right mental model. I hope that my explanation via IM the other night helped at least nudge you in the right direction.

Ultimately, we expect the majority of Ember.js developers to not be adapter authors. Most people will pick a backend, stick to those conventions, and find an adapter that works with those conventions.

Where are the tests? I know they exists. There's a tests folder, but there aren't any readable tests there. Things are obfuscated, probably for the test harness. But this is a mistake. When trying to learn a system, tests are a great resource to see examples of usage and get a better understanding of components. But that avenue isn't available here. It also hampers contributions from folks who want to help improve things.

Not sure what you mean by this. Ember.js is architected as a series of "microlibraries" that are composed together at build time. Each package has its own set of tests. I don't believe they are obfuscated in any way. For example, all of the routing tests are here: https://github.com/emberjs/ember.js/tree/master/packages/ember-routing/tests

Also I can't run the tests without phantomjs? That's no good. Ember is for the browser first and foremost right? IMO, tests for browser javascript should always be available by just hitting a url. Chrome dev tools is the preferred tool for debugging. You can add options for running in a headless browser or node. But browser should be the default.

You can definitely run the tests without phantomjs. Just run bin/rackup in the Ember directory, then visit http://localhost:9292/tests/index.html?package=all.

My foray into Ember land was extremely enlightening. Make no mistake, I could write a document this size about the stuff I like as well. But the barriers to entry are really significant unless you have a rails background. It's okay to lean on familiarity with rails if that's the audience you want. But if you want to capture more of the larger js community, you've gotta pull back the covers a bit more. Or at least you need to give us a thorough introduction to Convention over Configuration in the context of Ember, because not all of us are going to learn rails just to learn Ember.

You make a very good point. We definitely assume a worldview where our target developers accept that convention-over-configuration is good. We've tried to take the DHH ethos and recontextualize it in JavaScript. That's not really the current culture in JS (unfortunately, IMO).

I think focus in a project is important. For now, we are okay with focusing on meeting the needs of developers who already believe in convention-over-configuration. Once we've saturated that market, perhaps we can turn to convincing the rest of the JS community that some of these ideas are good ones. :)

Thanks again for your feedback. As I said at the beginning, stuff like this is absolutely invaluable for library developers.

@toranb
Copy link

toranb commented Jan 13, 2013

I can relate to the pain when learning ember and without a doubt you will find that the docs are in flux as the team approaches the 1.0 release. To help people learn about how to build a minimal ember app (with ember-data) I did a short screencast after the new router was merged into master. A few points above are mentioned as I attempt to show the template first model that ember is working toward in the latest release.

http://toranbillups.com/blog/archive/2013/01/03/Intro-to-ember-js-and-the-new-router-api/

I also wanted to show ember-data is more than a rails friendly project so I built a django adapter / serializer and open sources it for others to learn from.

https://github.com/toranb/ember-data-django-rest-adapter

@polotek
Copy link
Author

polotek commented Jan 13, 2013

Thanks @tomdale. The thing that excites me most about your response is the tests :) I totally get what's happening now. But I really didn't put that together. Even though I was looking through code files. Putting things in packages/ isn't a structure I've seen anywhere. And I kept looking in the root tests/ dir and all I saw was weirdness. I even see the notes about running tests in the README now. Apologies.

@polotek
Copy link
Author

polotek commented Jan 13, 2013

@toranb it's been a long time since I used django. Does it have a pretty standard REST infrastructure now? I'm gonna take a look at that.

@bsodmike
Copy link

Thoroughly enjoyed your feedback @tomdale - color me tempted to give Ember a spin now..!

In terms of debugging, we provide lots of introspection tools into the object model. We should document those better, and we're always open to suggestions for improvement.

Would appreciate any details on this please, even a nudge would suffice :)

@toranb
Copy link

toranb commented Jan 13, 2013

@polotek currently the django community has 2 well done REST frameworks that sit on top of the core django framework (django doesn't support a full blown REST api itself that I know of). The one I wrote is for the latest called "django-rest-framework" but the other framework "tastypie" also has an ember-data adapter

https://github.com/escalant3/ember-data-tastypie-adapter/

The big difference that I've seen (never done an app with tastypie myself so keep that bias in mind) is that django rest framework holds closer to the django convensions (think of a method in django and this REST framework has a method with the same name that does about the same thing except it makes it REST friendly).

Also the tastypie setup assumes hypermedia out of the box and the DRF makes that optional / both are worth a look

@sly7-7
Copy link

sly7-7 commented Jan 13, 2013

@bsodmike: I think http://vimeo.com/37539737 is a very good start. Some references are outdated, but it may help.

@bsodmike
Copy link

@toranb thanks for your screencast on the new router, I'm 30 minutes in and can already see the forest for the trees :D

@sly7-7 Thanks!

@thecodejack
Copy link

I like Ember and want to prefer it over other frameworks..But there are so many changes happened and expecting more, its really making difficult for the people like me....

@bernardeli
Copy link

Thanks for putting all your thoughts together and share.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment