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

Rubyモンキーパッチの世界

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for sutetotanuki sutetotanuki
January 14, 2017
2.4k

 Rubyモンキーパッチの世界

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

Avatar for sutetotanuki

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͸׬ᘳʹӅṭ͞ΕΔ