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

Ruby 2 in Ruby on Rails

Ruby 2 in Ruby on Rails

Slides for my keynote "Ruby 2 in Ruby on Rails" at RedDot RubyConf 2017 in Singapore https://www.reddotrubyconf.com/#amatsuda #rdrc2017

Akira Matsuda

June 23, 2017
Tweet

More Decks by Akira Matsuda

Other Decks in Programming

Transcript

  1. Ruby 2 in

    Ruby on Rails
    Akira Matsuda

    View full-size slide

  2. pp self

    name: Akira Matsuda

    GitHub: amatsuda

    Twitter: @a_matsuda

    From: Japan "

    View full-size slide

  3. A Ruby Committer

    Who lives in a "Ruby Village" in
    Tokyo

    "Many of the core contributors like Koichi Sasada,
    Shyouhei Urabe, Yui Naruse, Zachary Scott and
    Akira Matsuda live within 10-15 minutes of each
    other"
    https:/
    /appfolio-engineering.squarespace.com/appfolio-
    engineering/2017/5/24/how-is-ruby-different-in-japan

    View full-size slide

  4. A Rails Committer

    View full-size slide

  5. A Gem Author

    kaminari

    action_args

    active_decorator

    stateful_enum

    gem-src

    rfd

    etcetc.

    View full-size slide

  6. Chief Organizer of
    RubyKaigi

    View full-size slide

  7. RubyKaigi 2017

    September 18..20

    In Hiroshima

    Tickets are on sale!!
    http:/
    /rubykaigi.org/2017

    View full-size slide

  8. RubyKaigi 2017

    View full-size slide

  9. pp self

    name: Akira Matsuda

    GitHub: amatsuda

    Twitter: @a_matsuda

    From: Japan "

    Ruby: committer

    Rails: committer

    Gems: many

    RubyKaigi: the organizer

    View full-size slide

  10. Today I'm Going to

    Show You

    The new features that the Ruby
    team recently introduced to the
    language

    How the Rails framework uses
    these new features

    Some of my works as a
    "committer of both"

    View full-size slide

  11. Ruby & Rails Timeline
    Rails Ruby
    2.0.0 - Feb. 24, 2013
    4.0.0 - Jun. 25, 2013
    2.1.0 - Christmas, 2013
    4.1.0 - Apr. 08, 2014
    4.2.0 - Dec. 20, 2014
    2.2.0 - Christmas, 2014
    2.3.0 - Christmas, 2015
    5.0.0 - Jun. 30, 2016
    2.4.0 - Christmas, 2016
    5.1.0 - Apr. 27, 2017

    View full-size slide

  12. Ruby & Rails Timeline
    Rails Ruby
    2.0.0 - Feb. 24, 2013
    4.0.0 - Jun. 25, 2013
    2.1.0 - Christmas, 2013
    4.1.0 - Apr. 08, 2014
    4.2.0 - Dec. 20, 2014
    2.2.0 - Christmas, 2014
    2.3.0 - Christmas, 2015
    5.0.0 - Jun. 30, 2016
    2.4.0 - Christmas, 2016
    5.1.0 - Apr. 27, 2017

    View full-size slide

  13. Ruby & Rails Timeline

    Ruby 2 and Rails 4 came
    together

    Rails 5 supports only
    Ruby 2.2+

    View full-size slide

  14. Supported versions

    Rails 4.x: 1.9.3+

    Rails 5.x: 2.2.2+

    View full-size slide

  15. This Means...

    Rails 5.x (master) codebase
    can use Ruby <= 2.2 features

    Rails 5.x (master) codebase
    still cannot use Ruby 2.3, 2.4,
    2.5 features

    View full-size slide

  16. What Are New
    Features in Ruby 2?

    View full-size slide

  17. New Things in Ruby 2

    Core features

    Module#prepend, public include,
    Refinements, keyword arguments,
    frozen Strings, Symbol GC

    Syntax changes

    private def, %i, &., <<~, String#-@

    View full-size slide

  18. Module#prepend

    (2.0)

    View full-size slide

  19. This is Probably My
    Favorite Feature in Ruby 2

    View full-size slide

  20. Pre-prepend Era

    View full-size slide

  21. `alias_method_chain` in
    Active Support

    A great trick for
    extending an existing
    method

    View full-size slide

  22. Initial Implementation of
    `alias_method_chain`

    https:/
    /github.com/
    rails/rails/commit/
    794d93f7a5e
    class Module
    def alias_method_chain(target, feature)
    alias_method "#{target}_without_#{feature}", target
    alias_method target, "#{target}_with_#{feature}"
    end
    end

    View full-size slide

  23. Initial Implementation of
    `alias_method_chain`

    Back in 2006

    https:/
    /github.com/rails/rails/commit/
    794d93f7a5e

    View full-size slide

  24. Beauty of
    `alias_method_chain`

    Amazingly simple
    implementation

    Straightforward
    convention

    View full-size slide

  25. Usage of AMC
    gem 'activesupport', '4.2.8'
    require 'active_support/core_ext/module/aliasing'
    class C
    def foo() p:foo; end
    end
    class C
    def foo_with_bar() foo_without_bar; p:bar; end
    alias_method_chain :foo, :bar
    end
    C.new.foo
    #=> :foo, :bar

    View full-size slide

  26. Downside of AMC

    This feature is basically just a
    workaround

    Creates some publicly callable
    intermediate methods

    `foo_with_bar` and `foo_without_bar` in
    the example above

    Behavior of these methods are
    unpredictable

    View full-size slide

  27. public Methods
    gem 'activesupport', '4.2.8'
    require 'active_support/core_ext/module/aliasing'
    class C
    def foo() p:foo; end
    def foo_with_bar() foo_without_bar; p:bar; end
    alias_method_chain :foo, :bar
    end
    p C.instance_methods(false)
    #=> [:foo, :foo_with_bar, :foo_without_bar]

    View full-size slide

  28. What's Wrong with
    These public Methods?
    gem 'activesupport', '4.2.8'
    require 'active_support/core_ext/module/aliasing'
    class C
    def foo() p:foo; end
    def foo_with_bar() foo_without_bar; p:bar; end
    alias_method_chain :foo, :bar
    # defining bar first, then baz
    def foo_with_baz() foo_without_baz; p:baz; end
    alias_method_chain :foo, :baz
    end
    C.new.foo_without_bar
    #=> :foo

    View full-size slide

  29. What's Wrong with These
    public Methods? (2)
    gem 'activesupport', '4.2.8'
    require 'active_support/core_ext/module/aliasing'
    class C
    def foo() p:foo; end
    def foo_with_baz() foo_without_baz; p:baz; end
    alias_method_chain :foo, :baz
    # swapping the definition order of bar and baz
    def foo_with_bar() foo_without_bar; p:bar; end
    alias_method_chain :foo, :bar
    end
    C.new.foo_without_bar
    #=> :foo, :baz

    View full-size slide

  30. Can't Tell What's Gonna
    Happen with `foo_without_bar`

    But it's publicly callable

    View full-size slide

  31. Think About This Case

    You bundled two plugins that extend `foo()`
    method with AMC

    The "bar" plugin defines a public method
    `foo_without_bar`

    But the behavior is unpredictable

    The behavior depends on the order of plugin
    loading order

    So, if you change the sort order in Gemfile, the
    behavior would change

    View full-size slide

  32. We Needed a More Robust
    Monkey-patching Mechanism

    And so @wycats from
    the Rails team proposed
    `Module#prepend` as a
    language core feature

    View full-size slide

  33. Module#prepend as a
    Ruby Core Feature (2.0)
    class C
    def foo() p:foo; end
    end
    module M
    def foo() super; p:bar; end
    end
    C.send :prepend, M
    C.new.foo
    #=> :foo, :bar

    View full-size slide

  34. Very Clean Way of
    Monkey-patching

    No ugly `foo_with_bar`,
    `foo_without_bar` methods

    But you still can do that via
    `super_method` if you really want to

    The module name appears in
    `ancestors`

    Easier to debug

    View full-size slide

  35. There's A Pitfall Though

    You should never mix
    AMC and `prepend`

    View full-size slide

  36. What If We Mix AMC and
    `prepend`?
    gem 'activesupport', '4.2.8'
    require 'active_support/core_ext/module/aliasing'
    class C
    def foo() p:foo; end
    end
    # AMC first
    class C
    def foo_with_bar() foo_without_bar; p:bar; end
    alias_method_chain :foo, :bar
    end
    # Then prepend
    module M
    def foo() super; p:baz; end
    end
    C.send :prepend, M
    C.new.foo
    #=> :foo, :bar, :baz

    View full-size slide

  37. Looks Like It works!

    View full-size slide

  38. …Is It Working?
    gem 'activesupport', '4.2.8'
    require 'active_support/core_ext/module/aliasing'
    class C
    def foo() p:foo; end
    end
    # prepend first
    module M
    def foo() super; p:baz; end
    end
    C.send :prepend, M
    # Then AMC
    class C
    def foo_with_bar() foo_without_bar; p:bar; end
    alias_method_chain :foo, :bar
    end
    C.new.foo
    #=> stack level too deep

    View full-size slide

  39. AMC and `prepend`
    Cannot Co-exist

    It's dangerous to use both
    AMC and `prepend` against a
    single method

    The result is not only
    unpredictable

    Sometimes critical ☠

    View full-size slide

  40. AMC is Dead

    Rails terminated the use of AMC
    in Rails 5

    Plugin authors must abandon
    AMC and switch to `prepend`

    App developers should better
    refrain from bundling any plugin
    that still uses AMC

    View full-size slide

  41. As a Plugin Author

    We're forced to support
    `prepend`

    It's hard to maintain a
    codebase that supports
    both AMD and `prepend`

    View full-size slide

  42. Ruby 1's End

    Many gems stopped
    supporting Ruby 1.x due to this

    This brought the extinction of
    Ruby 1.x to the community

    This is another reason that I
    like `Module#prepend`

    View full-size slide

  43. Module#include and
    Module#prepend as
    public Methods (2.1)

    View full-size slide

  44. The `prepend` Code I Showed You
    Before Has a Small Room for
    Improvement

    View full-size slide

  45. The `send :include`
    Idiom
    C.send :include, M
    C.send :prepend, M

    View full-size slide

  46. `send :include`

    This had been the basic
    idiom for extending an
    existing Module for long
    time

    View full-size slide

  47. Rails Plugin Authors Use
    This Idiom Too Often

    I wanted to do this
    simpler

    So I proposed to make
    them public

    View full-size slide

  48. My Proposal
    https:/
    /bugs.ruby-lang.org/
    issues/8846

    And Matz accepted the
    proposal (although he
    didn't actually like it)

    View full-size slide

  49. public `include` Is Available in
    All Supported Versions of Ruby

    And it's everywhere in
    Rails' codebase and
    plugins already

    You may forget the
    `send :include` idiom and
    just use the public `include`

    View full-size slide

  50. Refinements
    (2.0)

    View full-size slide

  51. Who Have Ever Actually Used
    This Feature in Production


    View full-size slide

  52. Real World Usage in
    Rails Plugins

    action_args

    database_rewinder

    etc.

    View full-size slide

  53. database_rewinder's
    case (refine)
    # lib/database_rewinder/multiple_statements_executor.rb
    module DatabaseRewinder
    module MultipleStatementsExecutor
    refine ActiveRecord::ConnectionAdapters::AbstractAdapte
    do
    def execute_multiple(sql)
    ...
    ennnnd

    View full-size slide

  54. database_rewinder's
    case (using)
    # lib/database_rewinder/cleaner.rb
    using DatabaseRewinder::MultipleStatementsExecutor
    ...
    private def delete_all(ar_conn, tables, multiple: true)
    ...
    if multiple
    ar_conn.execute_multiple sql
    ...

    View full-size slide

  55. A Gem Internal Public
    Method

    The `execute_multiple` method
    in the example is

    - A public method

    - Visible only inside the gem

    - Invisible from the
    application code

    View full-size slide

  56. Super private Monkey-
    patching

    Very useful feature for plugin
    authors / monkey-patchers

    Since it's "file scoped", we
    can define any method that
    is only available inside the
    plugin

    View full-size slide

  57. Refinements in Rails

    I introduced refinements
    in Active Support 5.1
    codebase

    To extend `Array#sum`
    https:/
    /github.com/rails/rails/pull/27363

    View full-size slide

  58. The `Array#sum` Patch
    - class Array
    - alias :orig_sum :sum
    - end
    + # Using Refinements here in order not to expose our
    internal method
    + using Module.new {
    + refine Array do
    + alias :orig_sum :sum
    + end
    + }

    View full-size slide

  59. In This Case

    The previous code was defining a
    public `Array#orig_sum` method

    Although it was an internal method for
    calculating `Array#sum`

    The method used to be visible to all
    Active Support users

    Refinements made it perfectly "file
    scoped"

    View full-size slide

  60. File Scoped!

    File Scoped?

    While implementing
    `Array#sum`, I realized that
    it's not actually file scoped!

    And so I reported what I
    found
    https:/
    /bugs.ruby-lang.org/issues/13109

    View full-size slide

  61. I Told You That Refinements
    is "File Scoped"

    View full-size slide

  62. But I'm Sorry That Was a
    Lie

    View full-size slide

  63. The Scope Problem

    This happens when
    you're putting
    everything in one file

    View full-size slide

  64. This Works
    # Put a refinement first
    using Module.new {
    refine Object do
    def foo() p 'hello'; end
    end
    }
    # Then calling the method defined in the refinement
    class Object
    def bar() foo; end
    end
    Object.new.bar
    #=> "hello"

    View full-size slide

  65. But This Doesn't Work!
    # Put a method call first
    class Object
    def bar() foo; end
    end
    # Then define the actual method definition in a refinement
    using Module.new {
    refine Object do
    def foo() p 'hello'; end
    end
    }
    Object.new.bar
    #=> doesnot_work.rb:2:in `bar': undefined local variable or
    method `foo' for # (NameError)

    View full-size slide

  66. So The Scope Is Not
    Actually "File Scope"

    It's "file scope + physical
    position of using" scope

    I thought it's a bug, but Matz
    said "it's an intended behavior"

    View full-size slide

  67. This Behavior Is
    Sometimes Annoying

    I said refinements is useful for
    creating a library-internal "super
    private" method

    But when you define a private
    method, you usually put it below the
    public methods, right?

    However, refinements definition has
    to be put at the top of the file

    View full-size slide

  68. Such a Weird Feature!

    Probably the weirdest
    feature in Ruby that I
    know

    View full-size slide

  69. And… This Complex Behavior
    Is Still Undocumented for Now

    Maybe this behavior is very
    hard to explain

    Or maybe because nobody
    was aware of this beavior

    Anyway this is the "intended
    behavior"

    View full-size slide

  70. This Funny Feature Was Proposed
    And Implemented by Shugo

    Matz's boss at NaCl

    View full-size slide

  71. The protected
    Scope

    View full-size slide

  72. Another Funny Feature
    Created by Shugo

    View full-size slide

  73. Example (Taken from
    Shugo's RubyKaigi Slides)
    class BinaryCodedDecimal
    def +(other)
    figure1 = self.figure
    figure2 = other.figure
    ...
    end
    protected
    attr_accessor :figure
    end

    View full-size slide

  74. What Matz Thinks About
    protected

    "(If I would re-design Ruby)
    now? I'll reject protected"

    https:/
    /twitter.com/yukihiro_matz/status/
    180090910451834881

    View full-size slide

  75. Usage of protected in
    Rails

    Almost everyone misuses
    `protected`

    In most cases, you can just
    rewrite your `protected` to
    `private`, and it should still
    work

    View full-size slide

  76. Removal of protected in
    Rails

    I tried doing this for the
    Rails' codebase

    View full-size slide

  77. The Huge Patch

    3.years.ago

    https:/
    /github.com/amatsuda/rails/commit/
    6832343c

    View full-size slide

  78. I Made This But I Didn't
    Push This to The Upstream

    Although all tests were
    passing

    Because it caused some
    unintentional update on
    the API document

    View full-size slide

  79. Finally I Pushed Them
    Last Year

    Made a separate commit per each
    component

    Making sure that tests didn't break
    per each component

    Adding `:doc:` to all previously
    `protected` methods

    Last year, for Rails 5.1

    View full-size slide

  80. protected

    Dead in Rails master

    Don't use it

    View full-size slide

  81. Shugo, The Creator of
    Refinements And protected

    His most recent project

    View full-size slide

  82. A Text Editor Written in
    Ruby

    Based on curses Ruby binding

    That he created and he still
    maintains

    He's going to do a
    presentation about this at an
    international Ruby conference

    View full-size slide

  83. RubyKaigi 2017

    View full-size slide

  84. private def (2.1)

    View full-size slide

  85. Defining a private
    Method (1)
    class Klass
    ...
    # Changes the visibility of all methods defined below
    private
    def private_method; end
    end

    View full-size slide

  86. Defining a private
    Method (2)
    class Klass
    ...
    def private_method; end
    # Changes the visibility of this single method
    private :private_method
    end

    View full-size slide

  87. The private def Syntax
    class Klass
    ...
    # Now we can do this in one line.

    # Don't have to repeat the method name!
    private def private_method; end
    end

    View full-size slide

  88. usa's Patch

    `def` statement used to return nil, but
    he changed it to return the defined
    method name in Symbol
    https:/
    /github.com/ruby/ruby/commit/0f0b60ea86

    `private` (`protected`, `public`) takes a
    Symbol and changes the method's
    visibility

    The patch was written in August 2013

    View full-size slide

  89. This Feature Is a Very Important
    Feature Especially for The Rails Team

    View full-size slide

  90. The Rails Style
    class Klass
    ...
    private
    def private_method; end
    end

    View full-size slide

  91. VS. Traditional Style
    class Klass
    ...
    private
    def private_method; end
    end

    View full-size slide

  92. Everybody Is Happy with
    This, Right?
    class Klass
    ...
    private def private_method; end
    end

    View full-size slide

  93. That's Why I Said This Feature
    Is Very Important for Rails Team

    View full-size slide

  94. `private def` in Rails

    Very rarely used

    View full-size slide

  95. Still Only 17 Occurrencies
    in Rails! (As of Today)
    % git grep -c "private def"
    actioncable/test/channel/stream_test.rb:1
    actionmailer/test/abstract_unit.rb:2
    actionpack/test/abstract_unit.rb:2
    actionpack/test/controller/new_base/render_context_test.rb:
    actionview/test/abstract_unit.rb:2
    actionview/test/template/form_helper/form_with_test.rb:1
    activemodel/test/cases/helper.rb:2
    activerecord/lib/active_record/connection_adapters/
    abstract_mysql_adapter.rb:1
    activerecord/test/cases/query_cache_test.rb:1
    activesupport/test/abstract_unit.rb:2
    railties/test/abstract_unit.rb:2

    View full-size slide

  96. Because There Was a
    Documentation Problem
    class Klass
    def public_method_1; end
    private def private_method; end
    def public_method_2; end
    end

    View full-size slide

  97. The RDoc Problem

    RDoc couldn't properly generate
    the document

    The `private` (`protected`, `public`)
    prefix was changing the scope of
    all following methods

    It breaks the Rails API document
    https:/
    /github.com/rdoc/rdoc/issues/355

    View full-size slide

  98. The RDoc Problem
    class Klass
    def public_method_1; end
    private def private_method; end
    # This method is public and has to appear in the doc, but
    RDoc was treating this as a private method
    def public_method_2; end
    end

    View full-size slide

  99. Why Did RDoc Have

    Such Bug?

    Because RDoc has its
    own Ruby parser

    Nobody made a change
    to that parser along with
    the change in Ruby 2.1

    View full-size slide

  100. The Parser (Lexer) in
    RDoc

    lib/rdoc/ruby_lex.rb

    View full-size slide

  101. lib/rdoc/ruby_lex.rb
    # coding: US-ASCII
    # frozen_string_literal: false
    #--
    # irb/ruby-lex.rb - ruby lexcal analyzer
    # $Release Version: 0.9.5$
    # $Revision: 17979 $
    # $Date: 2008-07-09 10:17:05 -0700 (Wed, 09 Jul 2008) $
    # by Keiju ISHITSUKA([email protected])
    #
    #++
    require "e2mmap"
    require "irb/slex"
    require "stringio"
    ##
    # Ruby lexer adapted from irb.
    #
    # The internals are not documented because they are scary.
    class RDoc::RubyLex
    ...

    View full-size slide

  102. lib/rdoc/ruby_lex.rb

    It's a "ruby lexcal analyzer"

    Written by keiju

    "adapted from irb"

    OMG! RDoc requires IRB!

    View full-size slide

  103. lib/rdoc/ruby_lex.rb
    # coding: US-ASCII
    # frozen_string_literal: false
    #--
    # irb/ruby-lex.rb - ruby lexcal analyzer
    # $Release Version: 0.9.5$
    # $Revision: 17979 $
    # $Date: 2008-07-09 10:17:05 -0700 (Wed, 09 Jul 2008) $
    # by Keiju ISHITSUKA([email protected])
    #
    #++
    require "e2mmap"
    require "irb/slex"
    require "stringio"
    ##
    # Ruby lexer adapted from irb.
    #
    # The internals are not documented because they are scary.
    class RDoc::RubyLex
    ...
    WTF

    View full-size slide

  104. Parsers in Ruby

    parse.y (+ yacc, bison)

    Ripper

    ruby-lex.rb in IRB

    View full-size slide

  105. Ruby has Ripper in stdlib

    Written by Minero Aoki,
    the author of "Ruby
    Hacking Guide"

    View full-size slide

  106. But Why Didn't RDoc Use
    Ripper?

    Because Ripper was not
    yet created when Dave
    Thomas created RDoc

    View full-size slide

  107. Parsers History

    IRB: 1997

    RDoc: 2003

    Ripper: 2004

    View full-size slide

  108. I Tried, But...

    Shouldn't we rewrite the RDoc
    Ruby parser from the IRB parser to
    Ripper?

    I tried to do this before, but gave
    up

    Because the RDoc parser is a
    super chaotic legacy code

    View full-size slide

  109. Future Work

    But I brought this to
    Asakusa.rb, and someone
    is actually working on this

    The result is supposed to
    be presented soon at

    View full-size slide

  110. RubyKaigi 2017

    View full-size slide

  111. Parsers History (Reprise)

    IRB: 1997

    RDoc: 2003

    Ripper: 2004

    View full-size slide

  112. IRB

    Created in 1997

    It's IRB's 20th
    anniversary this year!

    View full-size slide

  113. Let's Celebrate!

    There'll be an IRB 20th
    anniversary speech by
    the original author

    View full-size slide

  114. RubyKaigi 2017

    View full-size slide

  115. Anyway,

    Let's go back to the
    `private def` problem

    View full-size slide

  116. I Gave Up Replacing The
    Whole RDoc Parser

    But I could make a tiny
    patch for RDoc to add
    `private def` support last
    year

    View full-size slide

  117. My Patch
    https:/
    /github.com/rdoc/
    rdoc/pull/435

    View full-size slide

  118. It Took 3 And Half Years

    Since `private def` syntax has been
    introduced to Ruby

    But it's finally unleashed in Rails

    RDoc >= 5.1 can properly parse
    `private def`

    So it won't break the document any
    longer

    View full-size slide

  119. Feel Free to Use

    `private def` Now!

    View full-size slide

  120. Keyword
    Arguments (2.0)

    View full-size slide

  121. Pseudo Keyward Arguments
    in Rails (As a Hash)
    def deliver_later(options = {})
    def form_for(record, options = {}, &block)
    def link_to_if(condition, name, options = {},
    html_options = {}, &block)

    View full-size slide

  122. Pseudo Keyward Arguments
    in Rails (Through Varargs)
    def accepts_nested_attributes_for(*attr_names)
    def cycle(first_value, *values)
    def redirect(*args, &block)

    View full-size slide

  123. Pseudo Keyward
    Arguments in Rails

    Uses a Ruby Hash to pass in
    arguments with names

    Method calls apparently look
    better than something like

    `form_for(@user, nil,
    false, :post, false, nil, false)`

    View full-size slide

  124. Pseudo Keyward
    Arguments in Rails

    But OTOH the method definition looks
    miserable

    The argument list becomes like
    "options as any Hash", or even "*args"

    It tells nothing unless we write a
    thorough documentation comment

    So hard for this non-statically typed
    language

    View full-size slide

  125. Rails Includes Some
    Tools

    To handle this psuedo
    keyword arguments

    View full-size slide

  126. Extracting the Options
    Hash from Varargs
    # actionview/lib/action_view/helpers/text_helper.rb
    def cycle(first_value, *values)
    options = values.extract_options!
    ...
    end

    View full-size slide

  127. Giving a Default Option
    Value
    # actionpack/lib/action_dispatch/routing/redirection.rb
    def redirect(*args, &block)
    options = args.extract_options!
    status = options.delete(:status) || 301
    ...

    View full-size slide

  128. Giving a Default Option
    Value

    The defaults have to be
    manipulated inside the
    method

    And they have to be
    properly documented
    outside of the method

    View full-size slide

  129. Verifying Option Keys
    # activerecord/lib/active_record/nested_attributes.rb
    def accepts_nested_attributes_for(*attr_names)
    options = { allow_destroy: false, update_only: false }
    options.update(attr_names.extract_options!)
    options.assert_valid_keys(:allow_destroy, :reject_if, :l
    imit, :update_only)

    View full-size slide

  130. assert_valid_keys

    Raises when an unexpected key was
    given

    Again, have to be declared inside the
    method, and be documented outside

    We very often forget to do this

    Then unhandled keys would just be
    ignored

    View full-size slide

  131. Ruby 2 Has The Built-in
    Keyword Arguments!

    Since 2.0

    Proposed and
    implemented by mame,
    the insane genius

    View full-size slide

  132. Rewriting with Ruby 2
    Kwargs (before)
    # activesupport/lib/active_support/message_verifier.rb
    def initialize(secret, options = {})
    raise ArgumentError, "Secret should not be nil." unless
    secret
    @secret = secret
    @digest = options[:digest] || "SHA1"
    @serializer = options[:serializer] || Marshal
    end

    View full-size slide

  133. Rewriting with Ruby 2
    Kwargs (patch)
    -def initialize(secret, options = {})
    +def initialize(secret, digest: 'SHA1', serializer:
    Marshal)
    raise ArgumentError, "Secret should not be nil." unless
    secret
    @secret = secret
    - @digest = options[:digest] || 'SHA1'
    - @serializer = options[:serializer] || Marshal
    + @digest = digest
    + @serializer = serializer
    end

    View full-size slide

  134. Merits of Ruby 2 Kwargs

    The logic becomes simple (obviously)

    The behavior becomes more strict
    (invalid keys, missing keys, etc.)

    Runs faster (in most cases, I guess)

    Creates less garbage objects (maybe)

    The method definition becomes a great
    documentation (most important IMO)

    View full-size slide

  135. A Branch that I Created for
    Experimenting Kwargs in Rails
    https:/
    /github.com/amatsuda/rails/tree/
    kwargs

    Back in Jan. 2013

    Before Ruby 2.0.0 stable release

    In order to experiment the new
    feature in a real codebase

    View full-size slide

  136. Then I Found A Problem
    While Experimenting This

    Some methods in Rails
    take Ruby keyword as
    an option name

    (e.g. if, unless, end)

    View full-size slide

  137. This Method Can Be
    Defined, And Can Be Called
    def x(if: nil)
    end
    x if: true

    View full-size slide

  138. But We Can Never Access
    The Given Parameter
    def x(if: nil)
    p if
    end
    x if: true
    #=> syntax error, unexpected keyword_end

    View full-size slide

  139. Kwargs Introduced A New Way
    to Define An Unaccessible Lvar

    `if = 1` is not a valid Ruby
    code, so this was never
    possible

    Kwargs opened a new
    possibility!

    View full-size slide

  140. I Broght This Problem to
    ruby-core

    (Couldn't find a ticket. Maybe at
    the developers' meeting, or
    maybe I discussed with ko1 or
    mame in person?)

    Then we introduced a
    new feature in 2.1

    View full-size slide

  141. Binding#local_variable_
    get (2.1)
    def x(if: nil)
    p binding.local_variable_get :if
    end
    x if: true
    #=> true

    View full-size slide

  142. You Can Also Set
    def x(nil: 1)
    binding.local_variable_set :nil, 2
    p binding.local_variables
    p nil
    p binding.local_variable_get :nil
    end
    x
    #=> [:nil], nil, 2

    View full-size slide

  143. My Proposal for Rails

    View full-size slide

  144. Kwags as a 1st Citizen
    API in Rails

    View full-size slide

  145. Kwags as a 1st Citizen
    API in Rails

    Should run faster

    Makes the code more readable

    And generates a better API
    document

    Rails master now supports only >=
    2.1 (actually 2.2.2) that does have
    `local_variable_get`

    View full-size slide

  146. require_relative
    (1.9)

    View full-size slide

  147. require_relative and
    Rails

    This feature was originally
    introduced to Ruby because Ruby
    excluded '.' from `$LOAD_PATH`

    Because '.' in `$LOAD_PATH` may
    be a potential security risk

    A "Rails effect" to the Ruby
    language

    View full-size slide

  148. require_relative May Run
    Faster Than require

    Because it's a very simple
    implementation

    It directly requires a file from
    `rb_current_realfilepath`

    Instead of scanning the file
    through all `$LOAD_PATH` entries

    View full-size slide

  149. Occurrences of `require` and
    `require_relative` in Rails

    (As of Today)

    '^require_relative': 73

    '^require ': 4055

    View full-size slide

  150. So, There's a Huge Room
    for an Improvement?

    I thought so before

    So I created a patch

    View full-size slide

  151. require_relative Patch
    for Rails

    Another huge patch
    from me that was never
    pushed to the upsteram
    https:/
    /github.com/amatsuda/
    rails/compare/
    master...require_relative

    View full-size slide

  152. Why Didn't I Push This

    Because I couldn't see
    any significant
    performance
    improvement

    View full-size slide

  153. Why No Perf
    Improvement?

    I haven't investigated deeper,
    but ko1 said maybe because
    Bundler is doing a good work(?)

    Or maybe I had better use a
    slow HDD when
    benchmarking?

    View full-size slide

  154. Anyway,

    `require_relative` is
    basically a good thing

    Although it's not popular
    in Rails

    View full-size slide

  155. ISeq#to_binary,
    ISeq.load_from_binary
    (2.3)

    View full-size slide

  156. Public APIs to Dump & Restore
    Compiled Instruction Sequences

    View full-size slide

  157. Yomikomu by ko1 (2015)

    An experimental gem
    https:/
    /github.com/ko1/yomikomu

    No significant perf
    improvement on real-world
    apps (ko1@RailsConf 2016)

    View full-size slide

  158. bootsnap (2017)

    Shopify's refined version of
    yomikomu

    With actual perf improvement!

    Proposed to be bundled in Rails'
    default Gemfile
    https:/
    /github.com/rails/rails/pull/29313

    Still misterious

    View full-size slide

  159. But,

    I heard that more about this
    is going to be presented soon

    View full-size slide

  160. RubyKaigi 2017

    View full-size slide

  161. Encoding (1.9-)

    View full-size slide

  162. Encoding in Ruby

    Every String object has its encoding

    Ruby can handle various encodings

    `Encoding.list.size` #=> 101

    Even matz had been using non-utf8
    Encoding until several years ago

    View full-size slide

  163. Encoding in Rails

    Rails has to face with various encodings

    Such as server OS encoding, DB encoding,
    program file encoding, HTTP request
    encoding, URL encoding, Cookie encoding

    Rails is a web framework, and the recent
    web defacto encoding is UTF-8

    Rails deals with YAML and JSON files, both
    of which supports only UTF (usualy UTF-8)

    View full-size slide

  164. Basic Strategy

    Keep every String in the
    Rails world be UTF-8

    Convert all non-UTF-8
    Strings to UTF-8 at the
    boundary between the
    outside and Rails

    View full-size slide

  165. Rails Needs to Be Defensive
    Against Inputs from Outside

    For example we're
    validating URLs not to
    include any broken
    (possibly maliscious)
    UTF-8 chars

    View full-size slide

  166. View Templates

    Template file encoding

    Magic comment at the
    top of the template

    View full-size slide

  167. Rails Seems to Be Supporting
    Multiple Encodings

    But nobody actually uses any other
    encoding than UTF-8, even in Japan

    I'm willing to drop multiencodings
    support from Action View

    I told this last year, but still I haven't
    published a patch

    I'm sorry, I'm so lazy...

    View full-size slide

  168. What I Only Did

    _.html.erb
    https:/
    /github.com/rails/rails/
    commit/da9038eaa5d

    View full-size slide

  169. ActiveSupport::Multibyt
    e

    Active Support includes an original
    multibyte chars handling utilities

    Since Ruby 1.8 era

    Includes a huge set of Unicode
    data in Active Support for this
    feature

    AS/values/unicode_tables.dat

    View full-size slide

  170. Improvements in Ruby

    1.9: Code Set Independent Encoding
    system

    2.0: UTF-8 script encoding by default

    2.2: Unicode normalization

    2.4: Non-ASCII case conversion

    2.4: Unicode 9.0 support in Regexp \X

    View full-size slide

  171. We're Ready to Remove Active
    Support Original Multibyte Library

    We can do everything using the language core features

    We can remove unicode_tables.dat from Active Support

    Smaller gem size

    Faster boot time

    Less memory consumption

    Multibyte chars handling will become faster

    Because it's implemented in C in Ruby

    No longer need to maintain that misterious code in
    Active Support

    View full-size slide

  172. Actually, We Have to Drop
    It As Soon As Possible

    Because @norman the long time maintainer
    has "completely retired from open source"

    And so I became the main maintainer of
    Haml after him

    View full-size slide

  173. Removing
    ActiveSupport::Multibyte

    Rails 5 still supports Ruby < 2.4

    It can't be completely removed in Rails
    5

    But we can use Ruby native
    implementation if RUBY_VERSION >= '2.4'

    We made a PR for this already

    https:/
    /github.com/rails/rails/pull/28067

    View full-size slide

  174. Frozen Strings
    (2.3)

    View full-size slide

  175. String#freeze PRs

    Got so many "I froze some
    String literals" PRs in the
    past few years

    Because freezing Strings
    resulted in good micro-
    benchmark results

    View full-size slide

  176. String#freeze Everywhere


    Pro: Some kind of
    performance
    improvement

    Con: Ugly code

    View full-size slide

  177. No Thank You, This Looks
    Too Ugly...
    # inflector/methods.rb
    def underscore(camel_cased_word)
    return camel_cased_word unless /[A-Z-]|::/.match?
    (camel_cased_word)
    word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze)
    word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)
    (#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 &&
    '_'.freeze }#{$2.downcase}" }
    word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
    word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
    word.tr!("-".freeze, "_".freeze)
    word.downcase!
    word
    end

    View full-size slide

  178. So, I Proposed to Introduce a
    New "Magic Comment" in 2015

    To be more accurate, I
    proposed to reconsider akr's
    proposal once rejected in 2013
    https:/
    /bugs.ruby-lang.org/issues/
    8976#note-30

    The proposal was accepted,
    and included in Ruby 2.3

    View full-size slide

  179. The Magic Comment
    # frozen_string_literal: true

    View full-size slide

  180. The "fstring" Literal
    p 'a'.frozen?
    p 'a'.freeze.frozen?
    p (-'a').frozen?

    View full-size slide

  181. The "fstring" Literal

    Maybe you've never heard of
    this before

    Actually a little bit faster than
    calling `String#freeze` method

    Very rarely used, but introduced
    in ERB recently by @k0kubun

    View full-size slide

  182. I Would Like to Put The Magic
    Comment to All Files in Rails

    But not now, because Rails
    5.x still supports Ruby 2.2

    This can be done when
    Rails drops Ruby 2.2
    support in the next major

    View full-size slide

  183. Symbol GC (2.2)

    View full-size slide

  184. Rails 5 Was Decided to Support
    Ruby 2.2+ Because of This Feature

    GC collects dynamically
    created Symbols

    Disables Symbol fixation
    attack

    View full-size slide

  185. Another Thing We Might
    Be Able to Do

    Reduce redundunt
    Stringifications

    May cause some compatibility
    issues but worth trying

    I haven't done anything yet
    though

    View full-size slide

  186. Integer
    Unification (2.4)

    View full-size slide

  187. Fixnum & Bignum =>
    Integer

    Use of Fixnum and Bignum
    has been deprecated

    We saw massive number of
    warnings in the gems

    Almost fixed in major
    libraries

    View full-size slide

  188. If you're Still Seeing the
    Warning

    Update bundled gems

    Patch it and PR if master is
    still not fixed

    Unbundle it and find a
    maintained alternative if your
    PR is left unmerged

    View full-size slide

  189. Active Support as an
    Experimental Lab for
    Ruby

    View full-size slide

  190. Object#try! (2.3)

    `&.`

    `try!` can be deprecated
    in favor of `&.` when we
    dropped 2.2 support

    View full-size slide

  191. String#strip_heredoc
    (2.3)

    `<<~`

    `strip_heredoc` can be
    deprecated in favor of
    `<<~` when we dropped
    2.2 support

    View full-size slide

  192. Numeric#positive?,
    negative? (2.3)

    Another trivial Active
    Support to Ruby pair of
    methods

    View full-size slide

  193. Array#append,
    Array#prepend (2.5)

    "Active Support can
    serve as an experimental
    lab for future core
    features in Ruby ❤"
    https:/
    /twitter.com/dhh/status/
    871034291786002433

    View full-size slide

  194. Array#sum,
    Enumerable#sum

    Ruby version is slightly different from
    the original Active Support version

    The Ruby version ignores non-
    numeric value

    It's OK because Ruby is a general
    purpose language, while Active
    Support is "web specific" language
    extension

    View full-size slide

  195. But The Situation Is So
    Complicated!!

    Rails 5 supports both Rubies that have and don't
    have `sum`

    `sum` is defined on both Enumerable and Array,
    where `Enumerable < Array`

    Native `sum` is faster, so it's preferrable because it's
    written in C

    Even if there's native `sum`, it has to fall back to
    `super` on non-numeric value

    It takes an argument

    It takes a block

    View full-size slide

  196. Somehow Done Before
    Releasing 5.1.0 Stable

    Not perfect, but works

    Can be made simpler
    when we dropped Ruby
    < 2.3 support

    View full-size slide

  197. Ruby 2.3, 2.4, and 2.5
    Features

    These Features Cannot Yet Be Used In The
    Rails Codebase

    Because Rails master still supports 2.2

    But you can use them in your apps!

    Update your production Ruby version to the
    newest stable, and start using these features
    now!

    There's no reason to stay on old version of
    Ruby

    View full-size slide

  198. Wrap Up

    Ruby is evolving day by day

    Rails is also getting better

    Ruby and Rails are in a very good relationship

    I'm having fun working on both projects

    There're always so many things to do
    because both Ruby and Rails are always
    changing

    I hope you to join us if you're interested!

    View full-size slide

  199. Thank You for Having Me

    And see you at another
    conference!

    Hopefully,

    View full-size slide

  200. RubyKaigi 2017

    View full-size slide

  201. pp self

    name: Akira Matsuda

    GitHub: amatsuda

    Twitter: @a_matsuda

    View full-size slide