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.

E6c6e133e74c3b83f04d2861deaa1c20?s=128

Justin Searls

February 08, 2014
Tweet

Transcript

  1. as easy as rails

  2. My name is Justin Searls Please tweet me @searls &

    Say hello@testdouble.com
  3. 1

  4. 2 1

  5. 2 1 3

  6. You own a bakery.

  7. None
  8. None
  9. None
  10. Honest mistake.

  11. You own a software studio.

  12. None
  13. None
  14. None
  15. None
  16. Honest mistake.

  17. Rails makes it too easy.

  18. server-side tools

  19. client-side tools

  20. Just kidding.

  21. client-side tools

  22. None
  23. Provocation:

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

  25. Before Rails was easy

  26. Before Rails was easy JavaScript wasn't hard

  27. Is JavaScript a terrible language?

  28. YES DEFINITELY

  29. Is that why writing JavaScript is terrible?

  30. ASK AGAIN LATER

  31. None
  32. The community changed the world.

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

  34. 2005

  35. 2005

  36. 2006

  37. 2007

  38. 2008

  39. 2009

  40. 2010

  41. 2011

  42. 2011

  43. 2012

  44. 2013

  45. 2014

  46. 2014

  47. Where are the best tools?

  48. Ruby's tool ecosystem is mature, crowded Where are the best

    tools?
  49. Ruby's tool ecosystem is mature, crowded Node's ecosystem is immature,

    innovative Where are the best tools?
  50. 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?
  51. 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?
  52. 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?
  53. 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?
  54. None
  55. Rails won the war.

  56. Rails won the war? which

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

  58. Basecamp

  59. Basecamp

  60. None
  61. Routing

  62. Routing Models

  63. Routing Models Persistence

  64. Routing Models Persistence Sessions

  65. Routing Models Persistence Sessions Mailers

  66. Routing Models Persistence Sessions Mailers JavaScript Alternatives

  67. Routing Models Persistence Sessions Mailers AJAX ERB Tags

  68. Routing Models Persistence Sessions Mailers RJS

  69. Routing Models Persistence Sessions Mailers AJAX ERB Tags Unobtrusive

  70. Routing Models Persistence Sessions Mailers Turbolinks

  71. Basecamp

  72. Basecamp

  73. Basecamp

  74. HTML UI

  75. HTML UI ! <a href="/items/1">Item 1</a>

  76. HTML UI ! <a href="/items/1">Item 1</a> ! <form action="/items" method="post">

    <input type="submit" value="Submit"/> </form>
  77. JavaScript UI

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

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

  80. HTML UI ! app ├── controllers │ └── items_controller.rb ├──

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

    models │ └── item.rb ├── views │ └── items │ └── index.html.erb └── assets └── javascripts └── application.js
  82. 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
  83. 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
  84. 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
  85. 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
  86. 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
  87. 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
  88. 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
  89. what's wrong with that vestigial appendage?

  90. app/

  91. app/

  92. app/

  93. app/

  94. ap

  95. ap

  96. ap p/

  97. app1/ ! app2/

  98. app1/ ! app2/

  99. app1/ ! app2/

  100. app1/ ! app2/

  101. app1

  102. app1

  103. app1 app2

  104. late extraction costs more than early abstraction

  105. None
  106. None
  107. None
  108. None
  109. None
  110. None
  111. None
  112. If you see this:

  113. If you see this: ! <script> </script>

  114. If you see this: ! <script> user = <%= user.to_json

    %> </script>
  115. If you see this: ! <script> user = <%= user.to_json

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

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

    %> time = <%= Time.now.to_i %> items = <%= items.to_json %> token = "<%= @token %>" </script>
  118. ! <script> user = <%= user.to_json %> time = <%=

    Time.now.to_i %> items = <%= items.to_json %> token = "<%= @token %>" </script> Your yarn is tangled
  119. late extraction costs more than early abstraction

  120. JavaScript made easy.

  121. App Framework Convention & Config Build Automation

  122. Rails App Framework Convention & Config Build Automation

  123. Rails Rails App Framework Convention & Config Build Automation

  124. Rails Rails Railsy Rake App Framework Convention & Config Build

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

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

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

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

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

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

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

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

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

    Automation Backbone Ember Angular ᵇ(´-ʆ)ᵃ

  134. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Node.js (Grunt) Backbone Ember Angular ᵇ(´-ʆ)ᵃ

  135. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Node.js (Grunt) ??? Backbone Ember Angular ᵇ(´-ʆ)ᵃ

  136. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Node.js (Grunt) Lineman Backbone Ember Angular ᵇ(´-ʆ)ᵃ

  137. @linemanjs linemanjs.com

  138. ! $ npm install -g lineman Install

  139. ! $ npm install -g lineman Install ! $ lineman

    new my-app Create
  140. None
  141. None
  142. hi() code

  143. hi() code ! save

  144. 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
  145. 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
  146. 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
  147. < 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
  148. None
  149. < 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
  150. 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
  151. None
  152. Can your server host static files?

  153. ! $ lineman build !

  154. ! $ lineman build ! $ tree dist

  155. ! $ lineman build ! $ tree dist ! dist

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

    ├── assets.json ├── css │ └── app-6193b1f500c0f944db0354592.css ├── index.html └── js └── app-8b750b723eae24580f7d757f62.js
  157. ! $ heroku config:set BUILDPACK_URL=http://github.com/ testdouble/heroku-buildpack-lineman.git ! $ git push

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

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

    Heroku Builds with Node.js Runs without Node.js
  160. Starter Projects

  161. angular Starter Projects

  162. backbone angular Starter Projects

  163. ember backbone angular Starter Projects

  164. ember backbone angular web libs Starter Projects

  165. ember backbone angular web libs markdown blogs Starter Projects

  166. easy extensions

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

  168. ! $ npm install --save-dev lineman-bower ! $ lineman run

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

    ! ... Running "bower:install" (bower) task >> Installed bower packages ... zero-config plugins
  170. None
  171. Grunt

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

  173. 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
  174. 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-*
  175. None
  176. None
  177. Client Server

  178. Client Server ?

  179. Client Server

  180. Client Server

  181. Client Server

  182. Lineman Sinatra

  183. Lineman Sinatra

  184. Lineman Sinatra

  185. Lineman Sinatra

  186. Lineman Sinatra

  187. None
  188. Client Server

  189. Client Server

  190. Client Server

  191. Client Server

  192. Lineman Sinatra

  193. Lineman Sinatra

  194. Lineman Sinatra

  195. Lineman Sinatra

  196. None
  197. 30 minute test build

  198. None
  199. 4 minutes

  200. 4 minutes 4 minutes +

  201. 4 minutes 4 minutes + + 2 minutes

  202. 4 minutes 4 minutes + + 2 minutes 10 minute

    test build
  203. It's habit forming.

  204. It's habit forming.

  205. It's habit forming.

  206. None
  207. linemanjs.com/rails.html

  208. None
  209. My name is Justin Searls Please tweet me @searls &

    Say hello@testdouble.com
  210. noun project attribution Yarn designed by Marie Coons from The

    Noun Project! ! Scale designed by Ritika Khasgiwale from The Noun Project