Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ember in the Real World

Ember in the Real World

If you’ve done online tutorials, courses, or followed blog posts, you probably have a decent idea of what Ember is. But how do you build real, production-grade applications in it? How do you stitch together the pieces from tutorials into a larger picture? What about all the stuff that isn’t in the tutorials?

We’ll help you sort through some of the tougher challenges beyond the guides and blog posts:

- Working with non-standard APIs
- Integrating existing jQuery components
- Authentication and security
- Testing strategies to let you sleep at night
- Refactoring toward Ember from “jQuery spaghetti”

We’ll condense 2 years of shipping production applications so you can connect the dots and put the individual pieces together in a real-world application, so you can build ambitious web applications that are fun and easy to maintain.

Presented at Fluent 2015.

tehviking

April 24, 2015
Tweet

More Decks by tehviking

Other Decks in Programming

Transcript

  1. HELLO THERE.

    View full-size slide

  2. BRANDON HAYS
    @TEHVIKING
    “We do chicken right!”

    View full-size slide

  3. IT’S TIME TO STOP BEING POLITE
    AND START GETTING REAL.

    View full-size slide

  4. YOU PROBABLY
    ALREADY KNOW
    ABOUT EMBER

    View full-size slide

  5. EMBER’S ABOUT DUE
    FOR ITS ANNUAL REVIEW

    View full-size slide

  6. BUT FIRST: STATE OF
    THE ECOSYSTEM

    View full-size slide

  7. WHY ALL THIS CHURN
    AND TURBULENCE?

    View full-size slide

  8. THE HYPE CYCLE
    PLATEAU OF
    PRODUCTIVITY
    SLOPE OF
    ENLIGHTENMENT
    TROUGH OF
    DISILLUSIONMENT
    TECHNOLOGY
    TRIGGER
    PEAK
    OF INFLATED
    EXPECTATIONS
    VISIBILITY
    MATURITY

    View full-size slide

  9. TECHNOLOGY
    TRIGGER
    TECHNOLOGY TRIGGER

    View full-size slide

  10. PEAK
    OF INFLATED
    EXPECTATIONS
    PEAK OF INFLATED EXPECTATIONS
    “Look at all
    the code I
    didn’t write!”

    View full-size slide

  11. INFLATED PROMISE: “THIS WILL
    MAKE YOU THE 10x DEVELOPER
    YOU ALWAYS KNEW YOU WERE”

    View full-size slide

  12. YOU ARE NOW A
    JEDI ROCKSTAR
    …RIGHT?

    View full-size slide

  13. (WE’LL TALK ABOUT THE
    REAL PROMISE AT THE END)

    View full-size slide

  14. TROUGH OF
    DISILLUSIONMENT
    TROUGH OF DISILLUSIONMENT

    View full-size slide

  15. “CIRCLE OF ETERNAL HOPE”
    CIRCLE OF
    ETERNAL
    HOPE

    View full-size slide

  16. SLOPE OF
    ENLIGHTENMENT
    SLOPE OF ENLIGHTENMENT

    View full-size slide

  17. PLATEAU OF
    PRODUCTIVITY
    PLATEAU OF PRODUCTIVITY

    View full-size slide

  18. NO ONE UPVOTES THE
    PLATEAU OF PRODUCTIVITY

    View full-size slide

  19. “HYPE” IS NOT AN INSULT, IT’S AN INEVITABILITY
    PLATEAU OF
    PRODUCTIVITY
    SLOPE OF
    ENLIGHTENMENT
    TROUGH OF
    DISILLUSIONMENT
    TECHNOLOGY
    TRIGGER
    PEAK
    OF INFLATED
    EXPECTATIONS
    VISIBILITY
    MATURITY

    View full-size slide

  20. EMBER’S ANNUAL
    360° REVIEW

    View full-size slide

  21. COMPLIMENT
    SANDWICH

    View full-size slide

  22. SO LET’S START WITH
    SOME NICE STUFF

    View full-size slide

  23. THE INITIAL DEVELOPER
    EXPERIENCE

    View full-size slide

  24. STUFF THAT USED TO SUCK
    ABOUT STARTING WITH EMBER
    Documentation
    Tutorials
    View Layer
    Routing
    Initial Setup
    File structure
    Sharing code/
    plugins
    Build tooling
    Closing div tags
    (seriously)
    Authentication
    Testing
    Data layer
    Templating syntax
    Animation
    Deployment
    Integrating with
    existing apps
    Performance

    View full-size slide

  25. STUFF THAT IS GREAT ABOUT
    STARTING WITH EMBER
    Documentation
    Tutorials
    View Layer
    Routing
    Initial Setup
    File structure
    Sharing code/
    plugins
    Build tooling
    Closing div tags
    (seriously)
    Authentication
    Testing
    Data layer
    Templating syntax
    Animation
    Deployment
    Integrating with
    existing apps
    Performance

    View full-size slide

  26. EMBER IS
    PRETTY GOOD
    AT TURNING
    WEAKNESSES
    INTO
    STRENGTHS

    View full-size slide

  27. THE MATERIALS HAVE
    GOTTEN… REALLY GOOD

    View full-size slide

  28. https://leanpub.com/ember-cli-101

    View full-size slide

  29. https://teamgaslight.com/training/courses/14-introduction-to-ember-js

    View full-size slide

  30. https://teamgaslight.com/training/courses/14-introduction-to-ember-js
    https://www.codeschool.com/courses/warming-up-with-ember-js

    View full-size slide

  31. EMBER CLI.
    OMIGOSH.

    View full-size slide

  32. LET’S MAKE SOMETHING
    UTTERLY NECESSARY

    View full-size slide

  33. EMBER CLI
    GENERATORS

    View full-size slide

  34. ember  new  use-­‐lss

    View full-size slide

  35. NPM INSTALL ENTIRE-UNIVERSE

    View full-size slide

  36. ember  generate  resource  product  
    installing  
       create  app/models/product.js  
       create  tests/unit/models/product-­‐test.js  
       create  app/routes/products.js  
       create  app/templates/products.hbs  
       create  tests/unit/routes/products-­‐test.js

    View full-size slide

  37. TEST INTEGRATION

    View full-size slide

  38. Add karma, karma-cil, karma-mocha, karma-chai-
    plugins, karma-chrome-launcher, karma-ember-
    preprocessor, karma-mocha, karma-phantomjs-
    launcher, karma-junit-reporter to package.json
    Compile and install PhantomJS
    npm install
    Install ember-mocha-adapter and chai-jquery via
    Bower
    Set up a karma.conf file
    In your karma.conf:
    Customize your frameworks section in karma.conf
    to include mocha, chai, sinon-chai, and chai-jquery
    Set up a Grunt task to build your Sass on each test
    run
    Include your vendor dependencies in karma.conf
    Include your code in files section
    Debug file load order issues
    Add & configure handlebars karma preprocessor
    Then, create test-helper.js & include it in
    karma.conf
    In your test-helper.js:
    Configure chai and mocha defaults
    Set Ember.testing to true
    Set App.setupForTesting to true
    Call App.injectTestHelpers()
    Set up app on each test run
    Tear down app after each test run
    Ensure beforeEach and afterEach use the run loop
    Use the .done() callback for async beforeEach
    TEST SETUP

    View full-size slide

  39. ember  generate  acceptance-­‐test  list-­‐products  
    installing  
       create  tests/acceptance/list-­‐products-­‐test.js
    LOL J/K

    View full-size slide

  40. THE FIRST TIME
    I DID THIS

    View full-size slide

  41. BUILD PIPELINE

    View full-size slide

  42. HOW DEVS SEE
    THEIR CUSTOM
    BUILD PIPELINE

    View full-size slide

  43. HOW IT
    ACTUALLY
    WORKS

    View full-size slide

  44. FAST, INCREMENTAL
    BUILDS WITH BROCCOLI

    View full-size slide

  45. DROP-IN SUPPORT FOR SASS,
    STYLUS, EVEN COFFEESCRIPT

    View full-size slide

  46. ES6 TRANSPILER:
    MODULES & NEW ES6
    SYNTAX, FOR FREE

    View full-size slide

  47. EMBER CLI ADDONS

    View full-size slide

  48. NEW, BUT IMPRESSIVE
    ECOSYSTEM

    View full-size slide

  49. LOOK AT ALL THE YAKS
    I HAVE TO SHAVE

    View full-size slide

  50. AND IT FEELS
    AWESOME

    View full-size slide

  51. YER A WIZARD HARRY

    View full-size slide

  52. THE EMBER WAY:
    FOLLOW IT, GET FREE
    STUFF

    View full-size slide

  53. EMBER ROUTER &
    NESTED ROUTES

    View full-size slide

  54. DATA, UI, STATE, URLs,
    IN PERFECT HARMONY

    View full-size slide

  55. ApplicationRoute (generated): ‘/’
    ProductRoute: ‘/:id’
    Rendered into products {{outlet}}
    URL: / products / 230
    ProductsRoute: ‘/products’
    Rendered into application {{outlet}}

    View full-size slide

  56. ACTION HANDLING
    ProductController
    {{action “add”}} ProductRoute
    ProductsRoute
    ApplicationRoute

    View full-size slide

  57. EMBER COMPONENTS

    View full-size slide


  58. EMBER COMPONENTS
    OF THE NEAR FUTURE

    View full-size slide


  59. {{each people as |person|}}
    {{person.name}}
    {{/each}}

    EMBER COMPONENTS
    OF THE NEAR FUTURE

    View full-size slide

  60. COMPUTED PROPERTIES
    & COMPUTED MACROS

    View full-size slide

  61. COMPUTED PROPERTIES
    app/controllers/cart.js
    export default Ember.Controller.extend({

    cartItemSubtotals: Ember.computed.mapBy("model.cartItems", "subtotal"),

    orderSubtotal: Ember.computed.sum("cartItemSubtotals"),

    shippingCost: Ember.computed(function() {

    return 0;

    }),

    orderTax: Ember.computed("orderSubtotal", function() {

    var taxCents = (this.get("orderSubtotal") * 100) * 0.0825;

    return taxCents / 100;

    }),

    orderTotal: Ember.computed("orderTax", "orderSubtotal", function() {

    return this.get("orderSubtotal") + this.get("orderTax");

    }),

    });

    View full-size slide

  62. COMPUTED MACROS
    app/controllers/cart.js
    export default Ember.Controller.extend({

    cartItemSubtotals: Ember.computed.mapBy("model.cartItems", "subtotal"),

    orderSubtotal: Ember.computed.sum("cartItemSubtotals"),

    shippingCost: Ember.computed(function() {

    return 0;

    }),

    orderTax: Ember.computed("orderSubtotal", function() {

    var taxCents = (this.get("orderSubtotal") * 100) * 0.0825;

    return taxCents / 100;

    }),

    orderTotal: Ember.computed("orderTax", "orderSubtotal", function() {

    return this.get("orderSubtotal") + this.get("orderTax");

    }),

    });

    View full-size slide

  63. IF YOU UNDERSTAND:
    • Router & Routes
    • Components
    • Computed Properties
    • Generating with Ember CLI
    YOU CAN BUILD AWESOME STUFF

    View full-size slide

  64. POST-TUTORIAL HIGH

    View full-size slide

  65. RE-ENTERING EARTH’s GRAVITY :(

    View full-size slide

  66. 3-week PoC
    6-month slog
    ESTIMATION IS HARD
    IMPOSSIBLE

    View full-size slide

  67. TRUE 10x ENGINEERING

    View full-size slide

  68. THE PROBLEM: YOUR BOSS
    THINKS POC IS PRODUCTION

    View full-size slide

  69. YEAH… IF YOU COULD JUST
    PUT THAT PoC IN PRODUCTION,
    THAT’D BE GREAT

    View full-size slide

  70. NOW WE’RE
    GOING TO
    HURT EMBER’S
    FEELINGS

    View full-size slide

  71. 5 THINGS THAT EMBER
    COULD DO BETTER

    View full-size slide

  72. 1. API CUSTOMIZATION
    2. AUTHENTICATION
    3. EXTERNAL jQUERY LIBRARIES
    4. TESTING
    5. INTEGRATION WITH EXISTING APPS

    View full-size slide

  73. WITH GUIDANCE, THE TROUGH
    CAN EVEN BE FUN TO NAVIGATE

    View full-size slide

  74. 1. EMBER DATA API
    CUSTOMIZATION

    View full-size slide

  75. MY API DOESN’T PLAY NICELY WITH
    EMBER DATA.
    EMBER GENERATE YOUR WAY OUT OF
    THAT ONE, SMART GUY.

    View full-size slide

  76. OUR APP WANTS DATA FROM
    A NON-REST ENDPOINT

    View full-size slide

  77. WHAT DO YOU DO WHEN YOUR
    API DOESN’T CONFORM?

    View full-size slide

  78. SOLUTION 1
    AJAX & pushPayload

    View full-size slide

  79. store.pushPayload()
    Push any JSON (properly formatted) into
    the store, skipping Ember Data’s AJAX.

    View full-size slide

  80. import Ember from 'ember';


    export default Ember.Route.extend({

    model: function() {

    var store = this.store;

    return Ember.$.getJSON("/user.json").then(function(response){

    Ember.run(function() {

    store.pushPayload("user", response);

    });

    return store.find("user", response.user.id);

    });

    }

    });
    USING PUSHPAYLOAD
    app/routes/application.js

    View full-size slide

  81. THE PAGE LOADS!

    View full-size slide

  82. ADD ITEM TO CART
    ISN’T WORKING

    View full-size slide

  83. GOOD API DOCS: ESSENTIAL

    View full-size slide

  84. SOLUTION 2
    Custom Serializer/Adapter

    View full-size slide

  85. ADAPTER:
    DEALS WITH THE SERVER
    SERIALIZER:
    DEALS WITH THE DATA

    View full-size slide

  86. ADAPTERS & SERIALIZERS
    Your API
    Object({id: 3}).save()
    Adapter
    Builds URL,
    sends XHR
    Serializer
    Generates
    payload &
    parses response

    View full-size slide

  87. CUSTOM ADAPTERS:
    WHEN YOU NEED TO
    MODIFY API ENDPOINTS

    View full-size slide

  88. YES, THERE’S A
    GENERATOR FOR
    THAT

    View full-size slide

  89. ember  generate  adapter  cart-­‐item

    View full-size slide

  90. export default ApplicationAdapter.extend({

    buildURL: function(type, id, record) {

    var url = this._super(type, id, record);
    // MODIFY URL HERE :)
    return url;

    }

    });
    USE buildURL TO MODIFY THE
    API ENDPOINT URL

    View full-size slide

  91. export default ApplicationAdapter.extend({

    buildURL: function(type, id, record) {

    var url = this._super("item", id, record);
    var modifiedURL = "/cart" + url;
    return url;

    }

    });
    OVERRIDE BUILDURL
    type is “cartItem” in the model…
    app/adapters/cart-item.js

    View full-size slide

  92. export default ApplicationAdapter.extend({

    buildURL: function(type, id, record) {

    var url = this._super("item", id, record);
    var modifiedURL = "/cart" + url;
    return url;

    }

    });
    OVERRIDE BUILDURL
    app/adapters/cart-item.js
    …so we’ll replace that with “item” for our API

    View full-size slide

  93. export default ApplicationAdapter.extend({

    buildURL: function(type, id, record) {

    var url = this._super("item", id, record);
    var modifiedURL = "/cart" + url;
    return url;

    }

    });
    OVERRIDE BUILDURL
    app/adapters/cart-item.js
    then prefix it with “cart” so it’s “/cart/items”

    View full-size slide

  94. PROBLEM: NON-COMPLIANT
    DATA PAYLOAD
    Started  POST  "/cart/items"  for  127.0.0.1  at  2015-­‐04-­‐15  14:53:50  -­‐0500  
    Processing  by  CartItemsController#create  as  JSON  
       Parameters:  {“cart_item"=>{"product_id"=>"1",  "cart_id"=>"1"}}  
       User  Load  (2.0ms)    SELECT    "users".*  FROM  "users"    WHERE  "users"."id"  =  1    
    ORDER  BY  "users"."id"  ASC  LIMIT  1  
       Cart  Load  (1.0ms)    SELECT    "cart".*  FROM  "carts"    WHERE  “carts”."user_id"  =  1  
    LIMIT  1  
    Completed  500  Internal  Server  Error  in  50ms  (ActiveRecord:  3.0ms)  
    ActionController::UnpermittedParameters  (found  unpermitted  parameters:  cart_id):  
       app/controllers/cart_items_controller.rb:27:in  `create_params'  
       app/controllers/cart_items_controller.rb:5:in  `create'

    View full-size slide

  95. CUSTOM SERIALIZERS:
    WHEN YOU NEED TO
    MODIFY THE DATA

    View full-size slide

  96. http://jsonapi.org/
    THE FUTURE

    View full-size slide

  97. THE PRESENT
    http://emberjs.com/blog/2013/05/03/ember-data-progress-update.html

    View full-size slide

  98. HI THERE I’M AN API COWBOY
    BANG BANG PAYLOAD PAYLOAD
    ENDPOINT ENPOINT URL

    View full-size slide

  99. export default ApplicationSerializer.extend({

    serialize: function(record, options) {

    var json = this._super(record, options);
    // MODIFY JSON HERE :)
    return json;

    }

    });
    USE serialize() TO MODIFY THE
    DATA TO THE SERVER

    View full-size slide

  100. export default ApplicationSerializer.extend({

    normalize: function(type, hash, prop) {
    // MODIFY HASH HERE :)
    return this._super(type, hash, prop);

    }

    });
    USE normalize() TO MODIFY
    THE DATA FROM THE SERVER

    View full-size slide

  101. ember  generate  serializer  cart-­‐item

    View full-size slide

  102. export default ApplicationSerializer.extend({

    serialize: function(cartItem, options) {

    var json = this._super(cartItem, options);

    delete json.cart_id;

    return json;

    }

    });

    OVERRIDE SERIALIZE
    app/serializers/cart-item.js
    override the method

    View full-size slide

  103. export default ApplicationSerializer.extend({

    serialize: function(cartItem, options) {

    var json = this._super(cartItem, options);

    delete json.cart_id;

    return json;

    }

    });

    OVERRIDE SERIALIZE
    app/serializers/cart-item.js
    assign super() call to var

    View full-size slide

  104. export default ApplicationSerializer.extend({

    serialize: function(cartItem, options) {

    var json = this._super(cartItem, options);

    delete json.cart_id;

    return json;

    }

    });

    OVERRIDE SERIALIZE
    app/serializers/cart-item.js
    modify & return modified data

    View full-size slide

  105. SET IT AND FORGET IT

    View full-size slide

  106. ADD TO CART: DONE!

    View full-size slide

  107. 2. AUTHENTICATION

    View full-size slide

  108. WHY THE HECK ISN’T
    AUTHENTICATION BUILT INTO EMBER?

    View full-size slide

  109. signIn: function(email, password) {

    Ember.$.post("/users/sign_in.json", {

    user: {

    email: email,

    password: password

    }

    }, "json").then(function(response) {

    this.store.pushPayload("user", response);

    var user = this.store.getById("user", response.user.id);

    this.send("signedIn", user);

    this.send("closeModal");

    }.bind(this));

    },
    ROLL YOUR OWN
    app/routes/application.js

    View full-size slide

  110. signOut: function() {

    Ember.$.ajax("/users/sign_out.json", {

    method: "post",

    dataType: "json",

    data: {

    _method: "DELETE"

    }

    }).then(function(response) {

    this.store.pushPayload("user", response);

    var user = this.store.getById("user", response.user.id);

    this.set("controller.model", user);

    this.send("closeModal");

    }.bind(this));

    }
    ROLL YOUR OWN
    app/routes/application.js

    View full-size slide

  111. IF ONLY THERE
    WERE A GREAT
    CODE SHARING
    STORY IN
    EMBER…
    OH WELL

    View full-size slide

  112. ember  install  ember-­‐cli-­‐simple-­‐auth
    USE ADDONS

    View full-size slide

  113. USING ADDONS

    View full-size slide

  114. AUTH & CLIENT SIDE
    DATA SECURITY

    View full-size slide

  115. “DID YOU JUST SAY
    CLIENT SIDE DATA SECURITY?”

    View full-size slide

  116. import Ember from 'ember';


    export default Ember.Route.extend({

    model: function() {

    return store.find(“post”);

    }

    });
    LESS SECURE

    View full-size slide

  117. import Ember from 'ember';


    export default Ember.Route.extend({
    model: function() {

    return this.currentUser.get(“posts”);

    }

    });
    MORE SECURE

    View full-size slide

  118. YOU’RE IN THE API SECURITY
    BUSINESS NOW, NOT JUST
    WEB PAGE SECURITY

    View full-size slide

  119. 3. jQUERY WIDGETS

    View full-size slide

  120. HOW DO I GET jQUERY WIDGETS TO
    PLAY NICE IN MY SINGLE PAGE APP?

    View full-size slide

  121. WRAP IN COMPONENTS

    View full-size slide

  122. ADAPTING TO
    jQUERY APIS
    lib-wrapper-component
    jquery-lib.js
    $.on(“lib:selected”)
    $.initFunction()
    didInsertElement sendAction
    “select”
    DATA IN DATA OUT
    “selectItem”
    action sent up
    the chain
    {{x-foo item=bar}} select=“selectItem”
    this.get(“bar”)

    View full-size slide

  123. TYPEAHEAD.JS

    View full-size slide

  124. BUT WE’LL BUILD OUR OWN

    View full-size slide

  125. INSTALLING DEPENDENCIES:
    BOWER & BROCCOLI

    View full-size slide

  126. bower  install  -­‐-­‐save-­‐dev  typeahead.js
    INSTALL TYPEAHEAD

    View full-size slide

  127. ...
    var app = new EmberApp();
    ...

    app.import("bower_components/typeahead.js/dist/typeahead.jquery.js");

    ...
    IMPORT IN BROCFILE
    Brocfile.js

    View full-size slide

  128. I HAVE THE WIDGET, HOW
    DO I GET DATA IN & OUT?

    View full-size slide

  129. FIND REQUIRED
    OPTIONS IN DOCS
    https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md

    View full-size slide

  130. YOU ARE AT THE MERCY OF
    THE API DESIGNER OF
    YOUR jQUERY WIDGET

    View full-size slide

  131. MAYBE THE API AUTHOR IS
    TRYING TO TELL YOU
    “DON’T USE THIS.”

    View full-size slide

  132. $(document).ready()
    IS OVER

    View full-size slide

  133. import Ember from 'ember';


    export default Ember.Component.extend({

    didInsertElement: function() {
    this._super.apply(this, arguments);

    this.$('input').typeahead({}, {

    displayKey: this.get('displayKey'),

    source: this.get('source')

    });

    }

    });

    DATA IN: PASSING PROPERTIES
    TO jQUERY ON INIT
    app/components/twitter-typeahead.js
    Pass displayKey and source
    in second arg object

    View full-size slide

  134. {{twitter-typeahead source=getStates displayKey=formatState}}
    From the controller…
    To the component
    DATA IN: PASSING PROPERTIES
    TO COMPONENT VIA TEMPLATE

    View full-size slide

  135. export default Ember.Component.extend({

    didInsertElement: function() {

    this._super.apply(this, arguments);
    this.typeahead = this.$('input').typeahead({}, {

    displayKey: this.get('displayKey'),

    source: this.get('source')

    });
    var _this = this;

    this.typeahead.on('typeahead:selected', function(e, suggestion) {

    Ember.run(function() {

    _this.sendAction(“on-select", suggestion);

    });

    });

    }

    });

    Listen on custom DOM event
    & send Ember action
    DATA OUT: LISTENING TO CUSTOM
    EVENTS & FIRING ACTIONS
    app/components/twitter-typeahead.js

    View full-size slide

  136. Bind the controller action to the component’s action
    INCLUDE THE COMPONENT
    app/templates/checkout/address.hbs
    {{twitter-­‐typeahead  source=getStates  displayKey=formatState  on-­‐select=“selectState”}}

    View full-size slide

  137. TYPEAHEAD COMPONENT:
    DONE!

    View full-size slide

  138. MAKE NO MISTAKE: EMBER CLI IS
    WAY BEYOND MOST OTHER TOOLS

    View full-size slide

  139. BUT WE’RE HOLDING IT
    TO A HIGHER STANDARD

    View full-size slide

  140. QUNIT
    module('Acceptance: ListProducts', {
    setup: function() {
    App = startApp();
    this.server = createServer();
    },
    teardown: function() {
    Ember.run(App, 'destroy');
    this.server.shutdown();
    }
    });
    test("shows all products", function() {
    visit("/products");
    andThen(function() {
    equal($(".spec-product-item").length, 3);
    });
    });

    View full-size slide

  141. describe('Starting the app', function() {

    beforeEach(function() {

    App = startApp();

    this.server = createServer();

    });


    afterEach(function() {

    Ember.run(App, 'destroy');

    this.server.shutdown();

    });


    describe('visiting /products', function() {

    beforeEach(function() {

    visit('/products');

    });

    it('shows all products', function() {

    expect($(".spec-product-item").length).to.equal(3);

    });

    });

    });

    MOCHA

    View full-size slide

  142. UNIT TEST:
    Verify a unit (model, route,
    component) works in isolation

    View full-size slide

  143. ACCEPTANCE TEST:
    Simulate a user’s path through
    using a feature & verify it works

    View full-size slide

  144. EMBER ACCEPTANCE TEST
    HELPERS
    visit
    click
    fillIn
    keyEvent
    triggerEvent
    find
    currentPath
    currentURL
    currentRouteName
    andThen
    …or register your
    own

    View full-size slide

  145. Mocha lends itself to acceptance style,
    qUnit to unit style.
    Bottom line: It’s personal preference.

    View full-size slide

  146. TDD, FOR ME, USUALLY MEANS
    ACCEPTANCE TESTS FIRST

    View full-size slide

  147. PRETENDER
    https://github.com/trek/pretender

    View full-size slide

  148. import Pretender from 'pretender';

    import Ember from 'ember';


    export function createServer() {

    return new Pretender(function() {

    this.get("/user.json", function(req) {

    return [200, {"Content-Type": "application/json"}, JSON.stringify(userFixture())];

    });

    this.get("/products", function(req) {

    return [200, {"Content-Type": "application/json"}, JSON.stringify(productsFixture())];

    });
    ...

    });

    }

    MOCK SERVER
    tests/fixtures.js

    View full-size slide

  149. export function productsFixture(id) {

    var productFixtures = {

    "1": {

    "id":1,

    “name":"Chicago Transit Authority",

    "artist":"Chicago",

    "amazon_url":"http://www.amazon.com/Chicago-Transit-Authority-CHICAGO"

    },
    ...

    };


    if(arguments.length) {

    return productFixtures[id];

    } else {

    var productsArray = Object.keys(productFixtures).map(function(k){return
    productFixtures[k];});

    return {products: productssArray};

    }

    }
    FIXTURE DATA
    tests/fixtures.js

    View full-size slide

  150. import Ember from 'ember';

    ...

    import startApp from 'adv-ember-training/tests/helpers/start-app';

    import { createServer } from '../fixtures';
    IMPORT CREATESERVER
    tests/acceptance/list-products-test.js

    View full-size slide

  151. module('Acceptance: ListProducts', {

    setup: function() {

    App = startApp();

    // Assign this.server to createServer()

    this.server = createServer();

    },

    SET UP PRETENDER
    tests/acceptance/list-products-test.js

    View full-size slide

  152. teardown: function() {

    Ember.run(App, 'destroy');

    // Tear down by calling .shutdown() on this.server

    this.server.shutdown();

    }

    TEAR DOWN PRETENDER
    tests/acceptance/list-products-test.js

    View full-size slide

  153. test("adding to cart", function(assert) {

    // Visit the /cart route

    visit('/cart');


    andThen(function() {

    // Use the click helper to click ".spec-add-item:first"

    click(".spec-add-item:first");

    });


    andThen(function() {

    // assert that $(“.spec-playlist-item).length is now 4.

    assert.equal($(".spec-cart-item").length, 4);

    });

    });
    WRITE ADD ITEM SPEC
    tests/acceptance/add-products-to-cart—test.js

    View full-size slide

  154. ember test
    1..53
    # tests 53
    # pass 53
    # fail 0
    # ok
    RUN TESTS

    View full-size slide

  155. WHAT ABOUT WHEN MY FIXTURE
    DATA BECOMES UNMAINTAINABLE?

    View full-size slide

  156. ¯\_(ツ)_/¯

    View full-size slide

  157. 5. “SPRINKLING”
    EMBER

    View full-size slide

  158. `EMBER NEW` IS AWESOME.
    BUT I ALREADY HAVE AN APP.

    View full-size slide

  159. NOW YOU CAN CHOOSE:
    COMPONENT-BASED “SPRINKLING”
    OR
    ROUTER-BASED “PJAX” REFACTOR

    View full-size slide

  160. BONUS: ANIMATION

    View full-size slide

  161. HOW THE HECK AM I SUPPOSED
    TO ANIMATE WHEN EMBER IS IN
    CHARGE OF THE DOM?

    View full-size slide

  162. this.transition(

    this.fromRoute('products.index'),

    this.toRoute('product'),

    this.use('explode', {

    matchBy: 'data-product-id',

    use: ['flyTo', {duration: 300}]

    }, {

    use: ['toLeft', {duration: 300}]

    }),

    this.reverse('explode', {

    matchBy: 'data-product-id',

    use: ['flyTo', {duration: 300}]

    }, {

    use: ['toRight', {duration: 300}]

    })

    );
    THIS IS THE CODE
    app/transitions.js

    View full-size slide

  163. BONUS: IF YOU UNDERSTAND
    LIQUID FIRE’S TRANSITION MAP,
    YOU UNDERSTAND EMBER ROUTING

    View full-size slide

  164. IT TOOK LONGER THAN
    WE’D HOPED, BUT WE’VE
    FINISHED THE APP!

    View full-size slide

  165. OUR STORE IS FULLY
    ARMED AND OPERATIONAL

    View full-size slide

  166. THE EMOTIONAL ROLLER COASTER OF YOUR
    FIRST PRODUCTION EMBER APP
    PLATEAU OF
    CONTINUOUS
    DELIVERY
    SLOPE OF
    MASTERY
    TROUGH OF
    “IT SHOULDN’T BE
    THIS TOUGH”
    “EMBER NEW”
    TRIGGER
    PEAK OF
    TUTORIAL-
    DRIVEN
    DEVELOPMENT
    ENTHUSIASM
    PROJECT DURATION

    View full-size slide

  167. WHY WADE THROUGH THE
    TROUGH OF DISILLUSIONMENT?

    View full-size slide

  168. WHILE YOUR BOSS WASN’T
    LOOKING, EVERYTHING CHANGED

    View full-size slide

  169. WHILE WE SWAM THE
    AVALANCHE, EVERYTHING
    CHANGED

    View full-size slide

  170. CLIENT DRIVEN UX IS A THING

    View full-size slide

  171. EMBER IS A MATURE,
    ROBUST ANSWER TO THIS

    View full-size slide

  172. WITH 2.0, EMBER IS APPROACHING
    THE PLATEAU OF PRODUCTIVITY

    View full-size slide

  173. HOW DID EMBER
    “CROSS THE CHASM”?

    View full-size slide

  174. BECAUSE WHILE SOME DEVS
    CHURN IN THIS CYCLE…
    CIRCLE OF
    ETERNAL
    HOPE

    View full-size slide

  175. EMBER HAS ALWAYS BEEN
    TARGETING THIS
    PLATEAU OF
    PRODUCTIVITY

    View full-size slide

  176. I USED TO THINK EMBER’S
    PROMISE WAS TO MAKE
    ME A 10x DEV

    View full-size slide

  177. “LOOK AT ALL THE CODE
    I’M NOT WRITING!”

    View full-size slide

  178. EMBER’S REAL PROMISE
    IS ABOUT HELPING YOU
    REACH THE PLATEAU

    View full-size slide

  179. ON THE PLATEAU
    THERE IS TIME TO FOCUS

    View full-size slide

  180. FOCUS ON THINGS
    THAT ADD VALUE

    View full-size slide

  181. (BUSINESS VALUE)

    View full-size slide

  182. ON THE PLATEAU
    THERE IS TIME TO THINK

    View full-size slide

  183. MAYBE ABOUT HOW
    YOUR BOSS IS AN IDIOT

    View full-size slide

  184. AND SINCE YOU FOCUS
    ON BUSINESS VALUE

    View full-size slide

  185. YOU CAN AFFORD TO
    FIRE YOUR BOSS

    View full-size slide

  186. AND THEN YOU DO

    View full-size slide

  187. AND YOU TURN OUT OK

    View full-size slide

  188. BUT THAT’S ANOTHER TALK

    View full-size slide

  189. THANKS
    @tehviking
    http://frontside.io

    View full-size slide

  190. IMAGE CREDITS
    Meditate by Nadir Hashmi https://www.flickr.com/photos/nadircruise/
    Sandwich by Adam Sherer https://www.flickr.com/photos/arsherer/
    The Weight of Thought by Evan Leeson https://www.flickr.com/photos/ecstaticist/

    View full-size slide