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

Rails Four

Rails Four

This is about Rails Four and the future of the web, at Magma Rails.

Aaron Patterson

June 07, 2012
Tweet

More Decks by Aaron Patterson

Other Decks in Programming

Transcript

  1. Thursday, June 7, 12

  2. Thank You Magma Rails Thursday, June 7, 12

  3. Aaron Patterson @tenderlove Thursday, June 7, 12

  4. Señor Software Engineer Thursday, June 7, 12

  5. ruby core rails core Thursday, June 7, 12

  6. WWFMD? Thursday, June 7, 12

  7. Rails Four For you and me! Thursday, June 7, 12

  8. New Features! Thursday, June 7, 12

  9. Bells & Whistles Thursday, June 7, 12

  10. Problems & Solutions Thursday, June 7, 12

  11. How Rails Core Works. Thursday, June 7, 12

  12. *opinions expressed here do not reflect the rest of the

    rails core team! :-P Thursday, June 7, 12
  13. The Plan: Thursday, June 7, 12

  14. There is no plan. Thursday, June 7, 12

  15. Features •Someone commits it •We might talk about it •It

    doesn’t get reverted Thursday, June 7, 12
  16. Reverts •We discuss and it seems bad •People complain •DHH

    doesn’t like it Thursday, June 7, 12
  17. How can I get a new feature? Thursday, June 7,

    12
  18. Send a pull request Thursday, June 7, 12

  19. Email rails-core Thursday, June 7, 12

  20. Prerequisite Thursday, June 7, 12

  21. Concurrency in MRI Thursday, June 7, 12

  22. CPU Thursday, June 7, 12

  23. I / O Thursday, June 7, 12

  24. GVL Thursday, June 7, 12

  25. CPU Concurrency Thursday, June 7, 12

  26. def fib(n) if n < 3 1 else fib(n-1) +

    fib(n-2) end end 4.times { fib(34) } Thursday, June 7, 12
  27. $ time ruby benchmark/bm_app_fib.rb real 0m10.327s user 0m10.225s sys 0m0.018s

    $ Thursday, June 7, 12
  28. $ time ruby benchmark/bm_app_fib.rb real 0m10.327s user 0m10.225s sys 0m0.018s

    $ Thursday, June 7, 12
  29. def fib(n) if n < 3 1 else fib(n-1) +

    fib(n-2) end end 4.times.map { Thread.new { fib(34) } }.each(&:join) Thursday, June 7, 12
  30. $ time ruby benchmark/bm_app_fib.rb real 0m9.486s user 0m9.212s sys 0m0.034s

    $ Thursday, June 7, 12
  31. $ time ruby benchmark/bm_app_fib.rb real 0m9.486s user 0m9.212s sys 0m0.034s

    $ Thursday, June 7, 12
  32. CPU CPU Thursday, June 7, 12

  33. CPU CPU Thread Thread Thursday, June 7, 12

  34. CPU CPU Thread Thread Thread ??? Thursday, June 7, 12

  35. CPU is a Resource Thursday, June 7, 12

  36. CPU CPU Thursday, June 7, 12

  37. CPU Thursday, June 7, 12

  38. CPU Thread Thread ??? Thursday, June 7, 12

  39. Thursday, June 7, 12

  40. Thursday, June 7, 12

  41. I / O Concurrency Thursday, June 7, 12

  42. Slow Mail Server server = GenericServer.new('127.0.0.1', 28561, 5) server.start do

    |socket| sleep 0.5 socket.print "HELLO" socket.close end Thursday, June 7, 12
  43. Slow Mail Server server = GenericServer.new('127.0.0.1', 28561, 5) server.start do

    |socket| sleep 0.5 socket.print "HELLO" socket.close end Thursday, June 7, 12
  44. Slow Mail Server server = GenericServer.new('127.0.0.1', 28561, 5) server.start do

    |socket| sleep 0.5 socket.print "HELLO" socket.close end Thursday, June 7, 12
  45. Mail Client require 'socket' 5.times { sock = TCPSocket.new '127.0.0.1',

    28561 puts sock.read } Thursday, June 7, 12
  46. $ time ruby client.rb HELLO HELLO HELLO HELLO HELLO real

    0m2.555s user 0m0.029s sys 0m0.011s Thursday, June 7, 12
  47. require 'socket' 5.times.map { Thread.new { sock = TCPSocket.new '127.0.0.1',

    28561 puts sock.read } }.each(&:join) Mail Client Thursday, June 7, 12
  48. $ time ruby client.rb HELLO HELLO HELLO HELLO HELLO real

    0m0.556s user 0m0.028s sys 0m0.011s Thursday, June 7, 12
  49. 1. Release GVL 2. Wait until data is available (select())

    3. Acquire GVL What Happened? Thursday, June 7, 12
  50. Not Just for Clients Thursday, June 7, 12

  51. Small Trick Thursday, June 7, 12

  52. Generate Keys require 'openssl' 5.times.map { OpenSSL::PKey::RSA.generate 5000 } Thursday,

    June 7, 12
  53. Thursday, June 7, 12

  54. Thursday, June 7, 12

  55. Generate Keys require 'openssl' 5.times.map { Thread.new { OpenSSL::PKey::RSA.generate 5000

    } }.each(&:join) Thursday, June 7, 12
  56. Thursday, June 7, 12

  57. Thursday, June 7, 12

  58. What Happened? 1. Release GVL 2. Perform CPU only key

    generation 3. Acquire GVL Thursday, June 7, 12
  59. Threads are for managing resources Thursday, June 7, 12

  60. A locked interpreter is a locked interpreter Thursday, June 7,

    12
  61. Be wary of libraries that boast Thursday, June 7, 12

  62. Understand Threads. Do not fear them. Thursday, June 7, 12

  63. Problem: Email Thursday, June 7, 12

  64. Action Mailer Thursday, June 7, 12

  65. class UsersController < ApplicationController def create @user = User.new(params[:user]) respond_to

    do |format| if @user.save # Send Email UserMailer.deliver_welcome_email(@user) end end end end Thursday, June 7, 12
  66. Create User Send Email Render View Thursday, June 7, 12

  67. Create User Send Email Render View Thursday, June 7, 12

  68. Create User Send Email Render View Thursday, June 7, 12

  69. Best case: Wasted user’s time Thursday, June 7, 12

  70. Worst case: Lose data and user Thursday, June 7, 12

  71. What do we do? Thursday, June 7, 12

  72. class UsersController < ApplicationController MAIL_QUEUE = ::Queue.new Thread.new { while

    work = MAIL_QUEUE.pop work.run end } def create @user = User.new(params[:user]) respond_to do |format| if @user.save MAIL_QUEUE.push UserMailer.new(@user) end end end end Thursday, June 7, 12
  73. Create User Render View Send Email Thursday, June 7, 12

  74. Create User Render View Send Email Thursday, June 7, 12

  75. Queue Issues •Not persistent •CPU intensive tasks •Cannot distribute to

    other machines Thursday, June 7, 12
  76. Queueing Libraries •Resque •Queue Classic •Sidekiq •DelayedJob •AMQP Thursday, June

    7, 12
  77. •Queue#push •Job#run We wrote: Thursday, June 7, 12

  78. If your API is inconsistent, you’re gonna have a bad

    time Thursday, June 7, 12
  79. Rails Queue* *not sure if this name is final Thursday,

    June 7, 12
  80. # app code Rails.queue.push SendEmail.new(user) # consumer while job =

    queue.pop job.run # SendEmail#run end Thursday, June 7, 12
  81. What’s the win? Thursday, June 7, 12

  82. class PeopleControllerTest < ActionController::TestCase fixtures :people def test_create_success assert_difference('Person.count') do

    assert_difference('Rails.queue.length') do post :create, :person => { :name => 'hi!', :password => 'lolwut', :password_confirmation => 'lolwut', :email => 'zomgwut' } end end assert_redirected_to '/' end end Thursday, June 7, 12
  83. Enforce Consistent API Thursday, June 7, 12

  84. Problem: Streaming Thursday, June 7, 12

  85. API Responses Thursday, June 7, 12

  86. Reducing time to first byte Thursday, June 7, 12

  87. Streaming with Rack Thursday, June 7, 12

  88. class StreamingApplication def call(env) [200, {}, ["hello world"]] end end

    Thursday, June 7, 12
  89. class StreamingApplication class Body def each yield "hello" yield "

    world" end end def call(env) [200, { }, Body.new] end end Thursday, June 7, 12
  90. What about Content-Length? Thursday, June 7, 12

  91. use Rack::Chunked Thursday, June 7, 12

  92. class StreamingApplication class Body def each yield "hello" yield "

    world\n" end end def call(env) [200, {'Content-Type' => 'text/plain'}, Body.new] end end use Rack::Chunked run StreamingApplication.new Thursday, June 7, 12
  93. Let’s make it infinite! Thursday, June 7, 12

  94. class StreamingApplication class Body < Struct.new(:q) def each while chunk

    = q.pop yield chunk end end end def call(env) q = Queue.new # A half second timer timer = Thread.new { loop { q.push "chunk\n" sleep 0.5 } } [200, {'Content-Type' => 'text/plain'}, Body.new(q)] end end Thursday, June 7, 12
  95. Thursday, June 7, 12

  96. Time until our first byte. Thursday, June 7, 12

  97. class StreamingApplication class Body def each yield HTML_HEADER sleep 0.5

    yield HTML_REST end end def call(env) [200, {'Content-Type' => 'text/html'}, Body.new] end end Thursday, June 7, 12
  98. Thursday, June 7, 12

  99. Thursday, June 7, 12

  100. Our tools •Ruby •Rack •Unicorn Thursday, June 7, 12

  101. Small Detour: Rack as node.js Thursday, June 7, 12

  102. Sample App http = require('http'); http.createServer(function(req, res) { res.writeHead(200, 'Content-Type':

    'text/plain') for(var i = 0; i < 10; i++) { res.chunk("Hello world\n") } res.end("Goodbye world\n") }); Thursday, June 7, 12
  103. In Ruby use Rack::Chunked run createServer { |req, res| res.writeHead(200,

    {'Content-Type' => 'text/plain'}) 10.times do res.chunk "Hello world\n" sleep 0.5 end res.end "Goodbye World\n" } Thursday, June 7, 12
  104. Adapter App class Adapter def initialize block @block = block

    end def call env resp = Response.new Thread.new { @block.call(env, resp) } # Wait for the child thread to write the header resp.await_header [resp.code, resp.header, resp] end end Thursday, June 7, 12
  105. Response class Response attr_reader :code, :header def initialize @header_written =

    Latch.new @q = Queue.new @code = 200 @header = {} end def await_header @header_written.await end def writeHead code, header @code = code @header = header @header_written.release end end Thursday, June 7, 12
  106. Response pt 2 class Response ## # Called from a

    child thread def chunk string @q.push string end ## # Called from a child thread def end string @q.push string @q.push nil end ## # Consumed in the main thread def each while part = @q.pop yield part end end end Thursday, June 7, 12
  107. Final piece def createServer &block Adapter.new block end Thursday, June

    7, 12
  108. Thursday, June 7, 12

  109. If the socket was available, threads could go away Thursday,

    June 7, 12
  110. Streaming in Rails? Thursday, June 7, 12

  111. In a Controller class UsersController < ApplicationController def index self.response_body

    = ["hello world"] end end Thursday, June 7, 12
  112. Rendering a template class UsersController < ApplicationController def index render

    :stream => true end end Thursday, June 7, 12
  113. I don’t like it Thursday, June 7, 12

  114. Rails Four class UsersController < ApplicationController def index # written

    to the socket response.body.write "hello" response.body.write " world\n" end end M y W ish! Thursday, June 7, 12
  115. Rails Four class UsersController < ApplicationController def index File.open('from aws')

    do |f| f.each_line do |line| response.body.puts line end end end end M y W ish! Thursday, June 7, 12
  116. Problem: Data Types Thursday, June 7, 12

  117. `create table users` class User 1:1 Active Record Thursday, June

    7, 12
  118. Active Record $ ruby script/rails c Loading development environment (Rails

    4.0.0.beta) irb(main):001:0> User.columns.map { |c| [c.name, c.type] } => [["id", :integer], ["name", :string], ["email", :string], ["password_digest", :string], ["created_at", :timestamp], ["updated_at", :timestamp]] Thursday, June 7, 12
  119. Column Class class ARBase class Column < Struct.new(:name, :type) def

    type_cast value case type when 'integer' then value.to_i when 'varchar(255)' then value.to_s when 'text' then value.to_s end end end end Thursday, June 7, 12
  120. class ARBase def self.columns table @cache[table] ||= conn.execute("PRAGMA table_info(#{table})").map {

    |row| Column.new(row[1], row[2]) } end def self.find id stmt = ARBase.conn.prepare( "SELECT * FROM #{table_name} WHERE id = ?" ) rs = stmt.execute id new Hash[columns(table_name).zip(rs.first)] end end Thursday, June 7, 12
  121. class ARBase def initialize record @record = record end def

    method_missing column column = self.class.columns(table_name).find { |c| c.name == column.to_s } column.type_cast @record[column.to_s] end end Type Casting Thursday, June 7, 12
  122. Column Class class ARBase class Column < Struct.new(:name, :type) def

    type_cast value case type when 'integer' then value.to_i when 'varchar(255)' then value.to_s when 'text' then value.to_s end end end end Thursday, June 7, 12
  123. Finder class ARBase def self.columns table @cache[table] ||= conn.execute("PRAGMA table_info(#{table})").map

    { |row| Column.new(row[1], row[2]) } end def self.find id stmt = ARBase.conn.prepare( "SELECT * FROM people WHERE id = ?" ) rs = stmt.execute id new Hash[rs.columns.zip(rs.first)] end end Thursday, June 7, 12
  124. SELECT name AS nickname .... SELECT 10 AS name ....

    Thursday, June 7, 12
  125. What do we do? Thursday, June 7, 12

  126. SELECT * Cache Type Type Type Type Thursday, June 7,

    12
  127. Active Record $ ruby script/rails c Loading development environment (Rails

    4.0.0.beta) irb(main):001:0> User.columns.map { |c| [c.name, c.type] } => [["id", :integer], ["name", :string], ["email", :string], ["password_digest", :string], ["created_at", :timestamp], ["updated_at", :timestamp]] Thursday, June 7, 12
  128. Column Class class ARBase class Column < Struct.new(:name, :type) def

    type_cast value case type when 'integer' then value.to_i when 'varchar(255)' then value.to_s when 'text' then value.to_s end end end end Thursday, June 7, 12
  129. class ARBase def self.columns table @cache[table] ||= conn.execute("PRAGMA table_info(#{table})").map {

    |row| Column.new(row[1], row[2]) } end def self.find id stmt = ARBase.conn.prepare( "SELECT * FROM #{table_name} WHERE id = ?" ) rs = stmt.execute id new Hash[columns(table_name).zip(rs.first)] end end Thursday, June 7, 12
  130. class ARBase def initialize record @record = record end def

    method_missing column column = self.class.columns(table_name).find { |c| c.name == column.to_s } column.type_cast @record[column.to_s] end end Type Casting Thursday, June 7, 12
  131. Column Class class ARBase class Column < Struct.new(:name, :type) def

    type_cast value case type when 'integer' then value.to_i when 'varchar(255)' then value.to_s when 'text' then value.to_s end end end end Thursday, June 7, 12
  132. Finder class ARBase def self.columns table @cache[table] ||= conn.execute("PRAGMA table_info(#{table})").map

    { |row| Column.new(row[1], row[2]) } end def self.find id stmt = ARBase.conn.prepare( "SELECT * FROM people WHERE id = ?" ) rs = stmt.execute id new Hash[rs.columns.zip(rs.first)] end end Thursday, June 7, 12
  133. SELECT name AS nickname .... SELECT 10 AS name ....

    Thursday, June 7, 12
  134. Column types require 'pg' conn = PG.connect 'dbname' => 'activerecord_unittest'

    rs = conn.exec 'SELECT * FROM posts' rs.fields.each_with_index do |fname, i| p [fname, rs.ftype(i), rs.fmod(i)] end Thursday, June 7, 12
  135. New Finder class ARBase def self.find id stmt = ARBase.conn.prepare(

    "SELECT * FROM #{table_name} WHERE id = ?" ) rs = stmt.execute id values = Hash[rs.columns.zip(rs.first)] types = {} rs.fields.each_with_index do |fname, i| types[fname] = type_cache(rs.ftype(i), rs.fmod(i)) end new(values, types) end end Thursday, June 7, 12
  136. New Casting class ARBase def initialize record, types @record =

    record @types = types end def method_missing column @types[column].type_cast @record[column] end end Thursday, June 7, 12
  137. Data types in Rails? Thursday, June 7, 12

  138. post = Post.new(:title => 'hello!') post.title # => 'hello!' post.respond_to?(:title)

    # => true Attribute Access Thursday, June 7, 12
  139. Lazily Defined begin p Post.instance_method(:name) rescue NameError puts "oh no!"

    end Post.new(:name => 'aaron') p Post.instance_method(:name) Thursday, June 7, 12
  140. Definition Time def method_missing(method, *args, &block) unless self.class.attribute_methods_generated? self.class.define_attribute_methods ...

    end end def respond_to?(name, include_private = false) unless self.class.attribute_methods_generated? self.class.define_attribute_methods end super end Thursday, June 7, 12
  141. puts caller {:method=>"name="} ########################################################################################## lib/active_record/attribute_assignment.rb:81:in `block in assign_attributes' lib/active_record/attribute_assignment.rb:78:in `each'

    lib/active_record/attribute_assignment.rb:78:in `assign_attributes' lib/active_record/base.rb:498:in `initialize' test/cases/argh.rb:16:in `new' test/cases/argh.rb:16:in `<main>' ########################################################################################## {:method=>"_run__1290908416815974468__initialize__2045902499019596289__callbacks"} ########################################################################################## lib/active_support/callbacks.rb:398:in `__run_callback' lib/active_support/callbacks.rb:385:in `_run_initialize_callbacks' lib/active_support/callbacks.rb:81:in `run_callbacks' lib/active_record/base.rb:501:in `initialize' test/cases/argh.rb:16:in `new' test/cases/argh.rb:16:in `<main>' ########################################################################################## Thursday, June 7, 12
  142. puts caller {:method=>"name="} ########################################################################################## lib/active_record/attribute_assignment.rb:81:in `block in assign_attributes' lib/active_record/attribute_assignment.rb:78:in `each'

    lib/active_record/attribute_assignment.rb:78:in `assign_attributes' lib/active_record/base.rb:498:in `initialize' test/cases/argh.rb:16:in `new' test/cases/argh.rb:16:in `<main>' ########################################################################################## {:method=>"_run__1290908416815974468__initialize__2045902499019596289__callbacks"} ########################################################################################## lib/active_support/callbacks.rb:398:in `__run_callback' lib/active_support/callbacks.rb:385:in `_run_initialize_callbacks' lib/active_support/callbacks.rb:81:in `run_callbacks' lib/active_record/base.rb:501:in `initialize' test/cases/argh.rb:16:in `new' test/cases/argh.rb:16:in `<main>' ########################################################################################## WTF? Thursday, June 7, 12
  143. after_initialize class Subject < ActiveRecord::Base after_initialize :set_email_address protected def set_email_address

    ... end end Thursday, June 7, 12
  144. use super? class Subject < ActiveRecord::Base def initialize(*args) super set_email_address

    # after_init end protected def set_email_address ... end end Thursday, June 7, 12
  145. 10.times { Post.new(:name => 'aaron') } Thursday, June 7, 12

  146. I digress... Thursday, June 7, 12

  147. method(:name).source Thursday, June 7, 12

  148. Value Lookup Type Cast Thursday, June 7, 12

  149. Value Lookup Type Cast Thursday, June 7, 12

  150. Post#name def name attr_name = 'name' unless @attributes.has_key?(attr_name) missing_attribute(attr_name, caller)

    end v = @attributes[attr_name] && v end Thursday, June 7, 12
  151. Post#name def name attr_name = 'name' unless @attributes.has_key?(attr_name) missing_attribute(attr_name, caller)

    end v = @attributes[attr_name] && v end SEEMS LEGIT Thursday, June 7, 12
  152. Post#created_at def created_at attr_name = 'created_at' unless @attributes.has_key?(attr_name) @attributes_cache[attr_name] ||=

    (missing_attribute(attr_name, caller) end (v=@attributes[attr_name]) && ActiveRecord::ConnectionAdapters::SQLiteColumn.string_to_time(v)) end Thursday, June 7, 12
  153. Post#created_at def created_at attr_name = 'created_at' unless @attributes.has_key?(attr_name) @attributes_cache[attr_name] ||=

    (missing_attribute(attr_name, caller) end (v=@attributes[attr_name]) && ActiveRecord::ConnectionAdapters::SQLiteColumn.string_to_time(v)) end WTF? Thursday, June 7, 12
  154. Method source depends on column type Thursday, June 7, 12

  155. Solution? Thursday, June 7, 12

  156. def read_attribute(attr_name) # If it's cached, just return it @attributes_cache.fetch(attr_name.to_s)

    { |name| column = @columns_hash[name] value = @attributes.fetch(name) { return attribute_missing(name, caller) } # Cache if we're supposed to if self.class.cache_attribute?(name) @attributes_cache[name] = column.type_cast(value) else column.type_cast value end } end Rails 4 Thursday, June 7, 12
  157. def name read_attribute 'name' end def created_at read_attribute 'created_at' end

    Generated CodeRails 4 Thursday, June 7, 12
  158. Complex type support Rails 4 Thursday, June 7, 12

  159. class Person < ActiveRecord::Base end person = Person.new person.preferences =

    { 'color' => 'green' } HStore (PG) Rails 4 Thursday, June 7, 12
  160. With PG 9.2, JSON support Thursday, June 7, 12

  161. Problem: Fat Clients Thursday, June 7, 12

  162. The future of the web is mobile. Thursday, June 7,

    12
  163. The future of the web is JavaScript. Thursday, June 7,

    12
  164. The future is Fat Clients. Thursday, June 7, 12

  165. Economy Thursday, June 7, 12

  166. Broadband Thursday, June 7, 12

  167. Smart Phones Thursday, June 7, 12

  168. History Thursday, June 7, 12

  169. Centralized Thursday, June 7, 12

  170. D i s t r i b u t e

    d Thursday, June 7, 12
  171. Centralized Thursday, June 7, 12

  172. D i s t r i b u t e

    d Thursday, June 7, 12
  173. API Support Thursday, June 7, 12

  174. •JBuilder •ActiveModel::Serializer •... and more! API Solutions Thursday, June 7,

    12
  175. Half Done. Thursday, June 7, 12

  176. Ship a producer and a consumer. Thursday, June 7, 12

  177. We need a Rails.js Thursday, June 7, 12

  178. Rails Four Theme: Parallelism & Distributed Computing Thursday, June 7,

    12
  179. Thank you! Thursday, June 7, 12

  180. Questions? Thursday, June 7, 12

  181. Thursday, June 7, 12