SlideShare a Scribd company logo
1 of 115
API’s and EmberJS
Edwin Cruz
MagmaConf 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 + CRUD
GET /api/products/2 #Show
{"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } }
REST + CRUD
POST /api/products #Create
{
"name" : "Product 2"
}
REST + CRUD
PUT /api/products/2 #Update
{
"name" : "Product dos"
}
REST + CRUD
DELETE /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 VERSIONING
http://int.myapp.com/products
http://api.myapp.com/products
IDEAL WORLD
IDEAL WORLD
Correct usage of: Accept Header
IDEAL WORLD
Correct usage of: Accept Header
Accept: application/vnd.mycompany.com;
IDEAL WORLD
Correct usage of: Accept Header
Accept: application/vnd.mycompany.com;
version=2,application/json
HTTP CODES
200 OK
201 Created
202 Accepted
400 Bad Request
401 Unauthorized
402 Payment Required
404 Not Found
409 Conflict
422 Unprocessable Entity
500 Internal Server Error
503 Service Unavailable
HTTP CODES
HTTP/1.1 200 ok
GET /v1/products
{"products" :
[
{"product" : { "id" : 1, "name" : "Product 1", "status" : "archived"} },
{"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } }
]
}
HTTP CODES
HTTP/1.1 201 Created
POST /v1/products
{
“product”: [
“name” : “name”
]
}
HTTP CODES
HTTP/1.1 400 Bad Request
{
“errors”: [
“Invalid JSON structure”,
“unexpected TSTRING, expected ‘}’ at
line 2”
]
}
HTTP CODES
HTTP/1.1 401 Unauthorized
{
“errors”: [
“access denied”
]
}
HTTP CODES
HTTP/1.1 401 Unauthorized
{
“errors”: [
“api_key not valid”,
“api_key can’t contain spaces”
]
}
HTTP CODES
HTTP/1.1 401 Unauthorized
{
“errors”: [
“api_key not found, please visit
http://account.myapp.com/api and register”
]
}
HTTP CODES
HTTP/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 CODES
HTTP/1.1 500 Internal Server Error
{
“exception”: [
“Database not available”
]
}
CODIGOS HTTP
HTTP/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’s
GET /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’s
GET /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’s
GET /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’s
def index
if stale?(SizeRun.count)
respond_with SizeRun.all
end
end
Automatic Etag/last_modified generation!!!
Rails to the rescue!
Better API’s
Rails to the rescue!
First load Following loads
304 Not Modified
Better API’s
Counter 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’s
Counter Caches
class InventoryUnit < ActiveRecord::Base
belongs_to :variant,
:counter_cache => :inventory_units_on_hand_count,
:conditions => "status = ‘on_hand’"
end
Caching with Solr
Solr:
SolrTM is the popular, blazing fast open
source enterprise search platform from
the Apache LuceneTMproject. Its major
features include powerful full-text
search, hit highlighting, faceted
search, near real-time indexing,
dynamic clustering, database
integration, rich document (e.g., Word,
PDF) handling, and geospatial search.
Sunspot - Solr
• Solr powerful interface
• Expressive DSL
• Easily pluggable in to any ORM
Sunspot - Solr
class Post < ActiveRecord::Base
searchable do
text :title, :body
text :comments do
comments.map { |comment| comment.body }
end
boolean :featured
integer :blog_id
integer :expensive_operation
integer :category_ids, :multiple => true
double :average_rating
time :published_at
time :expired_at
string :sort_title do
title.downcase.gsub(/^(an?|the)/, '')
end
end
end
Sunspot - Solr
class Post < ActiveRecord::Base
searchable do
text :title, :body
text :comments do
comments.map { |comment| comment.body }
end
boolean :featured
integer :blog_id
integer :expensive_operation
integer :category_ids, :multiple => true
double :average_rating
time :published_at
time :expired_at
string :sort_title do
title.downcase.gsub(/^(an?|the)/, '')
end
end
end
Sunspot - Solr
class Post < ActiveRecord::Base
searchable(auto_index: false) do
text :title, :body
text :comments do
comments.map { |comment| comment.body }
end
boolean :featured
integer :blog_id
integer :expensive_operation
integer :category_ids, :multiple => true
double :average_rating
time :published_at
time :expired_at
string :sort_title do
title.downcase.gsub(/^(an?|the)/, '')
end
end
end
Sunspot - Solr
Post.search do
fulltext 'best pizza'
with :blog_id, 1
with(:published_at).less_than Time.now
order_by :published_at, :desc
paginate :page => 2, :per_page => 15
facet :category_ids, :author_id
end
Sunspot - Solr
• Results
• Contains AR instances
• ‘Lazy’ loading
• Hits
• Solr indexed data
• super fast access
results vs hits
Sunspot - Solr
results vs hits
class ResultProxy < ResultHit
def method_missing(method, *args, &block)
begin
hit.instance.send(method)
rescue
hit.stored(method)
end
end
end
Sunspot - Solr
results vs hits
class ResultProxy < ResultHit
def method_missing(method, *args, &block)
begin
if setup.stored_fields(method).empty?
hit.instance.send(method)
else
hit.stored(method)
end
rescue
hit.instance.send(sym, *args)
end
end
end
Sunspot - Solr
results vs hits
class ResultProxy < ResultHit
def value(of)
hit.stored(of)
end
def has_property? name
!setup.stored_fields(name).empty?
end
end
Sunspot - Solr
Associations?
def method_missing method, *args
@solr_instance ||= Search::Collection.new(Program.search { with(:id, id)}).first
if @solr_instance && @solr_instance.has_property?(method)
@solr_instance.value(method)
else
super method, *args
end
end
$ 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 #List
No ideal for real world
Serializers
{meta: [{
"page": 1,
"per_page": 100,
"pages": 708,
"total": 70772
}],
"products": [
{.},{.},{.},{.}
],
"suppliers": [
{.},{.},{.},{.}
],
"properties": [
{.},{.},{.},{.}
],
}
GET /api/products #List
Ideal for real world
Serializers
#index.rabl
object false
node(:style_search) do
[{
page: @search_results.results.current_page,
total_pages: @search_results.results.total_pages,
per_page: @search_results.results.per_page,
}]
end
node :products do
@search_results.results
end
node :suppliers do
@suppliers
end
node :properties do
@properties
end
GET /api/products #List
Ideal for real world
EmberJS
A javascript framework for creating ambitious web
applications.
Modern Web 2.0
Problems
• Data binding
• Real time updates
• Performance
• Patterns
• Scalability
Modern Web 2.0
Problems
Logo Login Info
Profile Info
Login Info
Menu
List of items with edit options
FooterFooterFooter
Modern Web 2.0
Problems
Logo Currently selected Login Info
Profile Info
Login Info
Menu
List of items with edit options TopViewed
FooterFooterFooter
EmberJS
• Model
• Router
• Controller
• View
• Template
EmberJS - Models
• Handled by Ember Data
• Library to manage remote data
• Acts as ‘ORM’
• Transactional
EmberJS - Models
MyApp.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 - Observes
MyApp.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/
callbacks
var product = MyApp.Product.createRecord();
product.on('didCreate', this, function() {
console.log(‘Product created’);
});
EmberJS - Models
var MyAppAdapter = DS.RESTAdapter.extend();
MyAppAdapter.configure('MyApp.ProductSearch', {sideloadAs: 'products'});
MyAppAdapter.configure('MyApp.Properties', {sideloadAs: 'properties'});
MyAppAdapter.configure('MyApp.Supplier', {sideloadAs: 'suppliers'});
EmberJS - Models
MyApp.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/
transactions
var 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 - Router
App.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 - Router
App.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 - Controllers
App.ApplicationController = Ember.Controller.extend({
// the initial value of the `search` property
search: '',
query: function() {
// the current value of the text field
var query = this.get('search');
this.transitionToRoute('search', { query: query });
}
});
Route
EmberJS - Route
App.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 -Views
var view = Ember.View.create({
templateName: 'say-hello',
name: "Bob"
});
view.appendTo('#container');
EmberJS -Views/Events
MyApp.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 handlebar
expressions
• 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
EmberJS
Request
Router
View
Template
Model
Controller
HTML
Route
EmberJS
MyApp = 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}}
EmberJS
MyApp.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"}}
EmberJS
MyApp.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
EmberJS
Hopefully I didn’t confuse you... more
Thanks!
Questions?
Edwin Cruz
edwin@crowdint.com
@softr8

More Related Content

What's hot

E2 appspresso hands on lab
E2 appspresso hands on labE2 appspresso hands on lab
E2 appspresso hands on labNAVER D2
 
Building Beautiful REST APIs in ASP.NET Core
Building Beautiful REST APIs in ASP.NET CoreBuilding Beautiful REST APIs in ASP.NET Core
Building Beautiful REST APIs in ASP.NET CoreStormpath
 
Beautiful REST+JSON APIs with Ion
Beautiful REST+JSON APIs with IonBeautiful REST+JSON APIs with Ion
Beautiful REST+JSON APIs with IonStormpath
 
Build a Node.js Client for Your REST+JSON API
Build a Node.js Client for Your REST+JSON APIBuild a Node.js Client for Your REST+JSON API
Build a Node.js Client for Your REST+JSON APIStormpath
 
Elasticsearch for SQL Users
Elasticsearch for SQL UsersElasticsearch for SQL Users
Elasticsearch for SQL UsersAll Things Open
 
ElasticSearch for .NET Developers
ElasticSearch for .NET DevelopersElasticSearch for .NET Developers
ElasticSearch for .NET DevelopersBen van Mol
 
Rest and Sling Resolution
Rest and Sling ResolutionRest and Sling Resolution
Rest and Sling ResolutionDEEPAK KHETAWAT
 
The never-ending REST API design debate
The never-ending REST API design debateThe never-ending REST API design debate
The never-ending REST API design debateRestlet
 
Building APIs in an easy way using API Platform
Building APIs in an easy way using API PlatformBuilding APIs in an easy way using API Platform
Building APIs in an easy way using API PlatformAntonio Peric-Mazar
 
Amazon Web Services for PHP Developers
Amazon Web Services for PHP DevelopersAmazon Web Services for PHP Developers
Amazon Web Services for PHP DevelopersJeremy Lindblom
 
Lumberjack 2 - Supercharging WordPress in 2018
Lumberjack 2 - Supercharging WordPress in 2018Lumberjack 2 - Supercharging WordPress in 2018
Lumberjack 2 - Supercharging WordPress in 2018Joe Lambert
 
David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...
David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...
David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...Codemotion
 
Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4DEVCON
 
Best practices for RESTful web service design
Best practices for RESTful web service designBest practices for RESTful web service design
Best practices for RESTful web service designRamin Orujov
 
Building sustainable RESTFul services
Building sustainable RESTFul servicesBuilding sustainable RESTFul services
Building sustainable RESTFul servicesOrtus Solutions, Corp
 

What's hot (18)

E2 appspresso hands on lab
E2 appspresso hands on labE2 appspresso hands on lab
E2 appspresso hands on lab
 
Building Beautiful REST APIs in ASP.NET Core
Building Beautiful REST APIs in ASP.NET CoreBuilding Beautiful REST APIs in ASP.NET Core
Building Beautiful REST APIs in ASP.NET Core
 
Beautiful REST+JSON APIs with Ion
Beautiful REST+JSON APIs with IonBeautiful REST+JSON APIs with Ion
Beautiful REST+JSON APIs with Ion
 
Build a Node.js Client for Your REST+JSON API
Build a Node.js Client for Your REST+JSON APIBuild a Node.js Client for Your REST+JSON API
Build a Node.js Client for Your REST+JSON API
 
JAX-RS 2.0 New Features
JAX-RS 2.0 New FeaturesJAX-RS 2.0 New Features
JAX-RS 2.0 New Features
 
Elasticsearch for SQL Users
Elasticsearch for SQL UsersElasticsearch for SQL Users
Elasticsearch for SQL Users
 
Advanced Json
Advanced JsonAdvanced Json
Advanced Json
 
Ams adapters
Ams adaptersAms adapters
Ams adapters
 
ElasticSearch for .NET Developers
ElasticSearch for .NET DevelopersElasticSearch for .NET Developers
ElasticSearch for .NET Developers
 
Rest and Sling Resolution
Rest and Sling ResolutionRest and Sling Resolution
Rest and Sling Resolution
 
The never-ending REST API design debate
The never-ending REST API design debateThe never-ending REST API design debate
The never-ending REST API design debate
 
Building APIs in an easy way using API Platform
Building APIs in an easy way using API PlatformBuilding APIs in an easy way using API Platform
Building APIs in an easy way using API Platform
 
Amazon Web Services for PHP Developers
Amazon Web Services for PHP DevelopersAmazon Web Services for PHP Developers
Amazon Web Services for PHP Developers
 
Lumberjack 2 - Supercharging WordPress in 2018
Lumberjack 2 - Supercharging WordPress in 2018Lumberjack 2 - Supercharging WordPress in 2018
Lumberjack 2 - Supercharging WordPress in 2018
 
David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...
David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...
David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...
 
Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4
 
Best practices for RESTful web service design
Best practices for RESTful web service designBest practices for RESTful web service design
Best practices for RESTful web service design
 
Building sustainable RESTFul services
Building sustainable RESTFul servicesBuilding sustainable RESTFul services
Building sustainable RESTFul services
 

Similar to Api's and ember js

Need for Speed: Removing speed bumps in API Projects
Need for Speed: Removing speed bumps in API ProjectsNeed for Speed: Removing speed bumps in API Projects
Need for Speed: Removing speed bumps in API ProjectsŁukasz Chruściel
 
Building APIs in an easy way using API Platform
Building APIs in an easy way using API PlatformBuilding APIs in an easy way using API Platform
Building APIs in an easy way using API PlatformAntonio Peric-Mazar
 
Api development with rails
Api development with railsApi development with rails
Api development with railsEdwin Cruz
 
Building Awesome APIs in Grails
Building Awesome APIs in GrailsBuilding Awesome APIs in Grails
Building Awesome APIs in Grailsclatimer
 
Connecting your Python App to OpenERP through OOOP
Connecting your Python App to OpenERP through OOOPConnecting your Python App to OpenERP through OOOP
Connecting your Python App to OpenERP through OOOPraimonesteve
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...Fabio Franzini
 
Crafting Evolvable Api Responses
Crafting Evolvable Api ResponsesCrafting Evolvable Api Responses
Crafting Evolvable Api Responsesdarrelmiller71
 
Prairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API ResponsesPrairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API Responsesdarrelmiller71
 
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...MongoDB
 
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...MongoDB
 
SharePoint REST vs CSOM
SharePoint REST vs CSOMSharePoint REST vs CSOM
SharePoint REST vs CSOMMark Rackley
 
Python RESTful webservices with Python: Flask and Django solutions
Python RESTful webservices with Python: Flask and Django solutionsPython RESTful webservices with Python: Flask and Django solutions
Python RESTful webservices with Python: Flask and Django solutionsSolution4Future
 
REST API Best Practices & Implementing in Codeigniter
REST API Best Practices & Implementing in CodeigniterREST API Best Practices & Implementing in Codeigniter
REST API Best Practices & Implementing in CodeigniterSachin G Kulkarni
 
Getting Started with DrupalGap
Getting Started with DrupalGapGetting Started with DrupalGap
Getting Started with DrupalGapAlex S
 
Zero to Sixty: AWS CloudFormation (DMG201) | AWS re:Invent 2013
Zero to Sixty: AWS CloudFormation (DMG201) | AWS re:Invent 2013Zero to Sixty: AWS CloudFormation (DMG201) | AWS re:Invent 2013
Zero to Sixty: AWS CloudFormation (DMG201) | AWS re:Invent 2013Amazon Web Services
 

Similar to Api's and ember js (20)

Need for Speed: Removing speed bumps in API Projects
Need for Speed: Removing speed bumps in API ProjectsNeed for Speed: Removing speed bumps in API Projects
Need for Speed: Removing speed bumps in API Projects
 
Building APIs in an easy way using API Platform
Building APIs in an easy way using API PlatformBuilding APIs in an easy way using API Platform
Building APIs in an easy way using API Platform
 
Api development with rails
Api development with railsApi development with rails
Api development with rails
 
Mashing Up The Guardian
Mashing Up The GuardianMashing Up The Guardian
Mashing Up The Guardian
 
Building Awesome APIs in Grails
Building Awesome APIs in GrailsBuilding Awesome APIs in Grails
Building Awesome APIs in Grails
 
Connecting your Python App to OpenERP through OOOP
Connecting your Python App to OpenERP through OOOPConnecting your Python App to OpenERP through OOOP
Connecting your Python App to OpenERP through OOOP
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
 
Mashing Up The Guardian
Mashing Up The GuardianMashing Up The Guardian
Mashing Up The Guardian
 
Crafting Evolvable Api Responses
Crafting Evolvable Api ResponsesCrafting Evolvable Api Responses
Crafting Evolvable Api Responses
 
Prairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API ResponsesPrairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API Responses
 
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
 
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
MongoDB World 2018: Time for a Change Stream - Using MongoDB Change Streams t...
 
SharePoint REST vs CSOM
SharePoint REST vs CSOMSharePoint REST vs CSOM
SharePoint REST vs CSOM
 
Python RESTful webservices with Python: Flask and Django solutions
Python RESTful webservices with Python: Flask and Django solutionsPython RESTful webservices with Python: Flask and Django solutions
Python RESTful webservices with Python: Flask and Django solutions
 
REST API Best Practices & Implementing in Codeigniter
REST API Best Practices & Implementing in CodeigniterREST API Best Practices & Implementing in Codeigniter
REST API Best Practices & Implementing in Codeigniter
 
REST easy with API Platform
REST easy with API PlatformREST easy with API Platform
REST easy with API Platform
 
Getting Started with DrupalGap
Getting Started with DrupalGapGetting Started with DrupalGap
Getting Started with DrupalGap
 
Zero to Sixty: AWS CloudFormation (DMG201) | AWS re:Invent 2013
Zero to Sixty: AWS CloudFormation (DMG201) | AWS re:Invent 2013Zero to Sixty: AWS CloudFormation (DMG201) | AWS re:Invent 2013
Zero to Sixty: AWS CloudFormation (DMG201) | AWS re:Invent 2013
 
ActiveRecord vs Mongoid
ActiveRecord vs MongoidActiveRecord vs Mongoid
ActiveRecord vs Mongoid
 
Android and REST
Android and RESTAndroid and REST
Android and REST
 

More from Edwin Cruz

Codigo Escalable WDT
Codigo Escalable WDTCodigo Escalable WDT
Codigo Escalable WDTEdwin Cruz
 
SGCE 2015 - eCommerce platforms
SGCE 2015 - eCommerce platformsSGCE 2015 - eCommerce platforms
SGCE 2015 - eCommerce platformsEdwin Cruz
 
Devops with ansible
Devops with ansibleDevops with ansible
Devops with ansibleEdwin Cruz
 
Containers in 5... 9 minutes
Containers in 5... 9 minutesContainers in 5... 9 minutes
Containers in 5... 9 minutesEdwin Cruz
 
Chilango Rails Ecommerce Lightning talk
Chilango Rails Ecommerce Lightning talkChilango Rails Ecommerce Lightning talk
Chilango Rails Ecommerce Lightning talkEdwin Cruz
 
Home made ceviche
Home made cevicheHome made ceviche
Home made cevicheEdwin Cruz
 
FSL Vallarta, mejorando el rendimiento de las aplicaciones web
FSL Vallarta, mejorando el rendimiento de las aplicaciones webFSL Vallarta, mejorando el rendimiento de las aplicaciones web
FSL Vallarta, mejorando el rendimiento de las aplicaciones webEdwin Cruz
 
Presentacion Programador Apasionado
Presentacion Programador ApasionadoPresentacion Programador Apasionado
Presentacion Programador ApasionadoEdwin Cruz
 
MagmaRails - Passionate Programmer
MagmaRails - Passionate ProgrammerMagmaRails - Passionate Programmer
MagmaRails - Passionate ProgrammerEdwin Cruz
 
Presentacion programador apasionado
Presentacion programador apasionadoPresentacion programador apasionado
Presentacion programador apasionadoEdwin Cruz
 
Migrando Rails Apps entre Cloud y Bare Metal Servers
Migrando Rails Apps entre Cloud y Bare Metal ServersMigrando Rails Apps entre Cloud y Bare Metal Servers
Migrando Rails Apps entre Cloud y Bare Metal ServersEdwin Cruz
 

More from Edwin Cruz (11)

Codigo Escalable WDT
Codigo Escalable WDTCodigo Escalable WDT
Codigo Escalable WDT
 
SGCE 2015 - eCommerce platforms
SGCE 2015 - eCommerce platformsSGCE 2015 - eCommerce platforms
SGCE 2015 - eCommerce platforms
 
Devops with ansible
Devops with ansibleDevops with ansible
Devops with ansible
 
Containers in 5... 9 minutes
Containers in 5... 9 minutesContainers in 5... 9 minutes
Containers in 5... 9 minutes
 
Chilango Rails Ecommerce Lightning talk
Chilango Rails Ecommerce Lightning talkChilango Rails Ecommerce Lightning talk
Chilango Rails Ecommerce Lightning talk
 
Home made ceviche
Home made cevicheHome made ceviche
Home made ceviche
 
FSL Vallarta, mejorando el rendimiento de las aplicaciones web
FSL Vallarta, mejorando el rendimiento de las aplicaciones webFSL Vallarta, mejorando el rendimiento de las aplicaciones web
FSL Vallarta, mejorando el rendimiento de las aplicaciones web
 
Presentacion Programador Apasionado
Presentacion Programador ApasionadoPresentacion Programador Apasionado
Presentacion Programador Apasionado
 
MagmaRails - Passionate Programmer
MagmaRails - Passionate ProgrammerMagmaRails - Passionate Programmer
MagmaRails - Passionate Programmer
 
Presentacion programador apasionado
Presentacion programador apasionadoPresentacion programador apasionado
Presentacion programador apasionado
 
Migrando Rails Apps entre Cloud y Bare Metal Servers
Migrando Rails Apps entre Cloud y Bare Metal ServersMigrando Rails Apps entre Cloud y Bare Metal Servers
Migrando Rails Apps entre Cloud y Bare Metal Servers
 

Api's and ember js

  • 1. API’s and EmberJS Edwin Cruz MagmaConf 2013, Manzanillo, Colima
  • 2. Plan • API best practices • Better API’s • EmberJS • Lessons learned
  • 3. GET /api/products #List {"products" : [ {"product" : { "id" : 1, "name" : "Product 1", "status" : "archived"} }, {"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } } ] } REST + CRUD
  • 4. REST + CRUD GET /api/products/2 #Show {"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } }
  • 5. REST + CRUD POST /api/products #Create { "name" : "Product 2" }
  • 6. REST + CRUD PUT /api/products/2 #Update { "name" : "Product dos" }
  • 7. REST + CRUD DELETE /api/products/2 #Delete
  • 8. API VERSIONING • API Internal (internal apps) • API External (mobile app ? ) • API Public (general public)
  • 14. IDEAL WORLD Correct usage of: Accept Header
  • 15. IDEAL WORLD Correct usage of: Accept Header Accept: application/vnd.mycompany.com;
  • 16. IDEAL WORLD Correct usage of: Accept Header Accept: application/vnd.mycompany.com; version=2,application/json
  • 17. HTTP CODES 200 OK 201 Created 202 Accepted 400 Bad Request 401 Unauthorized 402 Payment Required 404 Not Found 409 Conflict 422 Unprocessable Entity 500 Internal Server Error 503 Service Unavailable
  • 18. HTTP CODES HTTP/1.1 200 ok GET /v1/products {"products" : [ {"product" : { "id" : 1, "name" : "Product 1", "status" : "archived"} }, {"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } } ] }
  • 19. HTTP CODES HTTP/1.1 201 Created POST /v1/products { “product”: [ “name” : “name” ] }
  • 20. HTTP CODES HTTP/1.1 400 Bad Request { “errors”: [ “Invalid JSON structure”, “unexpected TSTRING, expected ‘}’ at line 2” ] }
  • 21. HTTP CODES HTTP/1.1 401 Unauthorized { “errors”: [ “access denied” ] }
  • 22. HTTP CODES HTTP/1.1 401 Unauthorized { “errors”: [ “api_key not valid”, “api_key can’t contain spaces” ] }
  • 23. HTTP CODES HTTP/1.1 401 Unauthorized { “errors”: [ “api_key not found, please visit http://account.myapp.com/api and register” ] }
  • 24. HTTP CODES HTTP/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” ] ] }
  • 25. HTTP CODES HTTP/1.1 500 Internal Server Error { “exception”: [ “Database not available” ] }
  • 26. CODIGOS HTTP HTTP/1.1 503 Service Unavailable { “messages”: [ “Under maintenance” ] }
  • 27. Better API’s • Reduce number of requests • DRY • Cacheable • Scalable
  • 28. 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
  • 29. 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
  • 30. 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
  • 31. 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
  • 32. 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
  • 33. 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
  • 34. 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
  • 35. Better API’s GET /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'}, ] }
  • 37. Better API’s GET /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'}, ] }
  • 38. Better API’s GET /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!!!
  • 39. Better API’s def index if stale?(SizeRun.count) respond_with SizeRun.all end end Automatic Etag/last_modified generation!!! Rails to the rescue!
  • 40. Better API’s Rails to the rescue! First load Following loads 304 Not Modified
  • 41. Better API’s Counter 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 } } ]}
  • 42. Better API’s Counter Caches class InventoryUnit < ActiveRecord::Base belongs_to :variant, :counter_cache => :inventory_units_on_hand_count, :conditions => "status = ‘on_hand’" end
  • 44. Solr: SolrTM is the popular, blazing fast open source enterprise search platform from the Apache LuceneTMproject. Its major features include powerful full-text search, hit highlighting, faceted search, near real-time indexing, dynamic clustering, database integration, rich document (e.g., Word, PDF) handling, and geospatial search.
  • 45. Sunspot - Solr • Solr powerful interface • Expressive DSL • Easily pluggable in to any ORM
  • 46. Sunspot - Solr class Post < ActiveRecord::Base searchable do text :title, :body text :comments do comments.map { |comment| comment.body } end boolean :featured integer :blog_id integer :expensive_operation integer :category_ids, :multiple => true double :average_rating time :published_at time :expired_at string :sort_title do title.downcase.gsub(/^(an?|the)/, '') end end end
  • 47. Sunspot - Solr class Post < ActiveRecord::Base searchable do text :title, :body text :comments do comments.map { |comment| comment.body } end boolean :featured integer :blog_id integer :expensive_operation integer :category_ids, :multiple => true double :average_rating time :published_at time :expired_at string :sort_title do title.downcase.gsub(/^(an?|the)/, '') end end end
  • 48. Sunspot - Solr class Post < ActiveRecord::Base searchable(auto_index: false) do text :title, :body text :comments do comments.map { |comment| comment.body } end boolean :featured integer :blog_id integer :expensive_operation integer :category_ids, :multiple => true double :average_rating time :published_at time :expired_at string :sort_title do title.downcase.gsub(/^(an?|the)/, '') end end end
  • 49. Sunspot - Solr Post.search do fulltext 'best pizza' with :blog_id, 1 with(:published_at).less_than Time.now order_by :published_at, :desc paginate :page => 2, :per_page => 15 facet :category_ids, :author_id end
  • 50. Sunspot - Solr • Results • Contains AR instances • ‘Lazy’ loading • Hits • Solr indexed data • super fast access results vs hits
  • 51. Sunspot - Solr results vs hits class ResultProxy < ResultHit def method_missing(method, *args, &block) begin hit.instance.send(method) rescue hit.stored(method) end end end
  • 52. Sunspot - Solr results vs hits class ResultProxy < ResultHit def method_missing(method, *args, &block) begin if setup.stored_fields(method).empty? hit.instance.send(method) else hit.stored(method) end rescue hit.instance.send(sym, *args) end end end
  • 53. Sunspot - Solr results vs hits class ResultProxy < ResultHit def value(of) hit.stored(of) end def has_property? name !setup.stored_fields(name).empty? end end
  • 54. Sunspot - Solr Associations? def method_missing method, *args @solr_instance ||= Search::Collection.new(Program.search { with(:id, id)}).first if @solr_instance && @solr_instance.has_property?(method) @solr_instance.value(method) else super method, *args end end $ model_active_record = Model.find(id) $ model_active_record.association.value_stored_in_solr
  • 56. Serializers • ActiveModel::Serializer • Rabl • JSON Builder • render json: hash
  • 57. Serializers • ActiveModel::Serializer <= no yet,WIP • Rabl • JSON Builder • render json: hash
  • 58. Serializers • ActiveModel::Serializer • Rabl • JSON Builder <= Good enough • render json: hash
  • 59. Serializers • ActiveModel::Serializer • Rabl • JSON Builder • render json: hash <= Hipster
  • 60. Serializers • ActiveModel::Serializer •Rabl <= Good enough with cache • JSON Builder • render json: hash
  • 61. 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
  • 62. 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 No ideal for real world
  • 64. Serializers #index.rabl object false node(:style_search) do [{ page: @search_results.results.current_page, total_pages: @search_results.results.total_pages, per_page: @search_results.results.per_page, }] end node :products do @search_results.results end node :suppliers do @suppliers end node :properties do @properties end GET /api/products #List Ideal for real world
  • 65. EmberJS A javascript framework for creating ambitious web applications.
  • 66. Modern Web 2.0 Problems • Data binding • Real time updates • Performance • Patterns • Scalability
  • 67. Modern Web 2.0 Problems Logo Login Info Profile Info Login Info Menu List of items with edit options FooterFooterFooter
  • 68. Modern Web 2.0 Problems Logo Currently selected Login Info Profile Info Login Info Menu List of items with edit options TopViewed FooterFooterFooter
  • 69. EmberJS • Model • Router • Controller • View • Template
  • 70. EmberJS - Models • Handled by Ember Data • Library to manage remote data • Acts as ‘ORM’ • Transactional
  • 71. EmberJS - Models MyApp.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}, ]
  • 72. EmberJS - Observers • .property • computed properties • .observes • reacts when something happens • bindings • ensure objects in multiple places are in
  • 73. EmberJS - Observes MyApp.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, { .... .... }
  • 74. EmberJS - Models/ bindings • isLoaded • isReloading • isDirty • isSaving • isDeleted • isError • isNew • isValid
  • 75. EmberJS - Models/ callbacks • didLoad • didReload • didUpdate • didCreate • didDelete • becameInvalid • becameError
  • 76. EmberJS - Models/ callbacks var product = MyApp.Product.createRecord(); product.on('didCreate', this, function() { console.log(‘Product created’); });
  • 77. EmberJS - Models var MyAppAdapter = DS.RESTAdapter.extend(); MyAppAdapter.configure('MyApp.ProductSearch', {sideloadAs: 'products'}); MyAppAdapter.configure('MyApp.Properties', {sideloadAs: 'properties'}); MyAppAdapter.configure('MyApp.Supplier', {sideloadAs: 'suppliers'});
  • 78. EmberJS - Models MyApp.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') })
  • 79. EmberJS - Models/ transactions var transaction = this.get(‘store’).transaction(); var product = transaction.createRecord(MyApp.Product, {}); product.on('didCreate', this, function() { console.log(‘program created’); }); transaction.commit();
  • 81. 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
  • 82. EmberJS - Router App.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'}); }); });
  • 83. EmberJS - Router App.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'}); }); });
  • 85. 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
  • 86. EmberJS - Controllers App.ApplicationController = Ember.Controller.extend({ // the initial value of the `search` property search: '', query: function() { // the current value of the text field var query = this.get('search'); this.transitionToRoute('search', { query: query }); } });
  • 87. Route
  • 88. EmberJS - Route App.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’); } });
  • 89. Views
  • 90. EmberJS -Views • Barely used, because of handlebars • Sophisticated user events • Create re-usable components
  • 91. EmberJS -Views var view = Ember.View.create({ templateName: 'say-hello', name: "Bob" }); view.appendTo('#container');
  • 92. EmberJS -Views/Events MyApp.UpVoteView = Ember.View.extend({ tagName: 'span', title: 'Upvote', templateName: 'common/upvote', click: function(evt) { this.get('controller').send('upVote'); } });
  • 94. EmberJS - Templates • Handlebars is used as template engine • regular HTML with embedded handlebar expressions • Minimal Templating on Steroids • Emblem.js if you are haml lover
  • 99. EmberJS MyApp = Ember.Application.create(); var MyAppAdapter = DS.RESTAdapter.extend(); MyApp.Store = DS.Store.extend({ revision: 12, adapter: 'MyAppAdapter' });
  • 100. EmberJS $ application.handlebars <header> <h1>MagmaConf Talk!</h1> </header> <div> {{outlet}} </div> {{ outlet modal }} <footer> &copy;2013 Crowd Interactive. </footer>
  • 101. EmberJS $ application.handlebars <header> <h1>MagmaConf Talk!</h1> </header> <div> {{outlet}} </div> {{ outlet modal }} <footer> &copy;2013 Crowd Interactive. </footer>
  • 102. 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}}
  • 103. 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}}
  • 104. EmberJS MyApp.ProductRowView = Ember.View.extend({ templateName:'products/row', tagName:'tr', click:function(evt) { router = this.get('controller.target.router'); router.transitionTo('product.index', this.product); } });
  • 105. EmberJS $ products/row.handlebars {{#with product}} <td><img {{bindAttr src="primaryImageUrl"}} {{ bindAttr alt="name" }}></td> <td>{{name}}</td> <td>{{supplierNames}}</td> {{/with}}
  • 106. EmberJS $ products/row.handlebars {{#with product}} <td> <img {{bindAttr src="primaryImageUrl"}} {{ bindAttr alt="name" }}> </td> <td>{{name}}</td> <td>{{supplierNames}}</td> {{/with}}
  • 107. 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!
  • 108. 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>
  • 109. 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>
  • 110. 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"}}
  • 111. 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"}}
  • 112. EmberJS MyApp.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(); } });
  • 113. EmberJS • Enumerables • forEach, map, mapProperty, filter, • ContainerView • String functions • camelize, capitalize, classify, dasherize, underscore, etc! • View Components • TextArea,TextField, CheckBox • etc
  • 114. EmberJS Hopefully I didn’t confuse you... more