• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Api's and ember js
 

Api's and ember js

on

  • 3,926 views

This is my #MagmaConf presentation, I talk about API's, Ruby on Rails and EmberJS

This is my #MagmaConf presentation, I talk about API's, Ruby on Rails and EmberJS

Statistics

Views

Total Views
3,926
Views on SlideShare
3,535
Embed Views
391

Actions

Likes
7
Downloads
0
Comments
0

13 Embeds 391

http://www.scoop.it 206
http://stream.rubydaily.org 94
http://www.sgtalento.com 53
https://twitter.com 20
http://rubydailyorg.blogspot.com 6
http://feeds.rubydaily.org 3
http://localhost 2
http://www.soso.com 2
http://www.bing.com 1
https://hootsuite.scoop.it 1
http://cloud.feedly.com 1
https://duckduckgo.com 1
http://www.google.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Api's and ember js Api's and ember js Presentation Transcript

    • API’s and EmberJSEdwin CruzMagmaConf 2013, Manzanillo, Colima
    • Plan• API best practices• Better API’s• EmberJS• Lessons learned
    • GET /api/products #List{"products" :[{"product" : { "id" : 1, "name" : "Product 1", "status" : "archived"} },{"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } }]}REST + CRUD
    • REST + CRUDGET /api/products/2 #Show{"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } }
    • REST + CRUDPOST /api/products #Create{"name" : "Product 2"}
    • REST + CRUDPUT /api/products/2 #Update{"name" : "Product dos"}
    • REST + CRUDDELETE /api/products/2 #Delete
    • API VERSIONING• API Internal (internal apps)• API External (mobile app ? )• API Public (general public)
    • API VERSIONING/int/api/products/ext/api/products/pub/api/products
    • API VERSIONING/int/api/products?version=2/ext/api/products?version=2/pub/api/products?version=2
    • API VERSIONING/v2/int/api/products/v2/ext/api/products/v2/pub/api/products
    • API VERSIONINGhttp://int.myapp.com/productshttp://api.myapp.com/products
    • IDEAL WORLD
    • IDEAL WORLDCorrect usage of: Accept Header
    • IDEAL WORLDCorrect usage of: Accept HeaderAccept: application/vnd.mycompany.com;
    • IDEAL WORLDCorrect usage of: Accept HeaderAccept: application/vnd.mycompany.com;version=2,application/json
    • HTTP CODES200 OK201 Created202 Accepted400 Bad Request401 Unauthorized402 Payment Required404 Not Found409 Conflict422 Unprocessable Entity500 Internal Server Error503 Service Unavailable
    • HTTP CODESHTTP/1.1 200 okGET /v1/products{"products" :[{"product" : { "id" : 1, "name" : "Product 1", "status" : "archived"} },{"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } }]}
    • HTTP CODESHTTP/1.1 201 CreatedPOST /v1/products{“product”: [“name” : “name”]}
    • HTTP CODESHTTP/1.1 400 Bad Request{“errors”: [“Invalid JSON structure”,“unexpected TSTRING, expected ‘}’ atline 2”]}
    • HTTP CODESHTTP/1.1 401 Unauthorized{“errors”: [“access denied”]}
    • HTTP CODESHTTP/1.1 401 Unauthorized{“errors”: [“api_key not valid”,“api_key can’t contain spaces”]}
    • HTTP CODESHTTP/1.1 401 Unauthorized{“errors”: [“api_key not found, please visithttp://account.myapp.com/api and register”]}
    • HTTP CODESHTTP/1.1 422 Unprocessable Entity{“errors”: [“vendor_code: can’t be blank”],“doc”: [“vendor_code”: [“description” : “Vendor code”,“format” : “Three letters followed by 4 digits”,“example” : “SOL1234”]]}
    • HTTP CODESHTTP/1.1 500 Internal Server Error{“exception”: [“Database not available”]}
    • CODIGOS HTTPHTTP/1.1 503 Service Unavailable{“messages”: [“Under maintenance”]}
    • Better API’s• Reduce number of requests• DRY• Cacheable• Scalable
    • Better API’s{ products: [{product: {id: 1,name: Product 1,status: active,code: PRO3455,vendor: {id: 3,name: SuperVendor},variants: [variant: { sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},variant: { sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]}}]}GET /api/products #List
    • Better API’s{ products: [{product: {id: 1,name: Product 1,status: active,code: PRO3455,vendor: {id: 3,name: Super Vendor},variants: [variant: { sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},variant: { sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]}}]}GET /api/products #List
    • Better API’s{ products: [{product: {id: 1,name: Product 1,vendor: {id: 3,name: Super Vendor},variants: []}},{product: {id: 99,name: Product 99,vendor: {id: 3,name: Super Vendor},variants: []}}]}GET /api/products #List
    • Better API’s{ products: [{product: {id: 1,name: Product 1,vendor: {id: 3,name: Super Vendor},variants: []}},{ product: {id: 99,name: Product 99,vendor: {id: 3,name: Super Vendor},variants: []}}]}GET /api/products #List
    • Better API’s{ products: [{product: {id: 1,name: Product 1,vendor_id: 3,variants: []}},{product: {id: 99,name: Product 99,vendor_id: 3,variants: []}}],vendors: [{id: 3,name: Super Vendor}]}GET /api/products #List
    • Better API’s{ products: [{product: {id: 1,name: Product 1,vendor_id: 3,variants: []}},{product: {id: 99,name: Product 99,vendor_id: 3,variants: []}}],vendors: [{id: 3,name: SuperVendor}]}GET /api/products #List
    • Better API’s{ products: [{ id: 1,name: Product 1,vendor_id: 3,variants: []},{ id: 99,name: Product 99,vendor_id: 3,variants: []}],vendors: [{id: 3,name: SuperVendor}]}GET /api/products #List
    • Better API’sGET /api/products #List{ products: [{ id: 1,variants: [ {id : 3, size:‘S’, property_ids: [3, 4, 5] },{id : 4, size:‘M’, property_ids: [5, 6, 7] }},{ id: 99,variants: [ {id : 5, size:‘S’, property_ids: [6] },{id : 6, size:‘M’, property_ids: [7] }}],properties: [{id: 3, name: Cotton},{id: 4, name: Water Resistant},{id: 5, name: Imported},{id: 6, name: Special},{id: 7, name: Super Property},]}
    • Caching
    • Better API’sGET /api/products #List{ products: [{ id: 1,version: 1369972820,variants: [ {id : 3, size:‘S’, property_ids: [3, 4, 5] },{id : 4, size:‘M’, property_ids: [5, 6, 7] }},{ id: 99,version: 1369886447,variants: [ {id : 5, size:‘S’, property_ids: [6] },{id : 6, size:‘M’, property_ids: [7] }}],properties: [{id: 3, name: Cotton},{id: 4, name: Water Resistant},{id: 5, name: Imported},{id: 6, name: Special},{id: 7, name: Super Property},]}
    • Better API’sGET /api/products #List{ products: [{ id: 1,version: 1369972820,variants: [ {id : 3, size:‘S’, property_ids: [3, 4, 5] },{id : 4, size:‘M’, property_ids: [5, 6, 7] }},{ id: 99,version: 1369886447,variants: [ {id : 5, size:‘S’, property_ids: [6] },{id : 6, size:‘M’, property_ids: [7] }}],properties: [{id: 3, name: Cotton},{id: 4, name: Water Resistant},{id: 5, name: Imported},{id: 6, name: Special},{id: 7, name: Super Property},]}Russian Doll Caching!!!
    • Better API’sdef indexif stale?(SizeRun.count)respond_with SizeRun.allendendAutomatic Etag/last_modified generation!!!Rails to the rescue!
    • Better API’sRails to the rescue!First load Following loads304 Not Modified
    • Better API’sCounter Caches{ products: [{ id: 1,version: 1369972820,variants: [ {id : 3, size:‘S’, property_ids: [3, 4, 5], inventory_units_on_hand_count: 5 },{id : 4, size:‘M’, property_ids: [5, 6, 7], inventory_units_on_hand_count: 0 }},{ id: 99,version: 1369886447,variants: [ {id : 5, size:‘S’, property_ids: [6], inventory_units_on_hand_count: 15 },{id : 6, size:‘M’, property_ids: [7], inventory_units_on_hand_count: 500 }}]}
    • Better API’sCounter Cachesclass InventoryUnit < ActiveRecord::Basebelongs_to :variant,:counter_cache => :inventory_units_on_hand_count,:conditions => "status = ‘on_hand’"end
    • Caching with Solr
    • Solr:SolrTM is the popular, blazing fast opensource enterprise search platform fromthe Apache LuceneTMproject. Its majorfeatures include powerful full-textsearch, hit highlighting, facetedsearch, near real-time indexing,dynamic clustering, databaseintegration, rich document (e.g., Word,PDF) handling, and geospatial search.
    • Sunspot - Solr• Solr powerful interface• Expressive DSL• Easily pluggable in to any ORM
    • Sunspot - Solrclass Post < ActiveRecord::Basesearchable dotext :title, :bodytext :comments docomments.map { |comment| comment.body }endboolean :featuredinteger :blog_idinteger :expensive_operationinteger :category_ids, :multiple => truedouble :average_ratingtime :published_attime :expired_atstring :sort_title dotitle.downcase.gsub(/^(an?|the)/, )endendend
    • Sunspot - Solrclass Post < ActiveRecord::Basesearchable dotext :title, :bodytext :comments docomments.map { |comment| comment.body }endboolean :featuredinteger :blog_idinteger :expensive_operationinteger :category_ids, :multiple => truedouble :average_ratingtime :published_attime :expired_atstring :sort_title dotitle.downcase.gsub(/^(an?|the)/, )endendend
    • Sunspot - Solrclass Post < ActiveRecord::Basesearchable(auto_index: false) dotext :title, :bodytext :comments docomments.map { |comment| comment.body }endboolean :featuredinteger :blog_idinteger :expensive_operationinteger :category_ids, :multiple => truedouble :average_ratingtime :published_attime :expired_atstring :sort_title dotitle.downcase.gsub(/^(an?|the)/, )endendend
    • Sunspot - SolrPost.search dofulltext best pizzawith :blog_id, 1with(:published_at).less_than Time.noworder_by :published_at, :descpaginate :page => 2, :per_page => 15facet :category_ids, :author_idend
    • Sunspot - Solr• Results• Contains AR instances• ‘Lazy’ loading• Hits• Solr indexed data• super fast accessresults vs hits
    • Sunspot - Solrresults vs hitsclass ResultProxy < ResultHitdef method_missing(method, *args, &block)beginhit.instance.send(method)rescuehit.stored(method)endendend
    • Sunspot - Solrresults vs hitsclass ResultProxy < ResultHitdef method_missing(method, *args, &block)beginif setup.stored_fields(method).empty?hit.instance.send(method)elsehit.stored(method)endrescuehit.instance.send(sym, *args)endendend
    • Sunspot - Solrresults vs hitsclass ResultProxy < ResultHitdef value(of)hit.stored(of)enddef has_property? name!setup.stored_fields(name).empty?endend
    • Sunspot - SolrAssociations?def method_missing method, *args@solr_instance ||= Search::Collection.new(Program.search { with(:id, id)}).firstif @solr_instance && @solr_instance.has_property?(method)@solr_instance.value(method)elsesuper method, *argsendend$ model_active_record = Model.find(id)$ model_active_record.association.value_stored_in_solr
    • Serializers
    • Serializers• ActiveModel::Serializer• Rabl• JSON Builder• render json: hash
    • Serializers• ActiveModel::Serializer <= no yet,WIP• Rabl• JSON Builder• render json: hash
    • Serializers• ActiveModel::Serializer• Rabl• JSON Builder <= Good enough• render json: hash
    • Serializers• ActiveModel::Serializer• Rabl• JSON Builder• render json: hash <= Hipster
    • Serializers• ActiveModel::Serializer•Rabl <= Good enough with cache• JSON Builder• render json: hash
    • Serializers{ products: [{product: {id: 1,name: Product 1,status: active,code: PRO3455,vendor: {id: 3,name: SuperVendor},variants: [variant: { sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},variant: { sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]}}]}GET /api/products #List
    • Serializers{ products: [{product: {id: 1,name: Product 1,status: active,code: PRO3455,vendor: {id: 3,name: SuperVendor},variants: [variant: { sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},variant: { sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]}}]}GET /api/products #ListNo ideal for real world
    • Serializers{meta: [{"page": 1,"per_page": 100,"pages": 708,"total": 70772}],"products": [{.},{.},{.},{.}],"suppliers": [{.},{.},{.},{.}],"properties": [{.},{.},{.},{.}],}GET /api/products #ListIdeal for real world
    • Serializers#index.rablobject falsenode(:style_search) do[{page: @search_results.results.current_page,total_pages: @search_results.results.total_pages,per_page: @search_results.results.per_page,}]endnode :products do@search_results.resultsendnode :suppliers do@suppliersendnode :properties do@propertiesendGET /api/products #ListIdeal for real world
    • EmberJSA javascript framework for creating ambitious webapplications.
    • Modern Web 2.0Problems• Data binding• Real time updates• Performance• Patterns• Scalability
    • Modern Web 2.0ProblemsLogo Login InfoProfile InfoLogin InfoMenuList of items with edit optionsFooterFooterFooter
    • Modern Web 2.0ProblemsLogo Currently selected Login InfoProfile InfoLogin InfoMenuList of items with edit options TopViewedFooterFooterFooter
    • EmberJS• Model• Router• Controller• View• Template
    • EmberJS - Models• Handled by Ember Data• Library to manage remote data• Acts as ‘ORM’• Transactional
    • EmberJS - ModelsMyApp.Variant = DS.Model.extend({sku: DS.attr(string),size: DS.attr(string),unitsOnHand: DS.attr(string),product: DS.belongsTo(MyApp.Product)})variants: [{ sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},{ sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]
    • EmberJS - Observers• .property• computed properties• .observes• reacts when something happens• bindings• ensure objects in multiple places are in
    • EmberJS - ObservesMyApp.SavingMessage = Ember.Mixin.create({observeSaving: function() {if(this.get(isSaving) && !this.get(queueSavingMessage)) {this.set(queueSavingMessage, Ember.flashQueue.pushFlash(spindicator, Saving, true, true));} else {if(this.get(queueSavingMessage)) {this.set(queueSavingMessage.specialFlag, false);this.set(queueSavingMessage, null);}}}.observes(isSaving)});MyApp.Program = DS.Model.extend(MyApp.SavingMessage, {........}
    • EmberJS - Models/bindings• isLoaded• isReloading• isDirty• isSaving• isDeleted• isError• isNew• isValid
    • EmberJS - Models/callbacks• didLoad• didReload• didUpdate• didCreate• didDelete• becameInvalid• becameError
    • EmberJS - Models/callbacksvar product = MyApp.Product.createRecord();product.on(didCreate, this, function() {console.log(‘Product created’);});
    • EmberJS - Modelsvar MyAppAdapter = DS.RESTAdapter.extend();MyAppAdapter.configure(MyApp.ProductSearch, {sideloadAs: products});MyAppAdapter.configure(MyApp.Properties, {sideloadAs: properties});MyAppAdapter.configure(MyApp.Supplier, {sideloadAs: suppliers});
    • EmberJS - ModelsMyApp.ProductsSearch = DS.Model.extend({perPage: DS.attr(number),totalPages: DS.attr(number),totalEntries: DS.attr(number),page: DS.attr(number),products: DS.hasMany(MyApp.Product)})
    • EmberJS - Models/transactionsvar transaction = this.get(‘store’).transaction();var product = transaction.createRecord(MyApp.Product, {});product.on(didCreate, this, function() {console.log(‘program created’);});transaction.commit();
    • Router
    • EmberJS - Router• EmberJS way to manage states• Each state is represented by a URL• User interaction causes URL to change• redirected to a new URL• Updates controller• Change template screen
    • EmberJS - RouterApp.Router.map(function() {this.route(index, { path: /});this.resource(program, {path: /programs/:program_id}, function(){this.resource(programStyle, {path: /styles/:style_id}, function(){this.route(newImage, { path: /image/new});this.route(purchaseInfoTab, { path: /purchase_info});});this.resource(programSku, {path: /skus/:sku_id});});});
    • EmberJS - RouterApp.Router.map(function() {this.route(index, { path: /});this.resource(program, {path: /programs/:program_id}, function(){this.resource(programStyle, {path: /styles/:style_id}, function(){this.route(newImage, { path: /image/new});this.route(purchaseInfoTab, { path: /purchase_info});});this.resource(programSku, {path: /skus/:sku_id});});});
    • Controllers
    • EmberJS - Controllers• Designed to decorate models• display logic• Properties frontend specific• they don’t need to be saved• Decorates data• Bridge between your data and your
    • EmberJS - ControllersApp.ApplicationController = Ember.Controller.extend({// the initial value of the `search` propertysearch: ,query: function() {// the current value of the text fieldvar query = this.get(search);this.transitionToRoute(search, { query: query });}});
    • Route
    • EmberJS - RouteApp.Router.map(function() {this.resource(post, { path: /posts/:id });});App.PostRoute = Ember.Route.extend({model: function(params) {return App.Post.find(params.id);},setupController: function(controller, model) {this.controllerFor(‘myPosts’, model);},renderTemplate: function() {this.render(‘betterPost’);}});
    • Views
    • EmberJS -Views• Barely used, because of handlebars• Sophisticated user events• Create re-usable components
    • EmberJS -Viewsvar view = Ember.View.create({templateName: say-hello,name: "Bob"});view.appendTo(#container);
    • EmberJS -Views/EventsMyApp.UpVoteView = Ember.View.extend({tagName: span,title: Upvote,templateName: common/upvote,click: function(evt) {this.get(controller).send(upVote);}});
    • Templates
    • EmberJS - Templates• Handlebars is used as template engine• regular HTML with embedded handlebarexpressions• Minimal Templating on Steroids• Emblem.js if you are haml lover
    • Handlebars<div class="entry"><h1>{{title}}</h1><h2>By {{author.name}}</h2><div class="body">{{body}}</div></div>
    • Gluing everything
    • EmberJS
    • EmberJSRequestRouterViewTemplateModelControllerHTMLRoute
    • EmberJSMyApp = Ember.Application.create();var MyAppAdapter = DS.RESTAdapter.extend();MyApp.Store = DS.Store.extend({revision: 12,adapter: MyAppAdapter});
    • EmberJS$ application.handlebars<header><h1>MagmaConf Talk!</h1></header><div>{{outlet}}</div>{{ outlet modal }}<footer>&copy;2013 Crowd Interactive.</footer>
    • EmberJS$ application.handlebars<header><h1>MagmaConf Talk!</h1></header><div>{{outlet}}</div>{{ outlet modal }}<footer>&copy;2013 Crowd Interactive.</footer>
    • EmberJS$products/index.handlebars{{#if content.isLoaded}}<div class="row"><table class="table table-striped table-bordered" id="programs-table"><thead><tr><th>Product Image</th><th>Product Name</th><th>Vendor</th></tr></thead><tbody>{{#each product in controller.currentPage.products}}{{ view MyApp.ProductRowView productBinding="product"}}{{else}}<td colspan="3">Your search returned no results.</td>{{/each}}</tbody></table></div>{{else}}<div class="row">Loading...</div>{{/if}}
    • EmberJS$products/index.handlebars{{#if content.isLoaded}}<div class="row"><table class="table table-striped table-bordered" id="programs-table"><thead><tr><th>Product Image</th><th>Product Name</th><th>Vendor</th></tr></thead><tbody>{{#each product in controller.currentPage.products}}{{ view MyApp.ProductRowView productBinding="product"}}{{else}}<td colspan="3">Your search returned no results.</td>{{/each}}</tbody></table></div>{{else}}<div class="row">Loading...</div>{{/if}}
    • EmberJSMyApp.ProductRowView = Ember.View.extend({templateName:products/row,tagName:tr,click:function(evt) {router = this.get(controller.target.router);router.transitionTo(product.index, this.product);}});
    • EmberJS$ products/row.handlebars{{#with product}}<td><img {{bindAttr src="primaryImageUrl"}} {{ bindAttr alt="name" }}></td><td>{{name}}</td><td>{{supplierNames}}</td>{{/with}}
    • EmberJS$ products/row.handlebars{{#with product}}<td><img {{bindAttr src="primaryImageUrl"}} {{ bindAttr alt="name" }}></td><td>{{name}}</td><td>{{supplierNames}}</td>{{/with}}
    • EmberJS$ products/row.handlebars{{#with product}}<td><img {{bindAttr src="primaryImageUrl"}} {{ bindAttr alt="name" }}></td><td>{{name}}</td><td>{{supplierNames}}</td>{{/with}}They will always be in sync!
    • EmberJS<ul><li {{bindAttr class="hasPrevious::disabled"}}><a href="#" {{action "previousPage"}}>&larr; previous</a></li>{{#each page in currentPage.listOfPages}}{{#with page}}<li {{bindAttr class="active"}}><a href="#" {{action changePage number}}>{{number}}</a></li>{{/with}}{{/each}}<li {{bindAttr class="hasNext::disabled"}}><a href="#" {{action "nextPage"}}> next &rarr;</a></li></ul>
    • EmberJS<ul><li {{bindAttr class="hasPrevious::disabled"}}><a href="#" {{action "previousPage"}}>&larr; previous</a></li>{{#each page in currentPage.listOfPages}}{{#with page}}<li {{bindAttr class="active"}}><a href="#" {{action changePage number}}>{{number}}</a></li>{{/with}}{{/each}}<li {{bindAttr class="hasNext::disabled"}}><a href="#" {{action "nextPage"}}> next &rarr;</a></li></ul>
    • EmberJS<h2>Editing User: {{content.name}}</h2><div class="row"><div class="span4"><label>First name</label>{{view Ember.TextField valueBinding="content.firstName"}}<label>Last name</label>{{view Ember.TextField valueBinding="content.lastName"}}<label>Email</label>{{view Ember.TextField valueBinding="content.email"}}
    • EmberJS<h2>Editing User: {{content.name}}</h2><div class="row"><div class="span4"><label>First name</label>{{view Ember.TextField valueBinding="content.firstName"}}<label>Last name</label>{{view Ember.TextField valueBinding="content.lastName"}}<label>Email</label>{{view Ember.TextField valueBinding="content.email"}}
    • EmberJSMyApp.ProductRoute = Ember.Route.extend({exit: function() {var controller = this.controllerFor(product);var content = controller.get(content);if (content && content.get(isDirty)) {content.get(transaction).rollback();}this._super();}});
    • EmberJS• Enumerables• forEach, map, mapProperty, filter,• ContainerView• String functions• camelize, capitalize, classify, dasherize, underscore, etc!• View Components• TextArea,TextField, CheckBox• etc
    • EmberJSHopefully I didn’t confuse you... more
    • Thanks!Questions?Edwin Cruzedwin@crowdint.com@softr8