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

Rubyモンキーパッチの世界

sutetotanuki
January 14, 2017
2.1k

 Rubyモンキーパッチの世界

第76回 Ruby関西勉強会発表資料

sutetotanuki

January 14, 2017
Tweet

More Decks by sutetotanuki

Transcript

  1. class A def a "a" end end A.new.a # =>

    "a" class A def a "b" end end A.new.a # => "b"
  2. class A def a "a" end end A.new.a # =>

    "a" # 一度定義したクラスをもう一度定義しなおすと class A def a "b" end end A.new.a # => "b" メソッドの動きが変更されている
  3. class A def a "a" end end A.new.a # =>

    "a" class A def b # 新しいメソッドを定義することもできる "b" end end A.new.b # => "b"
  4. module CarrierWaveDirect module Uploader def filename # ~~~~ 省略 key_path

    = key.split("/") filename_parts = [] filename_parts.unshift(key_path.pop) unique_key = key_path.pop filename_parts.unshift(unique_key) if unique_key filename_parts.join("/") end end end
  5. module CarrierWaveDirect module Uploader def filename # ~~~~ 省略 key_path

    = key.split("/") filename_parts = [] filename_parts.unshift(key_path.pop) unique_key = key_path.pop filename_parts.unshift(unique_key) if unique_key filename_parts.join("/") end end end ŇP0ʢpMFOBNFͱ͍͏ϝιουͳͷʹ TͷLFZؚ͕·ΕͯΔͱεϥογϡͰ۠੾ͬͨ ࠷ޙͷཁૉͱɺ΋͏ҰͭΛ࣍ͷཁૉΛ ϑΝΠϧ໊ͱͯ͠ฦͯ͠Δ
  6. module CarrierWaveDirect module Uploader def filename # ~~~~ 省略 key_path

    = key.split("/") filename_parts = [] filename_parts.unshift(key_path.pop) unique_key = key_path.pop # filename_parts.unshift(unique_key) if unique_key filename_parts.join("/") end end end app/config/initializers/fix_carrier_wave_direct.rb ڧҾʹpMFOBNFͷΈΛฦ͢Α͏ʹͨ͠
  7. class NilClass # +nil+ is blank: # # nil.blank? #

    => true # # @return [true] def blank? true end end ˞3BJMTͷ"DUJWF4VQQPSU͔ΒҾ༻
  8. class NilClass # +nil+ is blank: # # nil.blank? #

    => true # # @return [true] def blank? true end end ˞3BJMTͷ"DUJWF4VQQPSU͔ΒҾ༻ /JM$MBTT OJMΦϒδΣΫτͷΫϥε ʹϝιο υΛ଍͢͜ͱʹͨͱ͑OJMʹରͯ͠CMBOL ϝ ιουݺͿ͜ͱΛՄೳʹ͍ͯ͠Δ
  9. var = ?? # nilの可能性がある変数でも var.blank? # blankが呼べるので # 例えばparamsから取り出した値でも安心して呼べる

    if params[:attr].blank? # ""(空文字) か nil の場合にここが実行される end # nilを拡張できない場合はNoMethodErrorを防ぐ為に # nilかどうかのチェックをする必要がある if !params[:attr] || params[:attr].blank? # ""(空文字) か nil の場合にここが実行される end
  10. # app/models/some_model.rb class SomeModel < ApplicationRecord end class String def

    inspect "test" end end ϓϩδΣΫτͷதʹΦʔϓϯΫϥε͍ͯ͠Δ ίʔυ͕ࠞͬͯ͡Δͱશମʹద༻͞Εͯ͠·͏
  11. class SomeModel < ApplicationRecord end class String def inspect "test"

    end end ϓϩδΣΫτͷதʹ ΦʔϓϯΫϥεΛ͍ͯ͠Δ ίʔυ͕ࠞͬͯ͡Δͱશମ ʹద༻͞Εͯ͠·͏ app/models/some_model.rb class SomeController < ApplicationController def index "param_#{params[:page]}" #=> param_test end end app/controller/some_controller.rb
  12. class A def a "a" end end module B refine

    A do def a "b" end end end A.new.a # => "a" using B A.new.a # => "b"
  13. class A def a "a" end end module B refine

    A do # refineの後にモンキーパッチしたいクラス def a # => 渡したブロックでクラス定義ができる "b" end end end A.new.a # => "a" using B # usingキーワードでモジュールを指定すると # それ以降モンキーパッチが適用される A.new.a # => "b"
  14. # file scope class A def b # ~~~ end

    end using M # Usingより下の行は全て対象 class B # classの中でも対象 def b # methodの中でも対象 end end # クラス定義終わっても対象
  15. # file scope class A def b # ~~~ end

    end using M # Usingより下の行は全て対象 class B # classの中でも対象 def b # methodの中でも対象 end end # クラス定義終わっても対象
  16. # class scope class A def b end using M

    # Usingより下行は対象 def b # methodの中でも対象 end end # クラスのスコープから外れると対象外 class B def b end end
  17. # class scope class A def b end using M

    # Usingより下行は対象 def b # methodの中でも対象 end end # クラスのスコープから外れると対象外 class B def b end end
  18. class A def a "a" end end module M refine

    A do def a "b" end end end class B def b using M # NoMethodError A.new.a end end
  19. module SkipValidation refine ApplicationRecord do def valid? true end end

    end class SomeController def save # バリデーションが行われる # ... end using SkipValidation def force_save # バリデーションがスキップされる # ... end end
  20. module SkipValidation refine ApplicationRecord do def valid? true end end

    end class SomeController def save # バリデーションが行われる # ... end using SkipValidation def force_save # バリデーションがスキップされる # ... end end 3FpOFNFOU͢Δ͜ͱʹΑͬͯ ͲͷΑ͏ͳϞϯΩʔύον͕ͳ͞ΕͯΔ ͔ͷώϯτʹͳͬͯൣғ΋ݶఆͰ͖Δͷ ͕ϝϦοτʁ
  21. module StringOperator refine String do def |(other) SQL.or(self, other) end

    def &(other) SQL.and(self, other) end end end class DSL using StringOperator def self.where(&block) # 現状blockの中にまでusingは適用できない instance_eval(&block) end end # より抽象度の高い記述が簡単なコードで実現できる DSL.where { "a" | "b" & "c" } "a" | "b" # NoMethodError
  22. # Array#sum was added in Ruby 2.4 but it only

    works with Numeric elements. # # We tried shimming it to attempt the fast native method, rescue TypeError, # and fall back to the compatible implementation, but that's much slower than # just calling the compat method in the first place. if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) # Using Refinements here in order not to expose our internal method using Module.new { refine Array do alias :orig_sum :sum end } class Array def sum(init = nil, &block) #:nodoc: if init.is_a?(Numeric) || first.is_a?(Numeric) init ||= 0 orig_sum(init, &block) else super end end end end
  23. require "active_support/core_ext/enumerable" [1,2,3].sum # => 6 ["a","b","c"].sum # => "abc"

    { a: 1, b: 2 }.sum # => [:a, 1, :b, 2] 3BJMT͕&OVNFSBCMF "SSBZʹ΋.JY*O͞Ε͍ͯΔ  ʹ଍͍ͯ͠ΔTVNͱ͍͏ϝιου͕͋ͬͯ ্هͷΑ͏ͳಈ͖Λ͠·͢
  24. [1,2,3].sum # => 6 ["a","b","c"].sum # => TypeError: String can't

    be coerced into Integer 3VCZͰ"SSBZʹTVNϝιου͕ͨ͞Εͯ /BUJWF࣮૷ͳͷͰͦͪΒͷ΄͏͕ૣ͍ Ͱ΋/VNFSJD͕ཁૉͷͱ͖͔͠࢖͑ͳ͍ͷͰ ཁૉ͕/VNFSJDͷ࣌͸/BUJWFͷTVNΛ͔ͭͬͯ ͦΕҎ֎ͷ࣌͸3BJMT͕֦ுͨ͠TVNϝιουΛ࢖͏
  25. if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) # Arrayのインスタンスメソッドにsumがあって文字列の要素のときにエラーになるとい うことは Ruby

    2.4〜 using Module.new { refine Array do alias :orig_sum :sum # Ruby 2.4のsumメソッドにorig_sumとaliasをつける end } class Array def sum(init = nil, &block) #:nodoc: if init.is_a?(Numeric) || first.is_a?(Numeric) # 要素がNumericの時は Ruby 2.4 のsumを呼び出す init ||= 0 orig_sum(init, &block) else # それ以外の時は Rails のsumを呼び出す super end end end end
  26. if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) using Module.new { #

    このModuleはローカル変数なので、このブロックでのみしか存 在しない refine Array do alias :orig_sum :sum # orig_sumはここよりファイルの末尾のみしかアクセスで きない end } class Array def sum(init = nil, &block) #:nodoc: if init.is_a?(Numeric) || first.is_a?(Numeric) init ||= 0 orig_sum(init, &block) else super end end end end
  27. if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) using Module.new { refine

    Array do alias :orig_sum :sum end } class Array def sum(init = nil, &block) #:nodoc: if init.is_a?(Numeric) || first.is_a?(Numeric) init ||= 0 orig_sum(init, &block) else super end end end end
  28. class SomeModel < ApplicationRecord def some "aa".orig_sum #=> NoMethodError end

    end BJBTΛߦͬͨ.PEVMFΛVTJOH͢Δ͜ͱ΋Ͱ͖ͳ͍ͷͰ 4USJOHPSJH@TVNʹΞΫηε͢Δ͜ͱ͸ແཧʹͳΔͷͰ PSJH@TVNNFUIPE͸׬ᘳʹӅṭ͞ΕΔ