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

as easy as rails

as easy as rails

My favorite part of Ruby on Rails was the code I didn't have to write. By setting a handful of conventions and providing a sensible default configuration, creating a new project or learning an existing one became easy. This talk challenges our assumptions about why everyone hates JavaScript and whether what's really missing is smarter development tools.

Justin Searls

February 08, 2014
Tweet

More Decks by Justin Searls

Other Decks in Programming

Transcript

  1. as easy
    as rails

    View full-size slide

  2. My name is Justin Searls
    Please tweet me @searls &
    Say [email protected]

    View full-size slide

  3. You own a bakery.

    View full-size slide

  4. Honest mistake.

    View full-size slide

  5. You own a software studio.

    View full-size slide

  6. Honest mistake.

    View full-size slide

  7. Rails makes it too easy.

    View full-size slide

  8. server-side tools

    View full-size slide

  9. client-side tools

    View full-size slide

  10. Just kidding.

    View full-size slide

  11. client-side tools

    View full-size slide

  12. Provocation:

    View full-size slide

  13. Non-rubyists
    are building better
    JavaScript apps
    Provocation:

    View full-size slide

  14. Before Rails
    was easy

    View full-size slide

  15. Before Rails
    was easy
    JavaScript
    wasn't hard

    View full-size slide

  16. Is JavaScript a
    terrible language?

    View full-size slide

  17. YES
    DEFINITELY

    View full-size slide

  18. Is that why writing
    JavaScript is terrible?

    View full-size slide

  19. ASK
    AGAIN
    LATER

    View full-size slide

  20. The community
    changed the world.

    View full-size slide

  21. Great new tools for
    the web, over time:

    View full-size slide

  22. Where are the best tools?

    View full-size slide

  23. Ruby's tool ecosystem is mature, crowded
    Where are the best tools?

    View full-size slide

  24. Ruby's tool ecosystem is mature, crowded
    Node's ecosystem is immature, innovative
    Where are the best tools?

    View full-size slide

  25. Ruby's tool ecosystem is mature, crowded
    Node's ecosystem is immature, innovative
    Tool authors aren't immune to trends
    Where are the best tools?

    View full-size slide

  26. Ruby's tool ecosystem is mature, crowded
    Node's ecosystem is immature, innovative
    Tool authors aren't immune to trends
    Tools address the problems of their day
    Where are the best tools?

    View full-size slide

  27. Ruby's tool ecosystem is mature, crowded
    Node's ecosystem is immature, innovative
    Tool authors aren't immune to trends
    Tools address the problems of their day
    Where are the best tools?

    View full-size slide

  28. Ruby's tool ecosystem is mature, crowded
    Node's ecosystem is immature, innovative
    Tool authors aren't immune to trends
    Tools address the problems of their day
    Node's web app tooling better
    solves today's problems
    Where are the best tools?

    View full-size slide

  29. Rails won the war.

    View full-size slide

  30. Rails won the war?
    which

    View full-size slide

  31. "Good frameworks
    are extractions,
    not inventions."
    - DHH

    View full-size slide

  32. Routing
    Models

    View full-size slide

  33. Routing
    Models
    Persistence

    View full-size slide

  34. Routing
    Models
    Persistence
    Sessions

    View full-size slide

  35. Routing
    Models
    Persistence
    Sessions
    Mailers

    View full-size slide

  36. Routing
    Models
    Persistence
    Sessions
    Mailers
    JavaScript
    Alternatives

    View full-size slide

  37. Routing
    Models
    Persistence
    Sessions
    Mailers
    AJAX ERB
    Tags

    View full-size slide

  38. Routing
    Models
    Persistence
    Sessions
    Mailers
    RJS

    View full-size slide

  39. Routing
    Models
    Persistence
    Sessions
    Mailers
    AJAX ERB
    Tags
    Unobtrusive

    View full-size slide

  40. Routing
    Models
    Persistence
    Sessions
    Mailers
    Turbolinks

    View full-size slide

  41. HTML UI
    !
    Item 1

    View full-size slide

  42. HTML UI
    !
    Item 1
    !



    View full-size slide

  43. JavaScript UI

    View full-size slide

  44. JavaScript UI
    !
    new ItemView('#items').render()

    View full-size slide

  45. JavaScript UI
    !
    new ItemView('#items').render()
    !
    $('button').click(function(e){
    $('.alert').text('Thanks!').show()
    });

    View full-size slide

  46. HTML UI
    !
    app
    ├── controllers
    │ └── items_controller.rb
    ├── models
    │ └── item.rb
    └── views
    └── items
    └── index.html.erb

    View full-size slide

  47. HTML UI
    !
    app
    ├── controllers
    │ └── items_controller.rb
    ├── models
    │ └── item.rb
    ├── views
    │ └── items
    │ └── index.html.erb
    └── assets
    └── javascripts
    └── application.js

    View full-size slide

  48. HTML UI + JS UI
    !
    app
    ├── controllers
    │ └── items_controller.rb
    ├── models
    │ └── item.rb
    ├── views
    │ └── items
    │ └── index.html.erb
    └── assets
    └── javascripts
    └── more_app
    ├── controllers
    │ └── items_controller.js
    ├── models
    │ └── item.js
    └── views
    └── items
    └── index.jst.ejs

    View full-size slide

  49. HTML UI + JS UI
    !
    app
    ├── controllers
    │ └── items_controller.rb
    ├── models
    │ └── item.rb
    ├── views
    │ └── items
    │ └── index.html.erb
    └── assets
    └── javascripts
    └── more_app
    ├── controllers
    │ └── items_controller.js
    ├── models
    │ └── item.js
    └── views
    └── items
    └── index.jst.ejs

    View full-size slide

  50. HTML UI + JS UI
    !
    app
    ├── controllers
    │ └── items_controller.rb
    ├── models
    │ └── item.rb
    ├── views
    │ └── items
    │ └── index.html.erb
    └── assets
    └── javascripts
    └── more_app
    ├── controllers
    │ └── items_controller.js
    ├── models
    │ └── item.js
    └── views
    └── items
    └── index.jst.ejs

    View full-size slide

  51. HTML UI + JS UI
    !
    app
    ├── controllers
    │ └── items_controller.rb
    ├── models
    │ └── item.rb
    ├── views
    │ └── items
    │ └── index.html.erb
    └── assets
    └── javascripts
    └── more_app
    ├── controllers
    │ └── items_controller.js
    ├── models
    │ └── item.js
    └── views
    └── items
    └── index.jst.ejs

    View full-size slide

  52. JSON API + JS UI
    !
    app
    ├── controllers
    │ └── items_controller.rb
    ├── models
    │ └── item.rb



    └── assets
    └── javascripts
    └── more_app
    ├── controllers
    │ └── items_controller.js
    ├── models
    │ └── item.js
    └── views
    └── items
    └── index.jst.ejs

    View full-size slide

  53. JSON API + JS UI
    !
    app
    ├── controllers
    │ └── items_controller.rb
    ├── models
    │ └── item.rb



    └── assets
    └── javascripts
    └── more_app
    ├── controllers
    │ └── items_controller.js
    ├── models
    │ └── item.js
    └── views
    └── items
    └── index.jst.ejs

    View full-size slide

  54. JSON API + JS UI
    !
    app
    ├── controllers
    │ └── items_controller.rb
    ├── models
    │ └── item.rb



    └── assets
    └── javascripts
    └── more_app
    ├── controllers
    │ └── items_controller.js
    ├── models
    │ └── item.js
    └── views
    └── items
    └── index.jst.ejs
    vestigial appendage

    View full-size slide

  55. what's wrong with
    that vestigial
    appendage?

    View full-size slide

  56. late extraction
    costs more than
    early abstraction

    View full-size slide

  57. If you see this:

    View full-size slide

  58. If you see this:
    !
    <br/>

    View full-size slide

  59. If you see this:
    !
    <br/>user = <%= user.to_json %><br/>

    View full-size slide

  60. If you see this:
    !
    <br/>user = <%= user.to_json %><br/>time = <%= Time.now.to_i %><br/>

    View full-size slide

  61. If you see this:
    !
    <br/>user = <%= user.to_json %><br/>time = <%= Time.now.to_i %><br/>items = <%= items.to_json %><br/>

    View full-size slide

  62. If you see this:
    !
    <br/>user = <%= user.to_json %><br/>time = <%= Time.now.to_i %><br/>items = <%= items.to_json %><br/>token = "<%= @token %>"<br/>

    View full-size slide

  63. !
    <br/>user = <%= user.to_json %><br/>time = <%= Time.now.to_i %><br/>items = <%= items.to_json %><br/>token = "<%= @token %>"<br/>
    Your yarn is tangled

    View full-size slide

  64. late extraction
    costs more than
    early abstraction

    View full-size slide

  65. JavaScript made easy.

    View full-size slide

  66. App Framework
    Convention & Config
    Build Automation

    View full-size slide

  67. Rails
    App Framework
    Convention & Config
    Build Automation

    View full-size slide

  68. Rails
    Rails
    App Framework
    Convention & Config
    Build Automation

    View full-size slide

  69. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation

    View full-size slide

  70. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation
    B-

    View full-size slide

  71. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation
    B-
    A+

    View full-size slide

  72. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation
    B+
    B-
    A+

    View full-size slide

  73. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation
    B+
    B-
    A+

    View full-size slide

  74. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation

    View full-size slide

  75. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation
    Backbone

    View full-size slide

  76. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation
    Backbone
    Ember

    View full-size slide

  77. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation
    Backbone
    Ember
    Angular

    View full-size slide

  78. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation
    Backbone
    Ember
    Angular
    ᵇ(´-ʆ)ᵃ


    View full-size slide

  79. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation Node.js (Grunt)
    Backbone
    Ember
    Angular
    ᵇ(´-ʆ)ᵃ


    View full-size slide

  80. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation Node.js (Grunt)
    ???
    Backbone
    Ember
    Angular
    ᵇ(´-ʆ)ᵃ


    View full-size slide

  81. Rails
    Rails
    Railsy Rake
    App Framework
    Convention & Config
    Build Automation Node.js (Grunt)
    Lineman
    Backbone
    Ember
    Angular
    ᵇ(´-ʆ)ᵃ


    View full-size slide

  82. @linemanjs
    linemanjs.com

    View full-size slide

  83. !
    $ npm install -g lineman
    Install

    View full-size slide

  84. !
    $ npm install -g lineman
    Install
    !
    $ lineman new my-app
    Create

    View full-size slide

  85. hi()
    code
    !
    save

    View full-size slide

  86. hi()
    code
    !
    save
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/
    tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    compile

    View full-size slide

  87. hi()
    code
    !
    save
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/
    tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    compile
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    concat

    View full-size slide

  88. hi()
    code
    !
    save
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/
    tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    compile
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    concat
    hello
    world!
    play

    View full-size slide

  89. < 100ms
    hi()
    code
    !
    save
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/
    tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    compile
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    concat
    hello
    world!
    play

    View full-size slide

  90. < 100ms
    hi()
    code
    !
    save
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/
    tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    compile
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    concat
    hello
    world!
    play

    View full-size slide

  91. hi()
    code
    !
    save
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/
    tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    compile
    loadTask task for task in npmTasks
    grunt.renameTask "copy", "images"
    loadTask "grunt-contrib-copy" # load again so webfonts can use
    it
    grunt.renameTask "copy", "webfonts"
    loadTask "grunt-contrib-copy" # load again to make available
    in userland
    loadTask = (module) ->
    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
    grunt.loadNpmTasks(module)
    else
    grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks")
    npmTasks = grunt.util._(linemanNpmTasks).chain().
    union("grunt-contrib-sass" if config.enableSass).
    union(config.loadNpmTasks).
    compact().value()
    concat test
    < 100ms

    View full-size slide

  92. Can your server
    host static files?

    View full-size slide

  93. !
    $ lineman build
    !

    View full-size slide

  94. !
    $ lineman build
    !
    $ tree dist

    View full-size slide

  95. !
    $ lineman build
    !
    $ tree dist
    !
    dist
    ├── css
    │ └── app.css
    ├── index.html
    └── js
    └── app.js

    View full-size slide

  96. !
    $ lineman build
    !
    $ tree dist
    !
    dist
    ├── assets.json
    ├── css
    │ └── app-6193b1f500c0f944db0354592.css
    ├── index.html
    └── js
    └── app-8b750b723eae24580f7d757f62.js

    View full-size slide

  97. !
    $ heroku config:set BUILDPACK_URL=http://github.com/
    testdouble/heroku-buildpack-lineman.git
    !
    $ git push
    Heroku

    View full-size slide

  98. !
    $ heroku config:set BUILDPACK_URL=http://github.com/
    testdouble/heroku-buildpack-lineman.git
    !
    $ git push
    Heroku
    Builds with Node.js

    View full-size slide

  99. !
    $ heroku config:set BUILDPACK_URL=http://github.com/
    testdouble/heroku-buildpack-lineman.git
    !
    $ git push
    Heroku
    Builds with Node.js
    Runs without Node.js

    View full-size slide

  100. Starter Projects

    View full-size slide

  101. angular
    Starter Projects

    View full-size slide

  102. backbone
    angular
    Starter Projects

    View full-size slide

  103. ember
    backbone
    angular
    Starter Projects

    View full-size slide

  104. ember
    backbone
    angular
    web libs
    Starter Projects

    View full-size slide

  105. ember
    backbone
    angular
    web libs markdown
    blogs
    Starter Projects

    View full-size slide

  106. easy extensions

    View full-size slide

  107. !
    $ npm install --save-dev lineman-bower
    !
    zero-config plugins

    View full-size slide

  108. !
    $ npm install --save-dev lineman-bower
    !
    $ lineman run
    !
    zero-config plugins

    View full-size slide

  109. !
    $ npm install --save-dev lineman-bower
    !
    $ lineman run
    !
    ...
    Running "bower:install" (bower) task
    >> Installed bower packages
    ...
    zero-config plugins

    View full-size slide

  110. }
    Make
    Cmake
    Rake
    Ant / NAnt
    MSBuild
    Maven
    Grunt

    View full-size slide

  111. grunt-contrib-clean
    grunt-contrib-coffee
    grunt-contrib-concat
    grunt-contrib-copy
    grunt-contrib-handlebars
    grunt-contrib-jshint
    grunt-contrib-jst
    grunt-contrib-less
    grunt-contrib-sass
    grunt-contrib-cssmin
    grunt-contrib-uglify
    grunt-watch-nospawn
    grunt-contrib-imagemin
    grunt-jasmine-bundle
    grunt-contrib-compass

    View full-size slide

  112. grunt-contrib-clean
    grunt-contrib-coffee
    grunt-contrib-concat
    grunt-contrib-copy
    grunt-contrib-handlebars
    grunt-contrib-jshint
    grunt-contrib-jst
    grunt-contrib-less
    grunt-contrib-sass
    grunt-contrib-cssmin
    grunt-contrib-uglify
    grunt-watch-nospawn
    grunt-contrib-imagemin
    grunt-jasmine-bundle
    grunt-contrib-compass
    grunt-*

    View full-size slide

  113. Client Server

    View full-size slide

  114. Client Server
    ?

    View full-size slide

  115. Client Server

    View full-size slide

  116. Client Server

    View full-size slide

  117. Client Server

    View full-size slide

  118. Lineman Sinatra

    View full-size slide

  119. Lineman Sinatra

    View full-size slide

  120. Lineman Sinatra

    View full-size slide

  121. Lineman Sinatra

    View full-size slide

  122. Lineman Sinatra

    View full-size slide

  123. Client Server

    View full-size slide

  124. Client Server

    View full-size slide

  125. Client Server

    View full-size slide

  126. Client Server

    View full-size slide

  127. Lineman Sinatra

    View full-size slide

  128. Lineman Sinatra

    View full-size slide

  129. Lineman Sinatra

    View full-size slide

  130. Lineman Sinatra

    View full-size slide

  131. 30 minute test build

    View full-size slide

  132. 4 minutes 4 minutes
    +

    View full-size slide

  133. 4 minutes 4 minutes
    +
    + 2 minutes

    View full-size slide

  134. 4 minutes 4 minutes
    +
    + 2 minutes
    10 minute test build

    View full-size slide

  135. It's habit forming.

    View full-size slide

  136. It's habit forming.

    View full-size slide

  137. It's habit forming.

    View full-size slide

  138. linemanjs.com/rails.html

    View full-size slide

  139. My name is Justin Searls
    Please tweet me @searls &
    Say [email protected]

    View full-size slide

  140. noun project attribution
    Yarn designed by Marie Coons from
    The Noun Project!
    !
    Scale designed by Ritika Khasgiwale
    from The Noun Project

    View full-size slide