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

Rails Holy Grail

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Rails Holy Grail

Is app/views the worst part of your codebase? Have you ever told someone on your team “remember to update the client-side views too”? Too long has the node.js community touted their advantage of using the same code on the client and the server. It’s time that Rails got a few punches in.

We should think of views as objects, not template files. In this talk I show how that lets us tease apart presentation from data, and build logic-less templates that are shared between client and server.

I gave this talk at RailsConf 2014. Since I used reveal.js to make the presentation, the PDF version looks a bit janky (and you lose the ability to play around with railsgenius if you're paging through it!) You can check out a better version of this presentation running on github.com: http://a-warner.github.io/rails-holy-grail

Avatar for Andrew Warner

Andrew Warner

April 22, 2014
Tweet

Other Decks in Programming

Transcript

  1. Rails Holy Grail Where did the OO go? Views should

    be objects too! Andrew Warner ( ) @wwarner Rap Genius ( ) @RapGenius
  2. In this talk! 1. What is the holy grail? 2.

    Existing Rails solutions 3. A new solution (for your consideration!) 4. What else does this new solution give us?
  3. The Holy Grail of the web Best possible user experience

    Best possible developer experience
  4. The Holy Grail of the web Best possible user experience—

    Single page app / "Thick" client 1. No full page reloads 2. Feels like a native desktop or mobile app
  5. The Holy Grail of the web Best possible developer experience

    1. Developer friendly framework– Ruby/Rails! 2. DRY (don't repeat yourself) – DRV 3. Write (mostly) one language 4. SEO friendly
  6. Airbnb Rendr Write backbone js client side code Client incrementally

    updates page Server renders full html pages for deep links
  7. Rendr / Node.js DRY (no duplicate views) ✓ SEO Friendly

    ✓ "Thick" client ✓ Mostly write one language ✓ Rails? X
  8. In this talk! 1. What is the holy grail? 2.

    Existing Rails solutions 3. A new solution (for your consideration!) 4. What else does this new solution give us?
  9. Duplicate code on client and server Write ERB/HAML views on

    the server Server has an API for the client Client side keeps mustache or underscore js templates
  10. Rendr / Node.js Duplicate Code DRY (no duplicate views) ✓

    X SEO Friendly ✓ ✓ "Thick" client ✓ ✓ Mostly write one language ✓ X Rails? X ½
  11. Turbolinks Write just server-side HAML/ERB views Pop in new pages

    without losing the browser instance Client is extremely simple That's it! Nowhere else to go
  12. Rendr / Node.js Duplicate Code Turbolinks DRY (no duplicate views)

    ✓ X ✓ SEO Friendly ✓ ✓ ✓ "Thick" client ✓ ✓ X Mostly write one language ✓ X ✓ Rails? X ½ ✓
  13. Ember/Angular/Backbone Server has an API for the client Entire "app"

    experience lives on client Initial page load downloads entire JS "app", builds page "New" twitter – 10 seconds to load a 140 character tweet!
  14. Rendr / Node.js Duplicate Code Turbolinks Ember / Angular /

    Backbone DRY (no duplicate views) ✓ X ✓ ✓ SEO Friendly ✓ ✓ ✓ X "Thick" client ✓ ✓ X ✓ Mostly write one language ✓ X ✓ X Rails? X ½ ✓ ½ Each of these makes key tradeoffs!
  15. In this talk! 1. What is the holy grail? 2.

    Existing Rails solutions 3. A new solution (for your consideration!) 4. What else does this new solution give us?
  16. Perspectives Rendr / Node.js Duplicate Code Turbolinks Ember / Angular

    / Backbone DRY (no duplicate views) ✓ ✓ X ✓ ✓ SEO Friendly ✓ ✓ ✓ ✓ X "Thick" client ✓ ✓ ✓ X ✓ Mostly write one language ✓ ✓ X ✓ X Rails? ✓ X ½ ✓ ½
  17. Same template on the client/server? <%= link_to user.name, user_url(user) %>

    <%= link_to "Quote of the day", ActiveRecord::Base.connection.execute(<<-SQL).first['url'] SELECT url FROM quotes_of_the_day ORDER BY Random() LIMIT SQL %>
  18. Mustache <blockquote> {{referent}} </blockquote> {{{body}}} {{#edit}} <a href="{{edit_href}}">Edit</a> {{/edit}} {

    referent: "node.js", body: '<a href="http://nodejs.org/">node.js</a> is a pla edit: true, edit_href: '/talks/1/annotations/3/edit' }
  19. Mustache – "tags" { ... body: '<a href="http://nodejs.org/">node.js</a> is a

    pla ... } {{{body}}} '<a href="http://nodejs.org/">node.js</a> is a platform...'
  20. Mustache – "tags" { ... edit: true, edit_href: '/talks/1/annotations/3/edit' }

    {{#edit}} <a href="{{edit_href}}">Edit</a> {{/edit}} '<a href="/talks/1/annotations/3/edit">Edit</a>'
  21. How does this help? <blockquote> {{referent}} </blockquote> {{{body}}} {{#edit}} <a

    href="{{edit_href}}">Edit</a> {{/edit}} { referent: "node.js", body: '<a href="http://nodejs.org/">node.js</a> is a pla edit: true, edit_href: '/talks/1/annotations/3/edit' }
  22. Perspective class Annotations::Show < Perspectives::Base param :annotation # param =

    input property(:referent) { annotation.referent } property(:body) { annotation.body_as_html } # property = output property(:edit) do annotation.created_by == current_user || current_user.admin? end property(:edit_href) { edit_annotation_path(annotation) } end
  23. Perspective class Annotations::Show < Perspectives::Base property(:referent) { annotation.referent } property(:body)

    { annotation.body_as_html } # ... end { referent: "node.js", body: '<a href="http://nodejs.org/">node.js</a> i edit: true, edit_href: '/talks/1/annotations/3/edit' }
  24. Getting started! Add one line to your controller class AnnotationsController

    < ApplicationController def show annotation = Annotation.find(params[:id]) respond_with(perspective('annotations/show', annotation: annotation)) end end
  25. In this talk! 1. What is the holy grail? 2.

    Existing Rails solutions 3. A new solution (for your consideration!) 4. What else does this new solution give us?
  26. ERB Version <blockquote> <%= @annotation.referent %> </blockquote> <%= @annotation.body_as_html %>

    <% if @annotation.created_by == current_user || current_user.admi <%= link_to 'Edit', edit_annotation_path(@annotation) %> <% end %>
  27. Separation of concerns ... {{#edit}} <a href="{{edit_href}}">Edit</a> {{/edit}} class Annotations::Show

    < Perspectives::Base # ... property(:edit) do annotation.created_by == current_user || current_user. end # ... end
  28. # spec/perspectives/annotations/show_spec.rb require 'spec_helper' describe Annotations::Show do it 'should be

    easy to test!' do user = double(:user) annotation = double(:annotation, created_by: user) annotations_show = described_class.new( {current_user: user}, annotation: annotation) annotations_show.edit.should be_true end end
  29. Caching class Annotations::Show < Perspectives::Base param :annotation cache { annotation

    } # cache_key: # "annotations/1-20140404025902750005000" end
  30. Caching class Annotations::Show < Perspectives::Base param :annotation cache { annotation

    } delegate :created_by, to: :annotation nested 'users/avatar', user: :created_by end
  31. Caching class Users::Avatar < Perspectives::Base param :user cache { user

    } # cache_key: # "users/4-20140420203350976646000" end
  32. Caching class Annotations::Show < Perspectives::Base param :annotation cache { annotation

    } delegate :created_by, to: :annotation nested 'users/avatar', user: :created_by end
  33. Caching class Annotations::Show < Perspectives::Base cache { annotation } nested

    'users/avatar', user: :created_by # cache_key: # "annotations/1-20140404025902750005000" # + # "users/4-20140420203350976646000" # = end "annotations/1-20140404025902750005000/users/4- 20140420203350976646000"
  34. Drive off into the sunset? Nascent library / weekend project

    Still a bunch of stuff to be done! Answer to these Node.js libraries Key takeaway: share views in thick client world