$30 off During Our Annual Pro Sale. View Details »

Dissecting Ruby with Ruby

Dissecting Ruby with Ruby

Dig into massive libraries with ease when you learn how to dissect ruby with ruby.

India Ruby Conf Edition.

Richard Schneeman

March 23, 2013
Tweet

More Decks by Richard Schneeman

Other Decks in Programming

Transcript

  1. Dissecting
    Ruby with
    Ruby
    @schneems

    View Slide

  2. @schneems

    View Slide

  3. Schnauser

    View Slide

  4. View Slide

  5. That man is
    married to
    Ruby, so to
    speak

    View Slide

  6. Ruby
    Me

    View Slide

  7. Python <3 ?

    View Slide

  8. Hans
    Peter
    Von
    Wolfe (the 5th)

    View Slide

  9. Sextant
    Gem

    View Slide

  10. Wicked


    Gem

    View Slide

  11. Triage
    Code
    codetriage.com

    View Slide

  12. View Slide

  13. Adjunct
    Professor

    View Slide

  14. Good News
    Everyone!
    schneems.com/ut-rails

    View Slide

  15. View Slide

  16. We optimize
    developer
    happiness

    View Slide

  17. Git
    Push:
    Deploy

    View Slide

  18. Ruby
    Task
    Force

    View Slide

  19. Ruby
    Task
    Force
    Member

    View Slide

  20. Co-worker

    View Slide

  21. Eat
    Naan
    Talk
    Ruby

    View Slide

  22. Eat
    Naan
    Talk
    Ruby

    View Slide

  23. Warning:
    I have
    Naan
    Jokes

    View Slide

  24. Some of
    them fall
    a bit “flat”

    View Slide

  25. Close your
    Laptops

    View Slide

  26. Unless you’re
    commenting
    on rails/rails
    issues

    View Slide

  27. We’ve all
    been there

    View Slide

  28. That
    comment is
    no longer
    valid

    View Slide

  29. I have no
    Idea how it
    works, it
    just does.

    View Slide

  30. Docs? Just
    look at the
    source code

    View Slide

  31. Get in
    Get Info
    Get Around

    View Slide

  32. Get in

    View Slide

  33. $ bundle open wicked

    View Slide

  34. $ bundle open wicked

    View Slide

  35. Set Editor

    View Slide

  36. ~/.bashrc
    export EDITOR="mvim"

    View Slide

  37. ~/.bashrc
    export EDITOR="subl -w"

    View Slide

  38. $
    echo $EDITOR
    subl -w

    View Slide

  39. Get Info
    Out

    View Slide

  40. Forget
    Fancy
    Debuggers

    View Slide

  41. All you need
    is “puts”

    View Slide

  42. Tracer Round

    View Slide

  43. puts “=================”
    Tracer Round

    View Slide

  44. Find the
    output

    View Slide

  45. puts “=================”
    Tracer Round

    View Slide

  46. puts “=================”
    Tracer Round
    Do you see it?

    View Slide

  47. How about
    now?

    View Slide

  48. A note on
    Notation

    View Slide

  49. String.new
    Class/Module

    View Slide

  50. String.new
    Method

    View Slide

  51. String.new
    class
    method

    View Slide

  52. Kernel#puts
    Not class
    method

    View Slide

  53. Caution!

    View Slide

  54. Nil

    View Slide

  55. Nil
    Null

    View Slide

  56. Nil
    Null
    Naan

    View Slide

  57. Obi-Naan
    Kenobi

    View Slide

  58. Who uses
    Ruby 2.0?

    View Slide

  59. Who uses
    Ruby 1.9.3?

    View Slide

  60. Who uses
    Ruby 1.8.7?

    View Slide

  61. Ruby 1.8.7
    is End of
    Life-d in
    days

    View Slide

  62. try 2.0.0 or
    JRuby on
    heroku

    View Slide

  63. Get Around

    View Slide

  64. Let’s look
    at Ruby

    View Slide

  65. Where is
    this method
    used?
    Problem:

    View Slide

  66. Solution:
    Kernel#caller

    View Slide

  67. www.
    ruby-doc
    .org

    View Slide

  68. Wat?

    View Slide

  69. Kernel#caller
    gives you the
    back trace

    View Slide

  70. def self.order_by_issue_count
    puts caller.inspect
    self.order("issues_count DESC")
    end

    View Slide

  71. [
    "~/projects/triage/app/controllers/pages_controller.rb:6:in `index'",
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/implicit_render.rb:4:in
    "ruby/gems/actionpack-3.2.12/lib/abstract_controller/base.rb:167:in `process_act
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/rendering.rb:10:in `proc
    "ruby/gems/actionpack-3.2.12/lib/abstract_controller/callbacks.rb:18:in `block i
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:414:in `_run__37
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:405:in `__run_ca
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:385:in `_run_pro
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:81:in `run_callb
    "ruby/gems/actionpack-3.2.12/lib/abstract_controller/callbacks.rb:17:in `process
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/rescue.rb:29:in `proces
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/instrumentation.rb:30:i
    "ruby/gems/activesupport-3.2.12/lib/active_support/notifications.rb:123:in `bloc
    "ruby/gems/activesupport-3.2.12/lib/active_support/notifications/instrumenter.rb
    "ruby/gems/activesupport-3.2.12/lib/active_support/notifications.rb:123:in `inst
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/instrumentation.rb:29:i
    File

    View Slide

  72. [
    "~/projects/triage/app/controllers/pages_controller.rb:6:in `index'",
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/implicit_render.rb:4:in
    "ruby/gems/actionpack-3.2.12/lib/abstract_controller/base.rb:167:in `process_act
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/rendering.rb:10:in `proc
    "ruby/gems/actionpack-3.2.12/lib/abstract_controller/callbacks.rb:18:in `block i
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:414:in `_run__37
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:405:in `__run_ca
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:385:in `_run_pro
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:81:in `run_callb
    "ruby/gems/actionpack-3.2.12/lib/abstract_controller/callbacks.rb:17:in `process
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/rescue.rb:29:in `proces
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/instrumentation.rb:30:i
    "ruby/gems/activesupport-3.2.12/lib/active_support/notifications.rb:123:in `bloc
    "ruby/gems/activesupport-3.2.12/lib/active_support/notifications/instrumenter.rb
    "ruby/gems/activesupport-3.2.12/lib/active_support/notifications.rb:123:in `inst
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/instrumentation.rb:29:i
    Line Number

    View Slide

  73. [
    "~/projects/triage/app/controllers/pages_controller.rb:6:in `index'",
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/implicit_render.rb:4:in
    "ruby/gems/actionpack-3.2.12/lib/abstract_controller/base.rb:167:in `process_act
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/rendering.rb:10:in `proc
    "ruby/gems/actionpack-3.2.12/lib/abstract_controller/callbacks.rb:18:in `block i
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:414:in `_run__37
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:405:in `__run_ca
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:385:in `_run_pro
    "ruby/gems/activesupport-3.2.12/lib/active_support/callbacks.rb:81:in `run_callb
    "ruby/gems/actionpack-3.2.12/lib/abstract_controller/callbacks.rb:17:in `process
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/rescue.rb:29:in `proces
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/instrumentation.rb:30:i
    "ruby/gems/activesupport-3.2.12/lib/active_support/notifications.rb:123:in `bloc
    "ruby/gems/activesupport-3.2.12/lib/active_support/notifications/instrumenter.rb
    "ruby/gems/activesupport-3.2.12/lib/active_support/notifications.rb:123:in `inst
    "ruby/gems/actionpack-3.2.12/lib/action_controller/metal/instrumentation.rb:29:i
    Method Name

    View Slide

  74. Use
    Kernel#caller

    View Slide

  75. Where is
    this method
    defined?
    Problem:

    View Slide

  76. Introducing
    My favorite
    super secret
    class

    View Slide

  77. Method

    View Slide

  78. Object#method

    View Slide

  79. > m = "hello world".method(:upcase)
    => #

    View Slide

  80. > m = "hello world".method(:upcase)
    => #
    > m.class
    => Method

    View Slide

  81. > m = "hello world".method(:upcase)
    => #
    > m.class
    => Method
    > m.call
    => "HELLO WORLD"

    View Slide

  82. Where is
    this method
    defined?
    Problem: (recap)

    View Slide

  83. Solution:
    Method#source_location

    View Slide

  84. $ cat app/models/user.rb
    class User < ActiveRecord::Base
    has_many :repo_subscriptions, dependent: :destroy
    has_many :repos, :through => :repo_subscriptions
    scope :public, where("private is not true")
    alias_attribute :token, :github_access_token
    def self.random
    order("RANDOM()")
    end
    #...

    View Slide

  85. $ rails c
    User.last.method(:github_url).source_location

    View Slide

  86. $ rails c
    User.last.method(:github_url).source_location
    =>["~/projects/triage/app/models/user.rb", 93]

    View Slide

  87. $ rails c
    User.last.method(:github_url).source_location
    =>["~/projects/triage/app/models/user.rb", 93]
    File

    View Slide

  88. $ rails c
    User.last.method(:github_url).source_location
    =>["~/projects/triage/app/models/user.rb", 93]
    Line Number

    View Slide

  89. Let’s see
    something
    real

    View Slide

  90. link_to in (rails)
    email templates
    needs a host set,
    but fails if you
    include http://
    Problem:

    View Slide

  91. Fails
    config.action_mailer.default_url_options = {
    host: 'http://example.com' }
    http://http://example.com

    View Slide

  92. Works
    config.action_mailer.default_url_options = {
    host: 'example.com' }
    http://example.com

    View Slide

  93. let’s remove the
    http://
    automatically,
    but how?
    Problem:

    View Slide

  94. How does that
    option get used?

    View Slide

  95. <% puts method(:link_to).source_location %>

    View Slide

  96. <% puts method(:link_to).source_location %>
    [“ruby/gems/actionpack-3.2.12/lib/action_view/helpers/
    url_helper.rb”
    231]

    View Slide

  97. $ bundle open
    $ bundle open actionpack

    View Slide

  98. def link_to(*args, &block)
    if block_given?
    options = args.first || {}
    html_options = args.second
    link_to(capture(&block), options, html_options)
    else
    name = args[0]
    options = args[1] || {}
    html_options = args[2]
    html_options = convert_options_to_data_attributes(options, html_options
    url = url_for(options)
    href = html_options['href']
    tag_options = tag_options(html_options)
    href_attr = "href=\"#{ERB::Util.html_escape(url)}\"" unless href
    "#{ERB::Util.html_escape(name || url)}end
    end
    {

    View Slide

  99. Enhance

    View Slide

  100. name = args[0]
    options = args[1] || {}
    html_options = args[2]
    html_options = convert_options_to_data_attributes(opt
    url = url_for(options)
    href = html_options['href']
    tag_options = tag_options(html_options)
    href_attr = "href=\"#{ERB::Util.html_escape(url)}\""
    "#{ERB::Util.html_escap

    View Slide

  101. name = args[0]
    options = args[1] || {}
    html_options = args[2]
    html_options = convert_options_to_data_attributes(opt
    url = url_for(options)
    href = html_options['href']
    tag_options = tag_options(html_options)
    href_attr = "href=\"#{ERB::Util.html_escape(url)}\""
    "#{ERB::Util.html_escap

    View Slide

  102. name = args[0]
    options = args[1] || {}
    html_options = args[2]
    html_options = convert_options_to_data_attributes(opt
    url = url_for(options)
    href = html_options['href']
    tag_options = tag_options(html_options)
    href_attr = "href=\"#{ERB::Util.html_escape(url)}\""
    "#{ERB::Util.html_escap

    View Slide

  103. Which
    url_for?

    View Slide

  104. View Slide

  105. View Slide

  106. 13 matches
    12 Files

    View Slide

  107. Method#
    source_location
    to the rescue

    View Slide

  108. name = args[0]
    options = args[1] || {}
    html_options = args[2]
    html_options = convert_options_to_data_attributes(opt
    puts “==============================”
    puts method(:url_for).source_location
    url = url_for(options)
    href = html_options['href']
    tag_options = tag_options(html_options)

    View Slide

  109. puts “==============================”
    puts method(:url_for).source_location
    =>
    [“actionpack-3.2.12/
    lib/
    action_view/helpers/url_helper.rb”,
    100]

    View Slide

  110. def url_for(options = {})
    options ||= {}
    case options
    when String
    options
    when Hash
    options = options.symbolize_keys.reverse_merge!(:
    super
    when :back
    controller.request.env["HTTP_REFERER"] || 'javasc
    else
    polymorphic_path(options)
    end
    end

    View Slide

  111. def url_for(options = {})
    options ||= {}
    case options
    when String
    options
    when Hash
    options = options.symbolize_keys.reverse_merge!(:
    puts "==========================="
    puts self.class.ancestors
    super
    when :back
    controller.request.env["HTTP_REFERER"] || 'javasc
    else
    polymorphic_path(options)
    end
    end

    View Slide

  112. Module#ancestors

    View Slide

  113. “foo”.class.ancestors
    => [String,
    Comparable,
    Object,
    Kernel, BasicObject]

    View Slide

  114. def url_for(options = {})
    options ||= {}
    case options
    when String
    options
    when Hash
    options = options.symbolize_keys.reverse_merge!(:
    puts "==========================="
    puts self.class.ancestors
    super
    when :back
    controller.request.env["HTTP_REFERER"] || 'javasc
    else
    polymorphic_path(options)
    end
    end

    View Slide

  115. puts "==========================="
    puts self.class.ancestors
    [#, ##, ActionMailer::MailHelper
    ActionDispatch::Routing::RouteSet::MountedHelpers,
    #, ActionDispatch::Routing:
    ActionView::Base, Devise::OmniAuth::UrlHelpers, Devi
    WillPaginate::ActionView, WillPaginate::ViewHelpers,
    ActionView::Helpers, ActionView::Helpers::Translatio
    ActionView::Helpers::RecordTagHelper, ActionControll
    ActionView::Helpers::OutputSafetyHelper, ActionView:
    ActionView::Helpers::JavaScriptHelper, ActionView::H
    ActionView::Helpers::FormHelper, ActionView::Helpers
    ActionView::Helpers::DebugHelper, ActionView::Helper
    ActionView::Helpers::ControllerHelper, ActionView::H
    ActionView::Helpers::AtomFeedHelper, ActionView::Hel

    View Slide

  116. View Slide

  117. Too much
    Ruby?

    View Slide

  118. Solution:
    Moar Ruby

    View Slide

  119. Module#method_defined?

    View Slide

  120. "foo".class.method_defined?(:upcase)
    => true
    Module#method_defined?

    View Slide

  121. "foo".class.method_defined?(:upcase)
    => true
    99.class.method_defined?(:upcase)
    => false
    Module#method_defined?

    View Slide

  122. Module#instance_method

    View Slide

  123. User.new.method(:github_url).source_location
    =>["~/projects/triage/app/models/user.rb", 93]
    User.instance_method(:github_url).source_location
    =>["~/projects/triage/app/models/user.rb", 93]
    Module#instance_method

    View Slide

  124. What were we
    doing?

    View Slide

  125. def url_for(options = {})
    options ||= {}
    case options
    when String
    options
    when Hash
    options = options.symbolize_keys.reverse_merge!(:
    puts "==========================="
    puts self.class.ancestors
    super
    when :back
    controller.request.env["HTTP_REFERER"] || 'javasc
    else
    polymorphic_path(options)
    end
    end

    View Slide

  126. Right

    View Slide

  127. def url_for(options = {})
    options ||= {}
    case options
    when String
    options
    when Hash
    options = options.symbolize_keys.reverse_merge!(:only
    puts "==========================="
    self.class.ancestors.each do |klass|
    next unless klass.method_defined?(:url_for)
    puts klass.instance_method(:url_for).source_locatio
    end
    super
    when :back
    controller.request.env["HTTP_REFERER"] || 'javascript
    else
    polymorphic_path(options)

    View Slide

  128. def url_for(options = {})
    options ||= {}
    case options
    when String
    options
    when Hash
    options = options.symbolize_keys.reverse_merge!(:only
    puts "==========================="
    self.class.ancestors.each do |klass|
    next unless klass.method_defined?(:url_for)
    puts klass.instance_method(:url_for).source_locatio
    end
    super
    when :back
    controller.request.env["HTTP_REFERER"] || 'javascript
    else
    polymorphic_path(options)

    View Slide

  129. def url_for(options = {})
    options ||= {}
    case options
    when String
    options
    when Hash
    options = options.symbolize_keys.reverse_merge!(:only
    puts "==========================="
    self.class.ancestors.each do |klass|
    next unless klass.method_defined?(:url_for)
    puts klass.instance_method(:url_for).source_locatio
    end
    super
    when :back
    controller.request.env["HTTP_REFERER"] || 'javascript
    else
    polymorphic_path(options)

    View Slide

  130. def url_for(options = {})
    options ||= {}
    case options
    when String
    options
    when Hash
    options = options.symbolize_keys.reverse_merge!(:only
    puts "==========================="
    self.class.ancestors.each do |klass|
    next unless klass.method_defined?(:url_for)
    puts klass.instance_method(:url_for).source_locatio
    end
    super
    when :back
    controller.request.env["HTTP_REFERER"] || 'javascript
    else
    polymorphic_path(options)

    View Slide

  131. self.class.ancestors.each do |klass|
    next unless klass.method_defined?(:url_for)
    puts klass.instance_method(:url_for).source_location
    end
    ======================
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_dispatch/routing/url_for.rb
    143

    View Slide

  132. ======================
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_dispatch/routing/url_for.rb
    143
    One of these
    things is not like
    the others

    View Slide

  133. ======================
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_dispatch/routing/url_for.rb
    143
    Nope

    View Slide

  134. ======================
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_dispatch/routing/url_for.rb
    143
    Nope

    View Slide

  135. ======================
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_dispatch/routing/url_for.rb
    143
    Nope

    View Slide

  136. ======================
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_view/helpers/url_helper.rb
    100
    actionpack-3.2.12/lib/action_dispatch/routing/url_for.rb
    143
    Yeah!

    View Slide

  137. Almost Done
    (kinda)

    View Slide

  138. Quick
    Break:

    View Slide

  139. View Slide

  140. View Slide

  141. Back to your
    regularly
    scheduled
    broadcast

    View Slide

  142. Verify Caller
    def url_for(options = nil)
    puts "==========================="
    puts caller.inspect
    case options
    when String
    options
    when nil, Hash
    _routes.url_for((options || {}).symbolize_keys.rev
    else
    polymorphic_url(options)
    end
    end

    View Slide

  143. It checks out
    ============================
    [“actionpack-3.2.12/
    lib/
    action_view/
    helpers/
    url_helper.rb:112:in `url_for'",
    # ...

    View Slide

  144. Follow the
    Source
    def url_for(options = nil)
    case options
    when String
    options
    when nil, Hash
    puts "==========================="
    puts _routes.method(:url_for).source_location
    _routes.url_for((options || {}).symbolize_keys.rev
    else
    polymorphic_url(options)
    end
    end

    View Slide

  145. action_dispatch/routing/route_set.rb:572
    def url_for(options)
    finalize!
    options = (options || {}).reverse_merge!(default_url_options)
    handle_positional_args(options)
    user, password = extract_authentication(options)
    path_segments = options.delete(:_path_segments)
    script_name = options.delete(:script_name)
    path = (script_name.blank? ? _generate_prefix(options) : script_name.chomp('/')
    path_options = options.except(*RESERVED_OPTIONS)
    path_options = yield(path_options) if block_given?
    path_addition, params = generate(path_options, path_segments || {})
    path << path_addition
    params.merge!(options[:params] || {})
    ActionDispatch::Http::URL.url_for(options.merge!({
    :path => path,
    :params => params,
    :user => user,
    :password => password
    }))
    end

    View Slide

  146. Success!

    View Slide

  147. Now What???

    View Slide

  148. Reproduce the
    problem

    View Slide

  149. Attempt a fix

    View Slide

  150. Raise
    Awareness
    (open an issue)

    View Slide

  151. rails/
    rails#9794

    View Slide

  152. View Slide