Slide 1

Slide 1 text

Ruby 2 Methodology Akira Matsuda RUBYCONF 2015

Slide 2

Slide 2 text

This Talk Focuses on “Method” in Ruby

Slide 3

Slide 3 text

I'm Sorry but this Talk Is Going to Be a Serious Ruby Talk

Slide 4

Slide 4 text

RubyConf 2015

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

RubyConf 2015 Tracks Domain Patterns Less Code Wetware Ruby In Depth Beyond Ruby Play

Slide 7

Slide 7 text

RubyConf 2015 Tracks Very well balanced Diverse topics

Slide 8

Slide 8 text

So Glad that RubyConf Still Has the ”Ruby In Depth” Track This Year Makes me feel like I came to a Ruby conference

Slide 9

Slide 9 text

RubyKaigi 2015 Next month In Japan "

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

2 Tracks * 3 Days

Slide 12

Slide 12 text

RubyKaigi Tracks Ruby Ruby Ruby Ruby Ruby Ruby

Slide 13

Slide 13 text

Everyone There Talks About Ruby Because it's a Ruby conference

Slide 14

Slide 14 text

If You Want to See More Ruby Talks RubyKaigi is the conference you should go!

Slide 15

Slide 15 text

The RubyKaigi Team

Slide 16

Slide 16 text

“Chief Organizer” Of RubyKaigi 2015

Slide 17

Slide 17 text

Akira Matsuda GitHub: amatsuda Twitter: @a_matsuda

Slide 18

Slide 18 text

GitHub: amatsuda

Slide 19

Slide 19 text

GitHub: amatsuda Ruby

Slide 20

Slide 20 text

GitHub: amatsuda Rails

Slide 21

Slide 21 text

I Work for Ruby, Rails, Several Other OSS Products, And Several Companies

Slide 22

Slide 22 text

begin

Slide 23

Slide 23 text

This Talk Focuses on “Method” in Ruby

Slide 24

Slide 24 text

I'm Going to Talk About Modern Usage of Ruby's Method Not just introducing the features I’d like to tell my stories With lots of Ruby code in the slides

Slide 25

Slide 25 text

Method Definition

Slide 26

Slide 26 text

def class Person def hello p 'hello' end end

Slide 27

Slide 27 text

Sometimes We Want to Define a Method with Weird Name Which character can/ cannot be a (part of) Method name?

Slide 28

Slide 28 text

Can a Method Name Contain Emoji?

Slide 29

Slide 29 text

def see: sferik/active_emoji

Slide 30

Slide 30 text

active_emoji/core_ext/ kernel.rb

Slide 31

Slide 31 text

Defining and Calling def () '' * 5; end puts #=>

Slide 32

Slide 32 text

Then Which Character Cannot Be a Method Name?

Slide 33

Slide 33 text

Some ASCII Chars Such as 1 ? ) @ " : etc.

Slide 34

Slide 34 text

def @ def @ end #=> Syntax Error

Slide 35

Slide 35 text

How Can We Define an @ Method? (Without using C-exts)

Slide 36

Slide 36 text

amatsuda/rfd I do this a lot in my app It might be interesting to see a real use case for methods with such weird names

Slide 37

Slide 37 text

rfd/commands.rb # Change current directory (cd). define_method(:'@') do process_command_line preset_command: 'cd' end # Execute a shell command in an external shell. define_method(:!) do process_shell_command end # Execute a command in the controller context. define_method(:':') do process_command_line end

Slide 38

Slide 38 text

Listing up ASCII Chars that Can/ Cannot Be Used for Method Name h = {def: [], define_method: []} (' '..'~').each do |c| begin eval "def #{c}() puts '#{c}'; end" h[:def] << c rescue SyntaxError define_method(c) { puts c } h[:define_method] << c end end puts h

Slide 39

Slide 39 text

Result :def => "!", "%", "&", "*", "+", "-", "/", "<", ">", "A"-"Z", "^", "_", "`", "a"-"z", "|", "~" :define_method => " ", "\"", "#", "$", "'", "(", ")", ",", ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "=", "?", "@", "[", "\\", "]", "{", "}"

Slide 40

Slide 40 text

Then How Can We Call this Method? foo = @() #=> syntax error

Slide 41

Slide 41 text

Then How Can We Call this Method? Kernel#send
 (or public_send)

Slide 42

Slide 42 text

send @ define_method(:'@') do p '@' end send :'@' #=> "@"

Slide 43

Slide 43 text

When to Use Kernel#send When calling a method with abnormal name When dynamically changing a Method to call When calling a Method from Outside of scope

Slide 44

Slide 44 text

When Dynamically Changing a Method to Call # actionpack/lib/abstract_controller/base.rb module AbstractController class Base ... private ... def process_action(method_name, *args) send_action(method_name, *args) end alias send_action send ... ennd

Slide 45

Slide 45 text

When Calling a Method from Outside of Scope Class.new { private def priv() p:hello end }.new.send :priv #=> :hello

Slide 46

Slide 46 text

Method Scopes

Slide 47

Slide 47 text

Method Scopes 3 Types of Method Scopes public, protected, and private

Slide 48

Slide 48 text

public Open to Everyone The Default Method Scope

Slide 49

Slide 49 text

private class C private def hello() p:hello end end C.new.hello #=> private method `hello' called for # (NoMethodError)

Slide 50

Slide 50 text

private class C private def hello() p:hello end def greeting() hello end end C.new.greeting

Slide 51

Slide 51 text

private class C private def hello() p:hello end def greeting() hello end end C.new.greeting #=> :hello

Slide 52

Slide 52 text

If There's a lvar with the Same Name, the private Method Would Never Be Called class C private def hello() 'hello' end def greeting(hello = 'hola') p hello end end C.new.greeting #=> "hola"

Slide 53

Slide 53 text

How to Avoid This?

Slide 54

Slide 54 text

Put Parens()? class C private def hello() 'hello' end def greeting(hello = 'hola') p hello() end end C.new.greeting #=> "hello"

Slide 55

Slide 55 text

Put Parens Sure it works, but we don't want to add parens to all method calls... Ruby is not JavaScript!

Slide 56

Slide 56 text

Prepend “self.” to private Method Calls class C private def hello() 'hello' end def greeting(hello = 'hola') p self.hello end end C.new.greeting

Slide 57

Slide 57 text

Prepend self. to private Method Calls class C private def hello() 'hello' end def greeting(hello = 'hola') p self.hello end end C.new.greeting #=> private method `hello' called for # (NoMethodError)

Slide 58

Slide 58 text

WTF

Slide 59

Slide 59 text

Our Real App Problem at Ubiregi Inc. We changed a public method in a Rails controller to be private, then we saw a NoMethodError because of “self.” But we thought this should not be an error

Slide 60

Slide 60 text

So, We Wrote a Patch Feature #11297 Proposed and patched by @soutaro, the CTO at Ubiregi Inc.

Slide 61

Slide 61 text

me @soutaro

Slide 62

Slide 62 text

Matz Accepted this “It changes the concept of private methods a little. It's OK to merge the patch if the document is updated at the same time..”

Slide 63

Slide 63 text

But, I Forgot to Merge this in and Do the Documentation

Slide 64

Slide 64 text

This Change Won't Be Included in 2.3, but Maybe in 2.4...

Slide 65

Slide 65 text

Another Solution to Call it with “self.” class C - private def hello() 'hello' end + protected def hello() 'hello' end def greeting(hello = 'hola') p self.hello end end C.new.greeting #=> "hello"

Slide 66

Slide 66 text

protected? What exactly is the protected scope? Who can explain?

Slide 67

Slide 67 text

protected Different from Java, C++’s protected Proposed and implemented by @shugomaeda

Slide 68

Slide 68 text

protected Method Can't Be Called from Outside class C protected def hello() 'hello' end end C.new.greeting #=> undefined method `greeting' for # (NoMethodError)

Slide 69

Slide 69 text

protected Method Can't Be Called from Outside class C protected def hello() 'hello' end end class D def greeting C.new.hello end end D.new.greeting #=> protected method `hello' called for # (NoMethodError)

Slide 70

Slide 70 text

protected Method Can Be Called from Other Instance of the C Class class C protected def hello() 'hello' end def greeting() p C.new.hello end end C.new.greeting #=> "hello"

Slide 71

Slide 71 text

Difference Between private and protected class C private def hello() 'hello' end def greeting() p C.new.hello end end C.new.greeting #=> private method `hello' called for # (NoMethodError)

Slide 72

Slide 72 text

Who Uses this Feature?

Slide 73

Slide 73 text

Let's Find a Real-world Use Case...

Slide 74

Slide 74 text

Let's Find a Real-world Use Case... in Rails

Slide 75

Slide 75 text

git grep "^ *protected" % git grep "^ *protected" | wc -l #=> 203

Slide 76

Slide 76 text

The Rails People Abuse protected Why are there that many “protected”s?

Slide 77

Slide 77 text

The Rails People Abuse And Misuse protected Do people really know what protected means?

Slide 78

Slide 78 text

So, I Wrote a Patch 150 occurrences of protected could actually be replaced to private https://github.com/amatsuda/ rails/commit/6832343

Slide 79

Slide 79 text

Only a Few Proper Use Cases of protected in Rails More than 90% of protected in Rails code actually means private

Slide 80

Slide 80 text

An Example of a Proper Use Case of protected in Rails # AP/lib/action_controller/metal/strong_parameters.rb module ActionController class Parameters < ActiveSupport::HashWithIndifferentAccess def dup super.tap do |duplicate| duplicate.permitted = @permitted end end protected def permitted=(new_permitted) @permitted = new_permitted ennnd

Slide 81

Slide 81 text

Don't Use protected Unless you're sure what you're doing

Slide 82

Slide 82 text

However,

Slide 83

Slide 83 text

The Patch Is Still not Merged

Slide 84

Slide 84 text

The RDoc Problem RDoc mutes private method documentation

Slide 85

Slide 85 text

The RDoc Problem class C # This is a public method def pub() end protected # This is a protected method def prot() end private # This is a private method def priv() end end

Slide 86

Slide 86 text

The Generated RDoc

Slide 87

Slide 87 text

RDoc Does not Generate Documentations for private Methods

Slide 88

Slide 88 text

My Rails Patch I want to change the method scope accurately, but that spoils so much documentations!

Slide 89

Slide 89 text

This Is How We Abuse protected, and Why We Can't Stop Abusing protected in Rails

Slide 90

Slide 90 text

Another RDoc Problem

Slide 91

Slide 91 text

✨(2.1) def Returns the Method Name In combination with `private :foo` syntax, now we can do Java like
 `private def foo() end` Proposed and implemented by usa (@unak)

Slide 92

Slide 92 text

Another RDoc Problem class C # This is a public method public def pub() end # This is a protected method protected def prot() end # This is a private method private def priv() end end

Slide 93

Slide 93 text

The Generated RDoc ɹEmpty!

Slide 94

Slide 94 text

And Even Worse… class C # This is a private method private def priv() end # This should be documented def pub1() end # This should also be documented def pub2() end end

Slide 95

Slide 95 text

The Generated RDoc Empty again!

Slide 96

Slide 96 text

rdoc/rdoc/issues/355

Slide 97

Slide 97 text

This Is Why We Still Don't Use “private def” Style Definition in Rails We need to patch RDoc first Patches are welcome!

Slide 98

Slide 98 text

The Method Object

Slide 99

Slide 99 text

Another Way to Invoke a Method Call `Method#call` via Ruby

Slide 100

Slide 100 text

Class.instance_methods class Person end p Person.instance_methods(false) #=> []

Slide 101

Slide 101 text

Inspecting Defined Methods Via Class.instance_methods class Person def hello p 'hello' end end p Person.instance_methods(false) #=> [:hello]

Slide 102

Slide 102 text

Getting an Instance of Method from an Instance of the Person Class class Person def hello p 'hello' end end p Person.new.method(:hello) #=> #

Slide 103

Slide 103 text

Calling the Method class Person def hello p 'hello' end end Person.new.method(:hello).call #=> "hello"

Slide 104

Slide 104 text

Passing in a Parameter class Person def say(something) p something end end Person.new.method(:say).call('hello') #=> "hello"

Slide 105

Slide 105 text

Getting an Instance of Method from the Person Class class Person def hello p 'hello' end end p Person.instance_method(:hello) #=> #

Slide 106

Slide 106 text

UnboundMethod? This must be a Method so let's just call it

Slide 107

Slide 107 text

Calling The UnboundMethod class Person def hello p 'hello' end end Person.instance_method(:hello).call #=> undefined method `call' for # (NoMethodError)

Slide 108

Slide 108 text

So, What's Defined on the UnboundMethod? class Person def hello p 'hello' end end p Person.instance_methods(false).sort #=> [:==, :arity, :bind, :clone, :eql?, :hash, :inspect,
 :name, :original_name, :owner, :parameters,
 :source_location, :super_method, :to_s]

Slide 109

Slide 109 text

Difference Between Method and UnboundMethod p m = Method.instance_methods(false).sort #=> [:==, :[], :arity, :call, :clone, :curry, :eql?,
 :hash, :inspect, :name, :original_name, :owner,
 :parameters, :receiver, :source_location, :super_method,
 :to_proc, :to_s, :unbind] p u = UnboundMethod.instance_methods(false).sort #=> [:==, :arity, :bind, :clone, :eql?, :hash, :inspect,
 :name, :original_name, :owner, :parameters,
 :source_location, :super_method, :to_s] p m - u #=> [:[], :call, :curry, :receiver, :to_proc, :unbind] p u - m #=> [:bind]

Slide 110

Slide 110 text

Binding an UnboundMethod to an Instance of Person class Person def hello p 'hello' end end p um = Person.instance_method(:hello) #=> # person = Person.new p meth = um.bind(person) #=> # meth.call #=> "hello"

Slide 111

Slide 111 text

Can a Cat Say Hello? class Person def hello() p 'hello' end end class Cat def hello() p 'Meow' end end um = Person.instance_method(:hello) cat = Cat.new meth = um.bind(cat) meth.call

Slide 112

Slide 112 text

No, A Cat Is not a Person class Person def hello() p 'hello' end end class Cat def hello() p 'Meow' end end um = Person.instance_method(:hello) cat = Cat.new meth = um.bind(cat) meth.call #=> bind argument must be an instance of Person (TypeError)

Slide 113

Slide 113 text

✨ (2.0) Method Transplanting Feature #4254 Proposed by @zimbatm Implemented by @nobu

Slide 114

Slide 114 text

Transplanting a Method From a Module module Greeter def hello p 'hello' end end class Cat define_method :hello, Greeter.instance_method(:hello) end Cat.new.hello #=> "hello"

Slide 115

Slide 115 text

Method Transplanting Seems like there’s nothing different from including the whole Module in this case You can cherry-pick a Method without including the whole Module

Slide 116

Slide 116 text

✨ (2.2, 2.3) Transplanting a Method From a Class module Greeter def hello() p 'hello' end end class Person include Greeter end class Cat define_method :hello, Person.instance_method(:hello) end Cat.new.hello #=> "hello"

Slide 117

Slide 117 text

Method Transplanting Methods can now be transplanted from a Class to another Class If the Method originally comes from a Module So many Module-based Rails Methods became portable now!

Slide 118

Slide 118 text

Parameter

Slide 119

Slide 119 text

Passing in a Parameter to Method#call class Person def say(something) p something end end Person.new.method(:say).call('hello') #=> "hello"

Slide 120

Slide 120 text

Parameter?

Slide 121

Slide 121 text

Methods Take Parameters, Then Parameters Can Be Accessed as Local Variables (lvar) in the Method def say(something) ... end def say something = 'hello' ... end # same kind of variable

Slide 122

Slide 122 text

How Can We Inspect a Method’s Parameters? Use Method#parameters

Slide 123

Slide 123 text

✨(1.9) Method#parameters Implemented by ko1

Slide 124

Slide 124 text

ko1 (@_ko1)

Slide 125

Slide 125 text

ko1 The author of Ruby 1.9 VM (YARV) Full-time Ruby Committer Employed by Heroku

Slide 126

Slide 126 text

Inspecting a Method's Parameter class Person def say(something) p something end end p Person.new.method(:say).parameters #=> “hello” #=> [[:req, :something]]

Slide 127

Slide 127 text

An Optional Parameter class Person def say(something, options = {}) p something end end p Person.new.method(:say).parameters #=> [[:req, :something], [:opt, :options]]

Slide 128

Slide 128 text

rest, block class Person def say(something, options = {}, *args, &blk) p something end end p Person.new.method(:say).parameters #=> [[:req, :something], [:opt, :options], [:rest, :args], [:block, :blk]]

Slide 129

Slide 129 text

Real-world Example of Method#parameters asakusarb/action_args

Slide 130

Slide 130 text

action_args A Rails plugin Placed under Asakusa.rb organization respecting Ko1's work on Method#parameters The plugin code is 99% written by me Something that makes your Rails app's controller code like Merb's controller Initially named ”Merbish” I ALWAYS use this when working on Rails apps

Slide 131

Slide 131 text

What action_args Does # Before class UsersController < ApplicationController def show @user = User.find params[:id] end end # After class UsersController < ApplicationController def show(id) @user = User.find id end end

Slide 132

Slide 132 text

It Used not to Be Able to Handle filters' Parameters It's supported in recent versions!

Slide 133

Slide 133 text

action_args Supports Keyword Arguments As Well

Slide 134

Slide 134 text

✨(2.0) Keyword Arguments Proposed and implemented by mame

Slide 135

Slide 135 text

mame (@mametter) Ruby 2.0 release manager Known by his crazy quine works, such as 
 mame/quine-relay

Slide 136

Slide 136 text

The quine-relay

Slide 137

Slide 137 text

Method#parameters for kwargs class Person def say(word:, volume: :loud, **args) p word end end p Person.new.method(:say).parameters #=> [[:keyreq, :word], [:key, :volume], [:keyrest, :args]]

Slide 138

Slide 138 text

This Apparently Makes the API Clearer We should rewrite Rails’ methods to conform this style!

Slide 139

Slide 139 text

kwargs and Rails My First Trial January 2013

Slide 140

Slide 140 text

My First Trial Replaced some methods in ActiveSupport, ActionPack, ActiveModel

Slide 141

Slide 141 text

Looks Good, Isn’t it? - def truncate(truncate_at, options = {})
 + def truncate(truncate_at, omission: '...', separator: nil) - def number_to_currency(number, options = {}) + def number_to_currency(number, locale: nil, format: nil, unit: nil, **options) - def cattr_accessor(*syms, &blk) + def cattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, &blk) - def truncate(text, options = {}, &block)
 + def truncate(text, length: 30, escape: true, **options, &block)

Slide 142

Slide 142 text

However,

Slide 143

Slide 143 text

I Found a Specification Problem Reserved words like if, unless, end can be a keyword But we can’t touch the variable

Slide 144

Slide 144 text

Reserved Words in Ruby (lvar) if = 1 #=> syntax error, unexpected '='

Slide 145

Slide 145 text

Reserved Words in Ruby (Method Parameter) def a(if) end #=> syntax error, unexpected keyword_if, expecting ')'

Slide 146

Slide 146 text

Reserved Words Can Be kwargs Keys! def say(something, if:) end say 'Hello', if: weather.good?

Slide 147

Slide 147 text

I Said, Method Parameters Are Essentially Local Variables

Slide 148

Slide 148 text

Which Means, You Can Create a lvar with a Reserved Name!

Slide 149

Slide 149 text

But Wait, How Can We Access the Variable? def say(something, if:) p something if if end say 'Hello', if: true #=> syntax error, unexpected keyword_end #=> syntax error, unexpected end-of-input, expecting keyword_end

Slide 150

Slide 150 text

This Is What Happened in Ruby 2.0

Slide 151

Slide 151 text

So I Reported this Problem Maybe via Twitter or at the meeting or Asakusa.rb or something (couldn’t find the issue) Then ko1 and nobu made a fix

Slide 152

Slide 152 text

Nobu the Patch Monster

Slide 153

Slide 153 text

nobu “Pachimon” Another Heroku employee

Slide 154

Slide 154 text

How Can We Access a lvar with a Reserved Name?

Slide 155

Slide 155 text

✨ (2.1) binding.local_variables and binding.local_variable_get/set/defined? a = 'foo' p binding.local_variables #=> [:a] p binding.local_variable_get(:a) #=> "foo"

Slide 156

Slide 156 text

kwargs + Reserved Word + binding.local_variable_get def say(something, if:) p something if binding.local_variable_get(:if) end say 'Hello', if: true #=> "Hello"

Slide 157

Slide 157 text

The Performance Problem of kwargs Because it used to create a Hash instance internally

Slide 158

Slide 158 text

✨(2.2) Performance Improvement of kwargs By ko1 Refactored not to create a Hash per method call

Slide 159

Slide 159 text

Rails 5 Coming soon (?) Supports only Ruby >= 2.2 Major version bump is a good chance to introduce an API incompatibility Now we have fast kwargs and binding.local_variable_get I think we're ready to revise the *args API and move to kwargs

Slide 160

Slide 160 text

If I Had Enough Time to Work on This and Finish My Patch...

Slide 161

Slide 161 text

AMC and Module#prepend

Slide 162

Slide 162 text

Rails 5 alias_method_chain is deprecated

Slide 163

Slide 163 text

alias_method_chain (a.k.a. AMC) A monkey-patching idiom Very simple, but super useful Genius work

Slide 164

Slide 164 text

The Very First Implementation of AMC

Slide 165

Slide 165 text

Usage of AMC class Record def save() p 'Saved.' end def save_with_validation p 'Validated.' save_without_validation end alias_method_chain :save, :validation end Record.new.save #=> "Validated." #=> "Saved."

Slide 166

Slide 166 text

You Still Can Call the Un- monkeypatched Version class Record def save() p 'Saved.' end def save_with_validation p 'Validated.' save_without_validation end alias_method_chain :save, :validation end Record.new.save_without_validation #=> "Saved."

Slide 167

Slide 167 text

*_without_* Methods Very handy Very intuitive method name Isn't is?

Slide 168

Slide 168 text

The Dark Side of AMC save_without_validation doesn’t always act as what it literally means

Slide 169

Slide 169 text

What If We Had Another AMC Chain? class Record def save() p 'Saving...' end def save_with_validation p 'Validating...' save_without_validation end alias_method_chain :save, :validation def save_with_callback save_without_callback p 'Calling back...' end alias_method_chain :save, :callback end Record.new.save #=> "Calling back..." #=> "Validating..." #=> "Saving..."

Slide 170

Slide 170 text

Now Let's save Without validation class Record def save() p 'Saving...' end def save_with_validation p 'Validating...' save_without_validation end alias_method_chain :save, :validation def save_with_callback save_without_callback p 'Calling back...' end alias_method_chain :save, :callback end Record.new.save_without_validation #=> "Saving..."

Slide 171

Slide 171 text

*_without_* Methods Are Harmful Unpredictable behaviour The method name (probably) lies! *_without_* shouldn't be callable *_without_* shouldn’t even be defined

Slide 172

Slide 172 text

We Needed a Nicer Language Level Monkey-patching Support

Slide 173

Slide 173 text

✨ (2.0) Module#prepend Proposed by @wycats Implemented by nobu

Slide 174

Slide 174 text

How It Works Basically it's like a reverse-ordered Module#include

Slide 175

Slide 175 text

Module#include class Record def save super p 'Saved.' ennd module Validation def save p 'Validated.' ennd Record.send :include, Validation Record.new.save #=> "Validated." #=> "Saved."

Slide 176

Slide 176 text

Common Idiom of Module#prepend in 2.0 class Record def save() p 'Saved.' end end module Validation def save p 'Validated.' super ennd Record.send :prepend, Validation Record.new.save #=> "Validated." #=> "Saved."

Slide 177

Slide 177 text

How it Makes Your Code Clean amatsuda/ html5_validators

Slide 178

Slide 178 text

No Other Method Definition Than render # html5_validators/action_view/form_helpers.rb module Html5Validators module ActionViewExtension module PresenceValidator def render ... super end end module LengthValidator def render ... super end end module NumericalityValidator def render ... super ennnnd module ActionView::Helpers::Tags::TextField prepend Html5Validators::ActionViewExtension::NumericalityValidator prepend Html5Validators::ActionViewExtension::LengthValidator prepend Html5Validators::ActionViewExtension::PresenceValidator end

Slide 179

Slide 179 text

Module#prepend Always Comes with send Because it initially was a private method in 2.0 In conformity to Module#include

Slide 180

Slide 180 text

Isn't It Natural to Make it public? The main purpose of this method is monkey-patching Usually done from outside, rather than from inside Also Module#include should be public (Object#extend is already public)

Slide 181

Slide 181 text

So, I Proposed and Patched

Slide 182

Slide 182 text

✨(2.1) Module#include and Module#prepend Became public Feature #8846 Proposed and implemented by a_matsuda

Slide 183

Slide 183 text

@a_matsuda

Slide 184

Slide 184 text

super and super_method

Slide 185

Slide 185 text

Numbers of `super` calls Rails 2.3: about 221 Rails master: about 499

Slide 186

Slide 186 text

`super` Is Heavily Used @carlhuda style “Micro Kernel Architecture”

Slide 187

Slide 187 text

Code Complexity So many method definitions with the same name e.g. `def save`, `def render` Hard to read through the code Hard to debug

Slide 188

Slide 188 text

ActiveRecord.has_many `super` def save(*args) create_or_update(*args) rescue ActiveRecord::RecordInvalid false end def save(*) if status = super changes_applied end status end def save(*) #:nodoc: rollback_active_record_state! do with_transaction_returning_status { super } end end def save(options={}) perform_validations(options) ? super : false end

Slide 189

Slide 189 text

Which save Method Will Actually Get Called Via Each `super`? Who can tell?

Slide 190

Slide 190 text

✨(2.2) Method#super_method Feature #9781 Returns a Method object that will be called via `super` Proposed by @schneems Implemented by nobu

Slide 191

Slide 191 text

For debugging purpose “I believe adding Method#super_method, or exposing this same information somewhere else, could greatly help developers to debug large systems easily.” - @schneems

Slide 192

Slide 192 text

But I Found that,

Slide 193

Slide 193 text

super_method Can Be Used not Only for Debugging

Slide 194

Slide 194 text

Let’s Get Back to AMC vs. Module#prepend Module#prepend does not define *_without_* method So, There's No Way Calling save_without_validation for Prepended Method?

Slide 195

Slide 195 text

No. You Can Call the super_method! class Record def save() p 'Saved.' end end module Validation def save p 'Validated.' and super end end Record.prepend Validation record = Record.new record.method(:save).super_method.call #=> "Saved."

Slide 196

Slide 196 text

Method#super_method Provides a Way to Call save_without_validation This technique can be used for include extend prepend refine

Slide 197

Slide 197 text

What if We Have 2 Modules Prepended?

Slide 198

Slide 198 text

You Can Call super_method’s super_method class Record def save() p 'Saved.' end end module Validation def save() p 'Validated.' and super ennd module Callback def save() super and p 'Calling back...' ennd Record.prepend Callback, Validation method = Record.new.method(:save) method.super_method.super_method.call #=> "Saved."

Slide 199

Slide 199 text

What if We Have n Modules Prepended?

Slide 200

Slide 200 text

Use Method#owner

Slide 201

Slide 201 text

Method#owner class Record def save() p 'Saved.' end end module Validation def save() p 'Validated.' and super ennd module Callback def save super and p 'Calling back...' ennd module Transaction def save p 'Transaction start.' and super and p 'Transaction end.' ennd Record.prepend Callback, Transaction, Validation method = Record.new.method(:save) p method.owner p method.super_method.owner #=> Callback #=> Transaction

Slide 202

Slide 202 text

What if We Have n Modules Prepended? ... Record.prepend Callback, Transaction, Validation method = Record.new.method(:save) until method.owner == Record method = method.super_method end method.call #=> "Saved."

Slide 203

Slide 203 text

Generalizing a Little Bit More class Method def call_without(mod, *args, &block) if mod == owner super_method.call(*args, &block) else super_method.call_without(mod, *args, &block) end end end ... Record.prepend Callback, Transaction, Validation method = Record.new.method(:save) method.call_without(Validation) #=> "Saved."

Slide 204

Slide 204 text

Method#super_method I’m sure nobody uses this feature Because it’s still so buggy

Slide 205

Slide 205 text

Module#prepend + UnboundMethod#super_method class C def a() end end module M def a() end end C.prepend M p C.instance_method(:a).super_method #=> nil # This should be #

Slide 206

Slide 206 text

Module#prepend + Method#super_method’s super_method class C def a() end end module M def a() end end C.prepend M p C.new.method(:a).super_method #=> # p C.new.method(:a).super_method.super_method #=> # p C.new.method(:a).super_method.super_method.super_method #=> # (and this loops...) # This should be nil

Slide 207

Slide 207 text

I’m Hoping These Bugs to Be Fixed Hopefully before 2.3.0 stable

Slide 208

Slide 208 text

Refinements

Slide 209

Slide 209 text

Module#prepend Is a Great Monkey-patching Tool Less polluting Only When Overriding an Existing Method But that's not Always the Case

Slide 210

Slide 210 text

Consider this Case What if we want to include a Module that has internal methods, and we don't want to expose them to the users?

Slide 211

Slide 211 text

Some Methods from
 the End Users class Framework::Base end module MyMonkeypatch # We want to reveal this to the end users public def foo ... end # We want to hide this from the end users private def bar ... end end Framework::Base.include MyMonkeypatch

Slide 212

Slide 212 text

Refinements For this purpose, we can use Refinements!

Slide 213

Slide 213 text

✨ (2.0) Refinements Proposed and implemented by shugo (@shugomaeda) The initial implementation was more powerful and useful See my talk “Ruby 2.0 on Rails” at RubyConf 2012 https://speakerdeck.com/a_matsuda/ruby-2-dot-0-on-rails Then diminished to the current specification

Slide 214

Slide 214 text

Current Refinements’ Spec File scoped monkey-patch “Lexical scope”

Slide 215

Slide 215 text

Basic Usage of Refinements # amplifier.rb module Amplifier refine String do def shout() p self + '!' end end end # another_file.rb require_relative 'amplifier' using Amplifier 'hello'.shout #=> "hello!"

Slide 216

Slide 216 text

Real-world Use Case of Refinements asakusarb/action_args

Slide 217

Slide 217 text

refining Rails’ Core Class # lib/action_args/params_handler.rb module ActionArgs module ParamsHandler refine AbstractController::Base do def extract_method_arguments_from_params(method_name) ... end def strengthen_params!(method_name) ... ennnnd

Slide 218

Slide 218 text

Then using it from Another File # lib/action_args/abstract_controller.rb require_relative 'params_handler' using ActionArgs::ParamsHandler module ActionArgs module AbstractControllerMethods def send_action(method_name, *args) ... strengthen_params! method_name values = extract_method_arguments_from_params method_name super method_name, *values ennd ... end

Slide 219

Slide 219 text

I Would Call this “super private” The methods defined inside the library are visible only inside the library Never exposed to the end-users (unless they explicitly call `using`) Thanks @_ksss_ for giving me an inspiration for this implementation!

Slide 220

Slide 220 text

A Pitfall of Refinements Refined methods can’t be called via Kernel#send Because Kernel#send is not lexically scoped? But we often want to dynamically change the method to call, for instance in Rails

Slide 221

Slide 221 text

I Think this Restriction Should Be Loosen So I raised a feature request Feature #11476 If this is allowed, we could implement something useful, maybe?

Slide 222

Slide 222 text

Ruby’s Method is still evolving! ⤴⤴ Let’s play with Method! You can make it better! Let’s make it more fun! Summary

Slide 223

Slide 223 text

end