Slide 1

Slide 1 text

Ruby 2 in
 Ruby on Rails Akira Matsuda

Slide 2

Slide 2 text

pp self name: Akira Matsuda GitHub: amatsuda Twitter: @a_matsuda From: Japan "

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

A Rails Committer

Slide 5

Slide 5 text

A Gem Author kaminari action_args active_decorator stateful_enum gem-src rfd etcetc.

Slide 6

Slide 6 text

Chief Organizer of RubyKaigi

Slide 7

Slide 7 text

RubyKaigi 2017 September 18..20 In Hiroshima Tickets are on sale!! http:/ /rubykaigi.org/2017

Slide 8

Slide 8 text

RubyKaigi 2017

Slide 9

Slide 9 text

pp self name: Akira Matsuda GitHub: amatsuda Twitter: @a_matsuda From: Japan " Ruby: committer Rails: committer Gems: many RubyKaigi: the organizer

Slide 10

Slide 10 text

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"

Slide 11

Slide 11 text

begin

Slide 12

Slide 12 text

Versions

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Ruby & Rails Timeline Ruby 2 and Rails 4 came together Rails 5 supports only Ruby 2.2+

Slide 16

Slide 16 text

Supported versions Rails 4.x: 1.9.3+ Rails 5.x: 2.2.2+

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

What Are New Features in Ruby 2?

Slide 19

Slide 19 text

New Things in Ruby 2 Core features Module#prepend, public include, Refinements, keyword arguments, frozen Strings, Symbol GC Syntax changes private def, %i, &., <<~, String#-@

Slide 20

Slide 20 text

Module#prepend
 (2.0)

Slide 21

Slide 21 text

This is Probably My Favorite Feature in Ruby 2

Slide 22

Slide 22 text

Pre-prepend Era

Slide 23

Slide 23 text

`alias_method_chain` in Active Support A great trick for extending an existing method

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Initial Implementation of `alias_method_chain` Back in 2006 https:/ /github.com/rails/rails/commit/ 794d93f7a5e

Slide 26

Slide 26 text

Beauty of `alias_method_chain` Amazingly simple implementation Straightforward convention

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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]

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Can't Tell What's Gonna Happen with `foo_without_bar` But it's publicly callable

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

We Needed a More Robust Monkey-patching Mechanism And so @wycats from the Rails team proposed `Module#prepend` as a language core feature

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

There's A Pitfall Though You should never mix AMC and `prepend`

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Looks Like It works!

Slide 40

Slide 40 text

…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

Slide 41

Slide 41 text

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 ☠

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

As a Plugin Author We're forced to support `prepend` It's hard to maintain a codebase that supports both AMD and `prepend`

Slide 44

Slide 44 text

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`

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

`send :include` This had been the basic idiom for extending an existing Module for long time

Slide 49

Slide 49 text

Rails Plugin Authors Use This Idiom Too Often I wanted to do this simpler So I proposed to make them public

Slide 50

Slide 50 text

My Proposal https:/ /bugs.ruby-lang.org/ issues/8846 And Matz accepted the proposal (although he didn't actually like it)

Slide 51

Slide 51 text

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`

Slide 52

Slide 52 text

Refinements (2.0)

Slide 53

Slide 53 text

Who Have Ever Actually Used This Feature in Production

Slide 54

Slide 54 text

Real World Usage in Rails Plugins action_args database_rewinder etc.

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Refinements in Rails I introduced refinements in Active Support 5.1 codebase To extend `Array#sum` https:/ /github.com/rails/rails/pull/27363

Slide 60

Slide 60 text

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 + }

Slide 61

Slide 61 text

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"

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

I Told You That Refinements is "File Scoped"

Slide 64

Slide 64 text

But I'm Sorry That Was a Lie

Slide 65

Slide 65 text

The Scope Problem This happens when you're putting everything in one file

Slide 66

Slide 66 text

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"

Slide 67

Slide 67 text

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)

Slide 68

Slide 68 text

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"

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Such a Weird Feature! Probably the weirdest feature in Ruby that I know

Slide 71

Slide 71 text

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"

Slide 72

Slide 72 text

This Funny Feature Was Proposed And Implemented by Shugo Matz's boss at NaCl

Slide 73

Slide 73 text

The protected Scope

Slide 74

Slide 74 text

Another Funny Feature Created by Shugo

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

What Matz Thinks About protected "(If I would re-design Ruby) now? I'll reject protected" https:/ /twitter.com/yukihiro_matz/status/ 180090910451834881

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Removal of protected in Rails I tried doing this for the Rails' codebase

Slide 79

Slide 79 text

The Huge Patch 3.years.ago https:/ /github.com/amatsuda/rails/commit/ 6832343c

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

protected Dead in Rails master Don't use it

Slide 83

Slide 83 text

BTW

Slide 84

Slide 84 text

Shugo, The Creator of Refinements And protected His most recent project

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

RubyKaigi 2017

Slide 87

Slide 87 text

private def (2.1)

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

But,

Slide 98

Slide 98 text

`private def` in Rails Very rarely used

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

Why?

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

The Parser (Lexer) in RDoc lib/rdoc/ruby_lex.rb

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

lib/rdoc/ruby_lex.rb It's a "ruby lexcal analyzer" Written by keiju "adapted from irb" OMG! RDoc requires IRB!

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

Parsers in Ruby parse.y (+ yacc, bison) Ripper ruby-lex.rb in IRB

Slide 110

Slide 110 text

Ruby has Ripper in stdlib Written by Minero Aoki, the author of "Ruby Hacking Guide"

Slide 111

Slide 111 text

But Why Didn't RDoc Use Ripper? Because Ripper was not yet created when Dave Thomas created RDoc

Slide 112

Slide 112 text

Parsers History IRB: 1997 RDoc: 2003 Ripper: 2004

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

RubyKaigi 2017

Slide 116

Slide 116 text

Parsers History (Reprise) IRB: 1997 RDoc: 2003 Ripper: 2004

Slide 117

Slide 117 text

IRB Created in 1997 It's IRB's 20th anniversary this year!

Slide 118

Slide 118 text

Let's Celebrate! There'll be an IRB 20th anniversary speech by the original author

Slide 119

Slide 119 text

RubyKaigi 2017

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

I Gave Up Replacing The Whole RDoc Parser But I could make a tiny patch for RDoc to add `private def` support last year

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

Feel Free to Use
 `private def` Now!

Slide 125

Slide 125 text

Keyword Arguments (2.0)

Slide 126

Slide 126 text

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)

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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)`

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

Rails Includes Some Tools To handle this psuedo keyword arguments

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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)

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

Ruby 2 Has The Built-in Keyword Arguments! Since 2.0 Proposed and implemented by mame, the insane genius

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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)

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

Then I Found A Problem While Experimenting This Some methods in Rails take Ruby keyword as an option name
 (e.g. if, unless, end)

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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!

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

My Proposal for Rails

Slide 149

Slide 149 text

Kwags as a 1st Citizen API in Rails

Slide 150

Slide 150 text

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`

Slide 151

Slide 151 text

require_relative (1.9)

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

Occurrences of `require` and `require_relative` in Rails
 (As of Today) '^require_relative': 73 '^require ': 4055

Slide 155

Slide 155 text

So, There's a Huge Room for an Improvement? I thought so before So I created a patch

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

Why Didn't I Push This Because I couldn't see any significant performance improvement

Slide 158

Slide 158 text

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?

Slide 159

Slide 159 text

Anyway, `require_relative` is basically a good thing Although it's not popular in Rails

Slide 160

Slide 160 text

ISeq#to_binary, ISeq.load_from_binary (2.3)

Slide 161

Slide 161 text

Public APIs to Dump & Restore Compiled Instruction Sequences

Slide 162

Slide 162 text

Yomikomu by ko1 (2015) An experimental gem https:/ /github.com/ko1/yomikomu No significant perf improvement on real-world apps (ko1@RailsConf 2016)

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

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

Slide 165

Slide 165 text

RubyKaigi 2017

Slide 166

Slide 166 text

Encoding (1.9-)

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

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)

Slide 169

Slide 169 text

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

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

View Templates Template file encoding Magic comment at the top of the template

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

What I Only Did _.html.erb https:/ /github.com/rails/rails/ commit/da9038eaa5d

Slide 174

Slide 174 text

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

Slide 175

Slide 175 text

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

Slide 176

Slide 176 text

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

Slide 177

Slide 177 text

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

Slide 178

Slide 178 text

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

Slide 179

Slide 179 text

Frozen Strings (2.3)

Slide 180

Slide 180 text

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

Slide 181

Slide 181 text

String#freeze Everywhere ❄ Pro: Some kind of performance improvement Con: Ugly code

Slide 182

Slide 182 text

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

Slide 183

Slide 183 text

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

Slide 184

Slide 184 text

The Magic Comment # frozen_string_literal: true

Slide 185

Slide 185 text

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

Slide 186

Slide 186 text

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

Slide 187

Slide 187 text

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

Slide 188

Slide 188 text

Symbol GC (2.2)

Slide 189

Slide 189 text

Rails 5 Was Decided to Support Ruby 2.2+ Because of This Feature GC collects dynamically created Symbols Disables Symbol fixation attack

Slide 190

Slide 190 text

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

Slide 191

Slide 191 text

Integer Unification (2.4)

Slide 192

Slide 192 text

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

Slide 193

Slide 193 text

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

Slide 194

Slide 194 text

Active Support as an Experimental Lab for Ruby

Slide 195

Slide 195 text

Object#try! (2.3) `&.` `try!` can be deprecated in favor of `&.` when we dropped 2.2 support

Slide 196

Slide 196 text

String#strip_heredoc (2.3) `<<~` `strip_heredoc` can be deprecated in favor of `<<~` when we dropped 2.2 support

Slide 197

Slide 197 text

Numeric#positive?, negative? (2.3) Another trivial Active Support to Ruby pair of methods

Slide 198

Slide 198 text

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

Slide 199

Slide 199 text

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

Slide 200

Slide 200 text

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

Slide 201

Slide 201 text

Somehow Done Before Releasing 5.1.0 Stable Not perfect, but works Can be made simpler when we dropped Ruby < 2.3 support

Slide 202

Slide 202 text

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

Slide 203

Slide 203 text

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!

Slide 204

Slide 204 text

That's It!

Slide 205

Slide 205 text

Thank You for Having Me And see you at another conference! Hopefully,

Slide 206

Slide 206 text

RubyKaigi 2017

Slide 207

Slide 207 text

end

Slide 208

Slide 208 text

pp self name: Akira Matsuda GitHub: amatsuda Twitter: @a_matsuda