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

Brian Mann Backbone + Rails - Journey to the F...

Brian Mann
February 13, 2013

Brian Mann Backbone + Rails - Journey to the Front End [Atlanta Ruby Meetup]

I gave this talk Feb 13th, 2013 at the Atlanta Ruby Meetup - covering Rails + Backbone.js

In this presentation I'll cover how well Ruby on Rails + Backbone work together. There are a lot of misconceptions when moving logic to the client, and how that changes the backend.

Rails has a lot of tools that make this a breeze, and with the right setup, working with javascript templates, using Rails view helpers, and generating JSON is super simple.

Rails comes with everything you need to deploy to production, minify, concatenate, and obfuscate all your Javascript files, and even load them in the proper order via the Asset Pipeline.

See videos and screencasts at:
http://www.backbonerails.com

Brian Mann

February 13, 2013
Tweet

More Decks by Brian Mann

Other Decks in Programming

Transcript

  1. Backbone + Rails: Journey to the Front End • Came

    from MVC PHP :-( ‣ CodeIgniter • Built internal CRM’s • Used jQuery / AJAX for front end logic ‣ Spaghetti code ‣ One giant file ‣ Perplexing... never felt right • Started Ruby on Rails - late 2011 Looking Back before 2011
  2. Backbone + Rails: Journey to the Front End • +

    Give logical structure to the DOM • + Separate behavior, presentation, and implementation • + Automatically keep view changes in sync • + Prevent jQuery spaghetti code callbacks What solves this problem?
  3. Backbone + Rails: Journey to the Front End • +

    Give logical structure to the DOM • + Separate behavior, presentation, and implementation • + Automatically keep view changes in sync • + Prevent jQuery spaghetti code callbacks What solves this problem? = Javascript MVC Frameworks
  4. Backbone + Rails: Journey to the Front End • What’s

    the best Framework? • What are the differences between... ‣ Backbone JS ‣ Ember JS ‣ Knockout JS ‣ ... Spine ... Batman ... Angular ... Difficult Questions Jan 2012
  5. Backbone + Rails: Journey to the Front End • Highly

    successful production-ready track record ‣ Major players using it successfully • Non-intimidating documentation • Seemed to be built for Rails out of the box ‣ REST API - No Configuration • Awesome community support ‣ Lots of tutorials ‣ Stack Overflow Why I Chose Backbone
  6. Backbone + Rails: Journey to the Front End • Introduces

    the concepts of Client Side MVC ‣ View - managing particular DOM node (template) ‣ Models / Collections - storing state of entities ‣ Router - Interprets client side URL / fires events ‣ Events - communication mechanism between components • The Ramifications ‣ No more mixed presentation and behavior ‣ You’re in control of everything, all interactions ‣ Enough basic structure to build widgets / small SPA’s How Backbone Works
  7. [{ id: 1, name: "Doctors Appt", where: "Dentist", date: "02-02-2013",

    time_start: "2:00pm", time_end: "3:00pm" },{ id: 2, name: "Ruby Meetup", where: "Centergy Bld", date: "02-13-2013", time_start: "7:00pm", time_end: "9:00pm" },{ id: 3, name: where: date: "02-14-2013", time_start: time_end: },{ id: 4, name: "Visit Vet", where: "Avian Center", date: "02-24-2013", time_start: "10:00am", time_end: "11:00am" }] "Buy Flowers!", null, null, null Event
  8. [{ id: 1, name: "Doctors Appt", where: "Dentist", date: "02-02-2013",

    time_start: "2:00pm", time_end: "3:00pm" },{ id: 2, name: "Ruby Meetup", where: "Centergy Bld", date: "02-13-2013", time_start: "7:00pm", time_end: "9:00pm" },{ id: 3, name: where: date: "02-14-2013", time_start: time_end: },{ id: 4, name: "Visit Vet", where: "Avian Center", date: "02-24-2013", time_start: "10:00am", time_end: "11:00am" }] Event
  9. [{ id: 1, name: "Doctors Appt", where: "Dentist", date: "02-02-2013",

    time_start: "2:00pm", time_end: "3:00pm" },{ id: 2, name: "Ruby Meetup", where: "Centergy Bld", date: "02-13-2013", time_start: "7:00pm", time_end: "9:00pm" },{ id: 3, name: "Chocolate", where: "Walmart", date: "02-14-2013", time_start: "5:00pm", time_end: "6:00pm" },{ id: 4, name: "Visit Vet", where: "Avian Center", date: "02-24-2013", time_start: "10:00am", time_end: "11:00am" }] Event
  10. [{ id: 1, name: "Doctors Appt", where: "Dentist", date: "02-02-2013",

    time_start: "2:00pm", time_end: "3:00pm" },{ id: 2, name: "Ruby Meetup", where: "Centergy Bld", date: "02-13-2013", time_start: "7:00pm", time_end: "9:00pm" },{ id: 3, name: "Chocolate", where: "Walmart", date: "02-14-2013", time_start: "5:00pm", time_end: "6:00pm" },{ id: 4, name: "Visit Vet", where: "Avian Center", date: "02-24-2013", time_start: "10:00am", time_end: "11:00am" }] Event
  11. [{ id: 1, name: "Doctors Appt", where: "Dentist", date: "02-02-2013",

    time_start: "2:00pm", time_end: "3:00pm" },{ id: 2, name: "Ruby Meetup", where: "Centergy Bld", date: "02-13-2013", time_start: "7:00pm", time_end: "9:00pm" },{ id: 3, name: "Chocolate", where: "Walmart", date: "02-14-2013", time_start: "5:00pm", time_end: "6:00pm" },{ id: 4, name: "Visit Vet", where: "Avian Center", date: "02-24-2013", time_start: "10:00am", time_end: "11:00am" }] Event
  12. Server Client M V JSON JSON C M C V

    <div id="header-region"></div> <div id="main-region"></div> <div id="footer-region"></div> <%= javascript_tag do %> $(function() { App.start(); }); <% end %>
  13. Backbone + Rails: Journey to the Front End • Asset

    Pipeline ! • No more awesome helpers right? ‣ wrong. • Routes.rb / I18n / Controller Instance Vars • Precompiled JST • ActiveRecord / Controllers / Gems Do you still need Rails?
  14. { "id": 1, "picture": "images/user_1.png", "age": 70, "name": "Stanley Kubrick",

    "gender": "male", "company": "MGM", "phone": "832-547-3983", "email": "[email protected]", "address": "Hertfordshire England", "friends": [ { "id": 1, "name": "Malcolm McDowell" }, { "id": 2, "name": "Arthur C. Clarke" }, { "id": 3, "name": "Peter Sellers" } ] }
  15. <div id=”user”>Hello, Stanley Kubrick</div> { "id": 1, "picture": "images/user_1.png", "age":

    70, "name": "Stanley Kubrick", "gender": "male", "company": "MGM", "phone": "832-547-3983", "email": "[email protected]", "address": "Hertfordshire England", "friends": [ { "id": 1, "name": "Malcolm McDowell" }, { "id": 2, "name": "Arthur C. Clarke" }, { "id": 3, "name": "Peter Sellers" } ] }
  16. <div id=”user”>Hello, Stanley Kubrick</div> <a href=”mailto:[email protected]”> Email Stanley </a> {

    "id": 1, "picture": "images/user_1.png", "age": 70, "name": "Stanley Kubrick", "gender": "male", "company": "MGM", "phone": "832-547-3983", "email": "[email protected]", "address": "Hertfordshire England", "friends": [ { "id": 1, "name": "Malcolm McDowell" }, { "id": 2, "name": "Arthur C. Clarke" }, { "id": 3, "name": "Peter Sellers" } ] }
  17. <div id=”user”>Hello, Stanley Kubrick</div> <a href=”mailto:[email protected]”> Email Stanley </a> <div

    id=”friends”> <span id=”count”>You have 3 friends:</span> <ul> <li>Malcolm McDowell</li> <li>Arthur C. Clarke</li> <li>Peter Sellers</li> </ul> </div> { "id": 1, "picture": "images/user_1.png", "age": 70, "name": "Stanley Kubrick", "gender": "male", "company": "MGM", "phone": "832-547-3983", "email": "[email protected]", "address": "Hertfordshire England", "friends": [ { "id": 1, "name": "Malcolm McDowell" }, { "id": 2, "name": "Arthur C. Clarke" }, { "id": 3, "name": "Peter Sellers" } ] }
  18. https://github.com/nesquena/rabl ## models/user.rb class User < ActiveRecord::Base has_many :posts attr_accessible

    :first_name, :last_name def full_name "#{first_name} #{last_name}" end end Rabl
  19. https://github.com/nesquena/rabl ## models/user.rb class User < ActiveRecord::Base has_many :posts attr_accessible

    :first_name, :last_name def full_name "#{first_name} #{last_name}" end end Rabl ## views/users/index.json.rabl collection @users attributes :id, :first_name, :last_name, :full_name, :date_created node do |user| { :date_created_formatted => user.date_created.to_s(:db), :date_created_ago => time_ago_in_words(user.date_created) } end child :posts do attributes :id, :title, :body end
  20. https://github.com/nesquena/rabl ## models/user.rb class User < ActiveRecord::Base has_many :posts attr_accessible

    :first_name, :last_name def full_name "#{first_name} #{last_name}" end end Rabl ## views/users/index.json.rabl collection @users attributes :id, :first_name, :last_name, :full_name, :date_created node do |user| { :date_created_formatted => user.date_created.to_s(:db), :date_created_ago => time_ago_in_words(user.date_created) } end child :posts do attributes :id, :title, :body end [{ id: 1, first_name: "Brian", last_name: "Mann", full_name: "Brian Mann", date_created: "2013-02-02T18:35:35.511", date_created_formatted: "2013-02-02 1:35:35", date_created_ago: "about 2 weeks", posts: [{ id: 100, title: "BackboneRails Released!", body: "go download them.", }] },{ id: 2, first_name: "Jennifer", last_name: "Shehane", full_name: "Jennifer Shehane", date_created: "2013-02-10T22:13:46.245", date_created_formatted: "2013-02-10 5:13:46", date_created_ago: "3 days", posts: [{ id: 101, title: "You misspelled ‘tenants’ in Ep02", body: "should be ‘tenets’", },{ id: 102, title: "Uhhh.....", body: "It is misspelled on every single slide :D", }] }]
  21. 1. Dependency Management //= require jquery //= require lib/underscore //=

    require lib/backbone //= require lib/marionette //= require_tree ./backbone/config //= require backbone/app //= require_tree ./backbone/entities //= require_tree ./backbone/mixins //= require_tree ./backbone/views //= require_tree ./backbone/components //= require_tree ./backbone/apps
  22. 1. Dependency Management //= require jquery //= require lib/underscore //=

    require lib/backbone //= require lib/marionette //= require_tree ./backbone/config //= require backbone/app //= require_tree ./backbone/entities //= require_tree ./backbone/mixins //= require_tree ./backbone/views //= require_tree ./backbone/components //= require_tree ./backbone/apps
  23. 1. Dependency Management //= require jquery //= require lib/underscore //=

    require lib/backbone //= require lib/marionette //= require_tree ./backbone/config //= require backbone/app //= require_tree ./backbone/entities //= require_tree ./backbone/mixins //= require_tree ./backbone/views //= require_tree ./backbone/components //= require_tree ./backbone/apps
  24. 1. Dependency Management //= require jquery //= require lib/underscore //=

    require lib/backbone //= require lib/marionette //= require_tree ./backbone/config //= require backbone/app //= require_tree ./backbone/entities //= require_tree ./backbone/mixins //= require_tree ./backbone/views //= require_tree ./backbone/components //= require_tree ./backbone/apps
  25. 1. Dependency Management //= require jquery //= require lib/underscore //=

    require lib/backbone //= require lib/marionette //= require_tree ./backbone/config //= require backbone/app //= require_tree ./backbone/entities //= require_tree ./backbone/mixins //= require_tree ./backbone/views //= require_tree ./backbone/components //= require_tree ./backbone/apps
  26. 2. Concatenation Development <script src="/assets/jquery.js?body=1" type="text/javascript"></script> <script src="/assets/lib/underscore.js?body=1" type="text/javascript"></script> <script

    src="/assets/lib/backbone.js?body=1" type="text/javascript"></script> <script src="/assets/lib/marionette.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/config/marionette/renderer.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/app.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/entities/_base/collections.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/entities/_base/models.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/entities/header.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/apps/footer/footer_app.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/apps/footer/show/show_controller.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/apps/footer/show/show_view.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/apps/header/header_app.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/apps/header/list/list_controller.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/apps/header/list/list_view.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/apps/header/list/templates/_header.js?body=1" type="text/javascript"></script> <script src="/assets/backbone/apps/header/list/templates/headers.js?body=1" type="text/javascript"></script> <script src="/assets/application.js?body=1" type="text/javascript"></script>
  27. 3. Minification / Obfuscation (function(e,t){function P(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:e.nodeType===1&&t?!0:n==="array"||n!=="function"&&(t===0||typeof t=="number"&&t>0&&t-1 in

    e)}function B(e){var t=H[e]={};return b.each(e.match(E)||[],function(e,n){t[n]=!0}),t}function I(e,n,r,i){if(!b.acceptData(e))return;var s,o,u=b.expando,a=typeof n=="string",f=e.nodeType,c=f?b.cache:e,h=f?e[u]:e[u]&&u;if((!h||!c[h]||!i&&!c[h].data)&&a&&r===t)return;h||(f?e[u]=h=l.pop()||b.guid+ +:h=u),c[h]||(c[h]={},f||(c[h].toJSON=b.noop));if(typeof n=="object"||typeof n=="function")i?c[h]=b.extend(c[h],n):c[h].data=b.extend(c[h].data,n);return s=c[h],i|| (s.data||(s.data={}),s=s.data),r!==t&&(s[b.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[b.camelCase(n)])):o=s,o}function q(e,t,n){if(!b.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?b.cache:e,a=o?e[b.expando]:b.expando;if(!u[a])return;if(t){s=n?u[a]:u[a].data;if(s){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in s?t=[t]: (t=b.camelCase(t),t in s?t=[t]:t=t.split(" "));for(r=0,i=t.length;r<i;r++)delete s[t[r]];if(!(n?U:b.isEmptyObject)(s))return}}if(!n){delete u[a].data;if(!U(u[a]))return}o? b.cleanData([e],!0):b.support.deleteExpando||u!=u.window?delete u[a]:u[a]=null}function R(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(F,"- $1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:j.test(r)?b.parseJSON(r):r}catch(s){} b.data(e,n,r)}else r=t}return r}function U(e){var t;for(t in e){if(t==="data"&&b.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function it(){return!0} function st(){return!1}function ct(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ht(e,t,n){t=t||0;if(b.isFunction(t))return b.grep(e,function(e,r){var i=!! t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if(typeof t=="string"){var r=b.grep(e,function(e){return e.nodeType===1});if(at.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.inArray(e,t)>=0===n})}function pt(e){var t=dt.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Mt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function _t(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified) +"/"+e.type,e}function Dt(e){var t=Ct.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Pt(e,t){var n,r=0;for(;(n=e[r])!=null;r+ +)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function Ht(e,t){if(t.nodeType!==1||!b.hasData(e))return;var n,r,i,s=b._data(e),o=b._data(t,s),u=s.events;if(u) {delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r<i;r++)b.event.add(t,n,u[n][r])}o.data&&(o.data=b.extend({},o.data))}function Bt(e,t){var n,r,i;if(t.nodeType!==1)return;n=t.nodeName.toLowerCase();if(!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}if(n==="script"&&t.text!==e.text)_t(t).text=e.text,Dt(t);else if(n==="object")t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML);else if(n==="input"&&xt.test(e.type))t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value);else if(n==="option")t.defaultSelected=t.selected=e.defaultSelected;else if(n==="input"||n==="textarea")t.defaultValue=e.defaultValue}function jt(e,n){var r,s,o=0,u=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!u)for(u=[],r=e.childNodes||e;(s=r[o])! =null;o++)!n||b.nodeName(s,n)?u.push(s):b.merge(u,jt(s,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],u):u}function Ft(e) {xt.test(e.type)&&(e.defaultChecked=e.checked)}function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--){t=en[i]+n;if(t in e)return t}return r}function nn(e,t){return e=t||e,b.css(e,"display")==="none"||!b.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,s=[],o=0,u=e.length;for(;o<u;o++){r=e[o];if(!r.style)continue;s[o]=b._data(r,"olddisplay"),n=r.style.display,t?(!
  28. 4. CoffeeScript @App.module "UsersApp.List", (List, App, Backbone, Marionette, $, _)

    -> List.Controller = listUsers: -> users = App.request "user:entities" @layout = @getLayoutView() @layout.on "show", => @showPanel users @listUsers users App.mainRegion.show @layout listUsers: (users) -> usersView = @getUsersView users usersView.on "itemview:edit:user", (iv, user) -> App.vent.trigger "edit:user", user @layout.tableRegion.show usersView getUsersView: (users) -> new List.Users collection: users getLayoutView: -> new List.Layout
  29. ## users/templates/show.jst.eco <%- @linkTo Routes.user_path(@id), Class: "button button-mini", -> %>

    <i class='icons-user'></i> Show User <% end %> ## users/show.html.erb <%= link_to user_path(@user), class: "button button-mini" do %> <i class='icons-user'></i> Show User <% end %>
  30. ## users/templates/show.jst.eco <%- @linkTo Routes.user_path(@id), Class: "button button-mini", -> %>

    <i class='icons-user'></i> Show User <% end %> ## users/show.html.erb <%= link_to user_path(@user), class: "button button-mini" do %> <i class='icons-user'></i> Show User <% end %> <!-- <a href="users/1" class="button button-mini"> <i class='icons-user'></i> Show User </a> -->
  31. Backbone + Rails: Journey to the Front End • Huge

    mental shi" / nuanced decisions ‣ When / how to load server resources ‣ Handling Routing w/Dialogs ‣ Complex nested layouts • Lack of Application Infrastructure ‣ Slow, Painful Development ‣ Bug Recursions ‣ Edge Cases ‣ Excess Boilerplate Technical Difficulties May 2012
  32. BACKBONE STACK MARIONETTE STACK VS Application Modules Layouts Regions Composite

    Views Collection Views Item Views Specialized Views Models Controllers Templates Messaging Bus App Router Routers Views Models Templates
  33. APPLICATION USERS LIST SHOW NEW APPOINTMENTS LIST SHOW NEW LEADS

    LIST SHOW NEW V E V E V E V E V E V E V E V E V E
  34. APPLICATION USERS LIST SHOW NEW APPOINTMENTS LIST SHOW NEW LEADS

    LIST SHOW NEW V E V E V E V E V E V E V E V E V E TEMPLATES
  35. APPLICATION USERS LIST SHOW NEW APPOINTMENTS LIST SHOW NEW LEADS

    LIST SHOW NEW V E V E V E V E V E V E V E V E V E T T T T T T T T T
  36. APPLICATION USERS LIST V E T SHOW V E T

    NEW V E T APPOINTMENTS LIST V E T SHOW V E T NEW V E T LEADS LIST V E T SHOW V E T NEW V E T
  37. Backbone + Rails: Journey to the Front End • Highly

    repeatable patterns • Scalable solutions ‣ file / folder organization ‣ inter-app communication ‣ hardly any procedural code ‣ mostly configuration ‣ CSS conventions • Began recording screencasts Mission Accomplished Dec 2012