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. *opinions expressed here do not reflect the rest of the

    rails core team! :-P Thursday, June 7, 12
  2. Features •Someone commits it •We might talk about it •It

    doesn’t get reverted Thursday, June 7, 12
  3. def fib(n) if n < 3 1 else fib(n-1) +

    fib(n-2) end end 4.times { fib(34) } Thursday, June 7, 12
  4. 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
  5. 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
  6. 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
  7. 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
  8. $ time ruby client.rb HELLO HELLO HELLO HELLO HELLO real

    0m2.555s user 0m0.029s sys 0m0.011s Thursday, June 7, 12
  9. 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
  10. $ time ruby client.rb HELLO HELLO HELLO HELLO HELLO real

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

    3. Acquire GVL What Happened? Thursday, June 7, 12
  12. What Happened? 1. Release GVL 2. Perform CPU only key

    generation 3. Acquire GVL Thursday, June 7, 12
  13. 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
  14. 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
  15. # app code Rails.queue.push SendEmail.new(user) # consumer while job =

    queue.pop job.run # SendEmail#run end Thursday, June 7, 12
  16. 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
  17. class StreamingApplication class Body def each yield "hello" yield "

    world" end end def call(env) [200, { }, Body.new] end end Thursday, June 7, 12
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. class Person < ActiveRecord::Base end person = Person.new person.preferences =

    { 'color' => 'green' } HStore (PG) Rails 4 Thursday, June 7, 12
  52. D i s t r i b u t e

    d Thursday, June 7, 12
  53. D i s t r i b u t e

    d Thursday, June 7, 12