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

Code Charcuterie

Code Charcuterie

My talk on Code Charcuterie from RailsBerry

Aaron Patterson

June 07, 2012
Tweet

More Decks by Aaron Patterson

Other Decks in Programming

Transcript

  1. ZOMG
    HAPPY FRIDAY!

    View Slide

  2. View Slide

  3. Rails Barry

    View Slide

  4. Do you have a Mac?
    Do you have a Wii?

    View Slide

  5. DarwiinRemote

    View Slide

  6. Aaron Patterson

    View Slide

  7. @tenderlove

    View Slide

  8. @tenderlove
    TWEET TO ME!

    View Slide

  9. View Slide

  10. [email protected]
    EMAIL TO ME!

    View Slide

  11. AT&T, AT&T logo and all AT&T related marks are trademarks of AT&T Intellectual Property and/or AT&T affiliated companies.

    View Slide

  12. Señor Software
    Engineer

    View Slide

  13. ruby-core
    rails-core

    View Slide

  14. WWFMD?

    View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. AWESOME
    LOGOS

    View Slide

  25. STOP IT

    View Slide

  26. View Slide

  27. View Slide

  28. NOTHING

    View Slide

  29. Code
    Charcuterie

    View Slide

  30. Amateur
    Charcuterier (sp?)

    View Slide

  31. View Slide

  32. View Slide

  33. View Slide

  34. View Slide

  35. Tight Coupling?

    View Slide

  36. Setting Expectations

    View Slide

  37. View Slide

  38. Instrumentation
    Concurrency
    Attributes

    View Slide

  39. Number of Doubts
    Chart of Doubts

    View Slide

  40. Number of Doubts
    Chart of Doubts
    Few Doubts

    View Slide

  41. Number of Doubts
    Chart of Doubts
    Many Doubts

    View Slide

  42. Instrumentation

    View Slide

  43. Publishing
    ActiveSupport::Notifications.instrument("render") do
    render :text => "Foo"
    end

    View Slide

  44. Subscribing
    events = []
    ActiveSupport::Notifications.subscribe("render") do |*e|
    events << e
    end

    View Slide

  45. def instrument(name, payload={})
    started = Time.now
    begin
    yield
    ensure
    @notifier.publish(name, started, Time.now, @id, payload)
    end
    end

    View Slide

  46. def instrument(name, payload={})
    started = Time.now
    begin
    yield
    ensure
    @notifier.publish(name, started, Time.now, @id, payload)
    end
    end
    Number of Doubts

    View Slide

  47. my time + children

    View Slide

  48. -> sql executed
    -> view rendered
    Events Received

    View Slide

  49. -> sql executed
    -> view rendered
    Events Received
    Number of Doubts

    View Slide

  50. Missing Information

    View Slide

  51. -> start rendering
    -> start execute SQL
    <- finish execute SQL
    -> start execute SQL
    <- finish execute SQL
    <- finish rendering

    View Slide

  52. Rendering
    SQL
    SQL

    View Slide

  53. class Listener
    def start(event)
    end
    def finish(event)
    end
    end
    listener = Listener.new
    AS::Notifications.subscribe("render", listener)

    View Slide

  54. Evented Publishing
    def instrument(name, payload={})
    @notifier.start(name, @id, payload)
    begin
    yield
    ensure
    @notifier.finish(name, @id, payload)
    end
    end

    View Slide

  55. Make it Quack
    class Timed
    def initialize(pattern, delegate)
    @delegate = delegate
    @timestack = Hash.new { |h,id|
    h[id] = Hash.new { |ids,name| ids[name] = [] }
    }
    end
    def start(name, id, payload)
    @timestack[id][name].push Time.now
    end
    def finish(name, id, payload)
    started = @timestack[id][name].pop
    @delegate.call(name, started, Time.now, id, payload)
    end
    end

    View Slide

  56. Tiny Factory
    def subscribe(pattern = nil, listener = Proc.new)
    if listener.respond_to?(:call)
    subscriber = Timed.new pattern, listener
    else
    subscriber = listener
    end
    @subscribers << subscriber
    end

    View Slide

  57. Our Reward

    View Slide

  58. class Indented
    def initialize
    @indentation = 0
    end
    def start name, id, payload
    print indent
    puts "-> #{name} -- #{payload[:virtual_path]}"
    @indentation += 1
    end
    def finish name, id, payload
    @indentation -= 1
    print indent
    puts "<- #{name} -- #{payload[:virtual_path]}"
    end
    private
    def indent
    " " * @indentation
    end
    end
    ActiveSupport::Notifications.notifier.subscribe(nil, Indented.new)

    View Slide

  59. -> process_action.action_controller --
    -> render_template.action_view --
    -> !render_template.action_view -- sessions/index
    <- !render_template.action_view -- sessions/index
    <- render_template.action_view --
    -> !render_template.action_view -- layouts/application
    -> render_partial.action_view --
    -> !render_template.action_view -- layouts/_navigation
    <- !render_template.action_view -- layouts/_navigation
    <- render_partial.action_view --
    -> render_partial.action_view --
    -> !render_template.action_view -- sessions/_slim_login
    <- !render_template.action_view -- sessions/_slim_login
    <- render_partial.action_view --
    <- !render_template.action_view -- layouts/application
    <- process_action.action_controller --

    View Slide

  60. View Slide

  61. ml
    !render_template.action_view (4.564 ms)
    virtual_path layouts/application
    render_partial.action_view (0.346 ms)
    identifier /app/views/layouts/_navigation
    render_partial.action_view (0.133 ms)
    identifier /app/views/sessions/_logout.ht
    !render_template.action_view (0.291 ms)
    virtual_path layouts/_navigation
    !render_template.action_view (0.087 ms)
    virtual_path sessions/_logout
    _l

    View Slide

  62. tion_controller (0.021 ms)
    MessagesController
    index
    GET
    /messages
    process_action.action_controller (95.463 ms)
    controller MessagesController
    action index
    method GET
    path /messages
    sql.active_record (5.671 ms)
    sql
    SELECT "messages"."id" AS
    t0_r0, "messages"."address_id"
    AS t0_r1, "messages"."from" AS
    t0_r2, "messages"."to" AS
    t0_r3, "messages"."disposable"
    AS t0_r4, "messages"."subject"
    AS t0_r5, "messages"."body" AS
    t0_r6, "messages"."plain" AS
    t0_r7, "messages"."html" AS
    t0_r8, "messages"."created_at"
    AS t0_r9,
    "messages"."updated_at" AS
    t0_r10,
    "messages"."parsed_message_id"
    AS t0_r11, "addresses"."id" AS
    t1_r0, "addresses"."name" AS
    t1_r1, "addresses"."person_id"
    AS t1_r2,
    "addresses"."created_at" AS
    t1_r3,
    "addresses"."updated_at" AS
    t1_r4 FROM "messages" LEFT
    render_template.action_view (63.632 ms)
    identifier /app/views/messages/index.html
    layout layouts/application

    View Slide

  63. Concurrency

    View Slide

  64. Amdahl's Law

    View Slide

  65. View Slide

  66. laptop = Laptop.new 1
    10.times {
    laptop.execute { sleep(1) }
    }

    View Slide

  67. View Slide

  68. = 1

    View Slide

  69. $ time ruby test.rb
    real 0m10.046s
    user 0m0.023s
    sys 0m0.008s
    $

    View Slide

  70. laptop = Laptop.new ∞
    10.times {
    laptop.execute { sleep(1) }
    }

    View Slide

  71. View Slide

  72. = 10

    View Slide

  73. $ time ruby test.rb
    real 0m1.033s
    user 0m0.023s
    sys 0m0.008s
    $

    View Slide

  74. Modeling Our
    Machine

    View Slide

  75. class Sleeper
    def run; sleep(1); end
    end
    q = Queue.new
    10.times { q.push Sleeper.new }
    10.times { q.push nil }
    # 10 cores! wow!
    10.times.map {
    Thread.new {
    while obj = q.pop
    obj.run
    end
    }
    }.each(&:join)

    View Slide

  76. What is my P?

    View Slide

  77. class Sleeper
    def run; sleep(1); end
    end
    q = SlowQueue.new # make `pop` cost more
    10.times { q.push Sleeper.new }
    10.times { q.push nil }
    # 10 cores! wow!
    10.times.map {
    Thread.new {
    while obj = q.pop
    obj.run
    end
    }
    }.each(&:join)

    View Slide

  78. $ time ruby test.rb
    real 0m2.153s
    user 0m0.027s
    sys 0m0.012s

    View Slide

  79. Hidden Sequential
    Code

    View Slide

  80. Starvation

    View Slide

  81. Scarce Resources

    View Slide

  82. View Slide

  83. ME EBI

    View Slide

  84. ME EBI

    View Slide

  85. Connection Pool

    View Slide

  86. Connections Are Not
    Thread Safe

    View Slide

  87. One to One

    View Slide

  88. One to One
    Number of Doubts

    View Slide

  89. pool.connection
    pool = ActiveRecord::Base.connection_pool
    p pool.connection.object_id # => 1234
    p pool.connection.object_id # => 1234

    View Slide

  90. Starvation
    5.times {
    Thread.new { pool.connection }
    }
    Thread.new { pool.connection }

    View Slide

  91. Starvation
    5.times {
    Thread.new { pool.connection }
    }
    Thread.new { pool.connection }

    View Slide

  92. Queueing

    View Slide

  93. Splitting Work
    cpu_queue = Queue.new
    io_queue = Queue.new
    5.times {
    Thread.new { io_queue.pop.run }
    }
    10.times {
    Thread.new { cpu_queue.pop.run }
    }

    View Slide

  94. Consumer
    class Saver
    def initialize(record)
    @record = record
    end
    def run
    @record.save!
    end
    end

    View Slide

  95. Producer
    class CreatePost
    def initialize(q)
    @q = q
    end
    def run
    post = Post.new(:name => 'aaron')
    @q.push SavePost.new(post)
    end
    end

    View Slide

  96. View Slide

  97. Parallel CPU
    def fib n
    if n < 3
    1
    else
    fib(n-1) + fib(n-2)
    end
    end
    2.times.map { fib(34) }

    View Slide

  98. $ time ruby test.rb
    real 0m3.315s
    user 0m3.264s
    sys 0m0.010s

    View Slide

  99. 2.times.map {
    Thread.new { fib(34) }
    }.each(&:join)
    Parallelize

    View Slide

  100. Caught in the GIL
    $ time ruby test.rb
    real 0m4.825s
    user 0m3.823s
    sys 0m1.978s

    View Slide

  101. Slow I/O Server
    server = GenericServer.new('127.0.0.1')
    server.start do |socket|
    sleep 0.5
    socket.print "HELLO"
    socket.close
    end

    View Slide

  102. TCP Client
    require 'socket'
    5.times.map {
    sock = TCPSocket.new '127.0.0.1', 28561
    puts sock.read
    }

    View Slide

  103. $ time ruby client.rb
    HELLO
    HELLO
    HELLO
    HELLO
    HELLO
    real 0m2.546s
    user 0m0.026s
    sys 0m0.009s

    View Slide

  104. require 'socket'
    5.times.map {
    Thread.new {
    sock = TCPSocket.new '127.0.0.1', 28561
    puts sock.read
    }
    }.each(&:join)
    Parallelize

    View Slide

  105. $ time ruby client.rb
    HELLO
    HELLO
    HELLO
    HELLO
    HELLO
    real 0m0.552s
    user 0m0.028s
    sys 0m0.009s

    View Slide

  106. Sending Email!

    View Slide

  107. class UserController < ApplicationController
    def create
    user = User.create!(params[:user])
    UserMailer.deliver_welcome_email(user)
    end
    end

    View Slide

  108. class UserController < ApplicationController
    def create
    user = User.create!(params[:user])
    UserMailer.deliver_welcome_email(user)
    end
    end

    View Slide

  109. class UserController < ApplicationController
    def create
    user = User.create!(params[:user])
    UserMailer.deliver_welcome_email(user)
    end
    end
    Number of Doubts

    View Slide

  110. class MailSender
    def initialize(id)
    @id = id
    end
    def run
    user = User.find(@id)
    UserMailer.deliver_welcome_email(user)
    end
    end

    View Slide

  111. class UserController < ApplicationController
    def create
    user = User.create!(params[:user])
    mail_queue.push MailSender.new(user.id)
    end
    end

    View Slide

  112. Thread.new {
    loop do
    mail_queue.pop.run
    }
    }

    View Slide

  113. View Slide

  114. Outside the loop
    More Parallel

    View Slide

  115. You are
    ENCOURAGED
    to use threads

    View Slide

  116. I/O can be parallelized
    CPU cannot
    * in MRI

    View Slide

  117. Attribute Accessors

    View Slide

  118. Attribute Access
    post = Post.new(:title => 'hello!')
    post.title # => 'hello!'
    post.respond_to?(:title) # => true

    View Slide

  119. Lazily Defined
    begin
    p Post.instance_method(:name)
    rescue NameError
    puts "oh no!"
    end
    Post.new(:name => 'aaron')
    p Post.instance_method(:name)

    View Slide

  120. Output
    $ ruby -I lib:test test/cases/argh.rb
    oh no!
    #)#__temp__>

    View Slide

  121. 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

    View Slide

  122. Who called
    respond_to?
    {: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 `'
    ##########################################################################################
    {: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 `'
    ##########################################################################################
    Number of Doubts

    View Slide

  123. after_initialize
    class Subject < ActiveRecord::Base
    after_initialize :set_email_address
    protected
    def set_email_address
    ...
    end
    end

    View Slide

  124. use super
    class Subject < ActiveRecord::Base
    def initialize(*args)
    super
    set_email_address # after_init
    end
    protected
    def set_email_address
    ...
    end
    end
    Number of Doubts

    View Slide

  125. DO IT AGAIN!
    10.times {
    Post.new(:name => 'aaron')
    }

    View Slide

  126. What method?
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    {:method=>"name="}
    {:method=>"_run__1048083580144971540__initialize__3082064574212577540__callbacks"}
    Number of Doubts

    View Slide

  127. Define Methods
    def define_attribute_methods
    ....
    @attribute_methods_mutex.synchronize do
    return if attribute_methods_generated?
    superclass.define_attribute_methods unless self == base_class
    super(column_names)
    column_names.each { |name| define_external_attribute_method(name) }
    @attribute_methods_generated = true
    end
    end

    View Slide

  128. Define Methods
    def define_attribute_methods
    ....
    @attribute_methods_mutex.synchronize do
    return if attribute_methods_generated?
    superclass.define_attribute_methods unless self == base_class
    super(column_names)
    column_names.each { |name| define_external_attribute_method(name) }
    @attribute_methods_generated = true
    end
    end
    wtf???

    View Slide

  129. RAWWWRRRR
    class A
    def hi(stuff)
    end
    end
    class B < A
    def hi
    super(%w{ problem? })
    end
    end

    View Slide

  130. :'(
    def calls_hi(obj)
    obj.hi(%w{ how are you? })
    end
    calls_hi(A.new) # => OK!
    calls_hi(B.new) # => Boom!

    View Slide

  131. IF
    THEN
    B is an A
    B can be used as an A

    View Slide

  132. Number of Doubts

    View Slide

  133. define_attribute_methods

    View Slide

  134. ActiveModel

    View Slide

  135. define_attribute_methods
    def define_attribute_methods(attr_names)
    attr_names.each { |attr_name|
    define_attribute_method(attr_name)
    }
    end

    View Slide

  136. define_attribute_methods
    def define_attribute_methods(attr_names)
    attr_names.each { |attr_name|
    define_attribute_method(attr_name)
    }
    end
    THANKS
    ACTIVE MODEL

    View Slide

  137. define_attribute_method
    def define_attribute_method(attr_name)
    ...
    unless instance_method_already_implemented?(method_name)
    generate_method = "define_method_#{matcher.method_missing_target}"
    if respond_to?(generate_method, true)
    send(generate_method, attr_name)
    else
    define_optimized_call generated_attribute_methods, method_name,
    matcher.method_missing_target, attr_name.to_s
    end
    end
    end

    View Slide

  138. define_attribute_method
    def define_attribute_method(attr_name)
    ...
    unless instance_method_already_implemented?(method_name)
    generate_method = "define_method_#{matcher.method_missing_target}"
    if respond_to?(generate_method, true)
    send(generate_method, attr_name)
    else
    define_optimized_call generated_attribute_methods, method_name,
    matcher.method_missing_target, attr_name.to_s
    end
    end
    end

    View Slide

  139. define_attribute_method
    def define_attribute_method(attr_name)
    ...
    unless instance_method_already_implemented?(method_name)
    generate_method = "define_method_#{matcher.method_missing_target}"
    if respond_to?(generate_method, true)
    send(generate_method, attr_name)
    else
    define_optimized_call generated_attribute_methods, method_name,
    matcher.method_missing_target, attr_name.to_s
    end
    end
    end

    View Slide

  140. define_attribute_method
    def define_attribute_method(attr_name)
    ...
    unless instance_method_already_implemented?(method_name)
    generate_method = "define_method_#{matcher.method_missing_target}"
    if respond_to?(generate_method, true)
    send(generate_method, attr_name)
    else
    define_optimized_call generated_attribute_methods, method_name,
    matcher.method_missing_target, attr_name.to_s
    end
    end
    end

    View Slide

  141. define_attribute_method
    def define_attribute_method(attr_name)
    ...
    unless instance_method_already_implemented?(method_name)
    generate_method = "define_method_#{matcher.method_missing_target}"
    if respond_to?(generate_method, true)
    send(generate_method, attr_name)
    else
    define_optimized_call generated_attribute_methods, method_name,
    matcher.method_missing_target, attr_name.to_s
    end
    end
    end

    View Slide

  142. WHY AREN'T THEY
    ALL OPTIMIZED????

    View Slide

  143. define_method_attribute
    define_method_attribute=
    define_method_attribute_before_type_cast
    define_method_attribute?
    define_method_attribute_changed?
    define_method_attribute_change
    define_method_attribute_will_change!
    define_method_attribute_was
    define_method_reset_attribute!
    define_method__attribute
    define_method_attribute
    define_method_attribute=
    define_method_attribute_before_type_cast
    define_method_attribute?
    define_method_attribute_changed?
    define_method_attribute_change
    define_method_attribute_will_change!
    define_method_attribute_was
    define_method_reset_attribute!
    define_method__attribute

    View Slide

  144. define_method_attribute
    define_method_attribute=
    define_method_attribute_before_type_cast
    define_method_attribute?
    define_method_attribute_changed?
    define_method_attribute_change
    define_method_attribute_will_change!
    define_method_attribute_was
    define_method_reset_attribute!
    define_method__attribute
    define_method_attribute
    define_method_attribute=
    define_method_attribute_before_type_cast
    define_method_attribute?
    define_method_attribute_changed?
    define_method_attribute_change
    define_method_attribute_will_change!
    define_method_attribute_was
    define_method_reset_attribute!
    define_method__attribute

    View Slide

  145. define_method_attribute
    define_method_attribute=
    define_method_attribute_before_type_cast
    define_method_attribute?
    define_method_attribute_changed?
    define_method_attribute_change
    define_method_attribute_will_change!
    define_method_attribute_was
    define_method_reset_attribute!
    define_method__attribute
    define_method_attribute
    define_method_attribute=
    define_method_attribute_before_type_cast
    define_method_attribute?
    define_method_attribute_changed?
    define_method_attribute_change
    define_method_attribute_will_change!
    define_method_attribute_was
    define_method_reset_attribute!
    define_method__attribute

    View Slide

  146. define_method_attribute
    define_method_attribute=
    define_method_attribute_before_type_cast
    define_method_attribute?
    define_method_attribute_changed?
    define_method_attribute_change
    define_method_attribute_will_change!
    define_method_attribute_was
    define_method_reset_attribute!
    define_method__attribute
    define_method_attribute
    define_method_attribute=
    define_method_attribute_before_type_cast
    define_method_attribute?
    define_method_attribute_changed?
    define_method_attribute_change
    define_method_attribute_will_change!
    define_method_attribute_was
    define_method_reset_attribute!
    define_method__attribute
    THANKS
    ACTIVE MODEL

    View Slide

  147. Number of Doubts

    View Slide

  148. Side Note:
    Does anyone USE this?

    View Slide

  149. Side Note:
    Should you use this?

    View Slide

  150. Where were we?

    View Slide

  151. Post.new
    respond_to?
    AR define_attr_methods
    AM define_attr_methods
    AM define_attr_method
    AR define_method_attr

    View Slide

  152. In VIM
    •Ctrl-]
    •Ctrl-o
    •:help ctags

    View Slide

  153. def define_method_attribute(attr_name)
    generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
    def __temp__
    #{internal_attribute_access_code(attr_name, attribute_cast_code(attr_name))}
    end
    alias_method '#{attr_name}', :__temp__
    undef_method :__temp__
    STR
    end

    View Slide

  154. method(:name).source

    View Slide

  155. Type Cast
    Value Lookup
    post.method(:title).source

    View Slide

  156. 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

    View Slide

  157. 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

    View Slide

  158. 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
    ([email protected][attr_name]) &&
    ActiveRecord::ConnectionAdapters::SQLiteColumn.string_to_time(v))
    end

    View Slide

  159. 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
    ([email protected][attr_name]) &&
    ActiveRecord::ConnectionAdapters::SQLiteColumn.string_to_time(v))
    end
    DOUBTS
    INCREASING

    View Slide

  160. attribute_cast_code
    def attribute_cast_code(attr_name)
    columns_hash[attr_name].type_cast_code('v')
    end

    View Slide

  161. type_cast_code
    def type_cast_code(var_name)
    klass = self.class.name
    case type
    when :string, :text then var_name
    when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
    when :float then "#{var_name}.to_f"
    when :decimal then "#{klass}.value_to_decimal(#{var_name})"
    when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
    when :time then "#{klass}.string_to_dummy_time(#{var_name})"
    when :date then "#{klass}.string_to_date(#{var_name})"
    when :binary then "#{klass}.binary_to_string(#{var_name})"
    when :boolean then "#{klass}.value_to_boolean(#{var_name})"
    else var_name
    end
    end

    View Slide

  162. type_cast
    def type_cast(value)
    return nil if value.nil?
    return coder.load(value) if encoded?
    klass = self.class
    case type
    when :string, :text then value
    when :integer then value.to_i rescue value ? 1 : 0
    when :float then value.to_f
    when :decimal then klass.value_to_decimal(value)
    when :datetime, :timestamp then klass.string_to_time(value)
    when :time then klass.string_to_dummy_time(value)
    when :date then klass.string_to_date(value)
    when :binary then klass.binary_to_string(value)
    when :boolean then klass.value_to_boolean(value)
    else value
    end
    end

    View Slide

  163. Number of Doubts

    View Slide

  164. Number of Doubts
    TOO MANY!

    View Slide

  165. HOWTO FIX?

    View Slide

  166. What do we want?
    •Less dynamic code
    •Consistent method definitions
    •Matching speed (even on read_attribute)

    View Slide

  167. What does it do?
    •Attribute lookup
    •Column lookup
    •Value typecasting
    •Typecast value caching

    View Slide

  168. 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

    View Slide

  169. def name
    read_attribute 'name'
    end
    Post#name

    View Slide

  170. def created_at
    read_attribute 'created_at'
    end
    Post#created_at

    View Slide

  171. BONUS FEATURE!
    Post.select('true as hi').first.hi # => true

    View Slide

  172. View Slide

  173. View Slide

  174. Clean Code
    IS A FEATURE

    View Slide

  175. Don't throw out the baby
    with the bathwater!

    View Slide

  176. Questions?
    ὑ ὑ ὑ ὑ ὑ ὑ

    View Slide