Lets enjoy creating gems

Cfbe23392787cb3ad4689c5f72463fcc?s=47 makicamel
October 03, 2020

Lets enjoy creating gems

継承とメタプログラミング満載なアプリケーションコードでもアクションとフィルタに悩まないためのGemを作った話
- 2020.10.03. Kaigi on Rails STAY HOME Edition

Cfbe23392787cb3ad4689c5f72463fcc?s=128

makicamel

October 03, 2020
Tweet

Transcript

  1. ܧঝͱϝλϓϩάϥϛϯάຬࡌͳ ΞϓϦέʔγϣϯίʔυͰ΋ΞΫγϣϯͱ ϑΟϧλʹ೰·ͳ͍ͨΊͷGemΛ࡞ͬͨ࿩ 2020.10.03. Kaigi on Rails STAY HOME Edition

    @makicamel
  2. w!NBLJDBNFM઒ݪສق wɹ w3VCZͱϏʔϧɹɹͱ͓ञ͕޷͖ ࣗݾ঺հ

  3. ͓खݩͷRails ΞϓϦʹ BQQMJDBUJPO@DPOUSPMMFSSC ͍ͭ͋͘Γ·͔͢ʁ ಥવͰ͕͢

  4. ˞೥݄೔ݱࡏ "/%1"% $ rails stats +----------------------+--------+--------+---------+---------+-----+-------+ | Name | Lines

    | LOC | Classes | Methods | M/C | LOC/M | +----------------------+--------+--------+---------+---------+-----+-------+ | Controllers | 66567 | 52928 | 1030 | 5473 | 5 | 7 | +----------------------+--------+--------+---------+---------+-----+-------+ $ find . -name application_controller.rb | wc -l 3 ׂ͕ ApplicationController # => 325
  5. •֤֊૚͝ͱʹ application_controller.rb ͕͍Δ •֤ ApplicationController ͸ ਌֊૚ͷ ApplicationController Λܧঝ ͞Βʹ

    •ڞ௨ॲཧΛϞδϡʔϧԽ֤ͯ͠ॴͰ include •ϝλϓϩຬࡌ "/%1"% ˞೥݄೔ݱࡏ
  6. Α͋͘Δޫܠ ɹ͜ͷϑΝΠϧΛमਖ਼͢Δͧ BXFTPNF@DPOUSPMMFSSC

  7. ɹݖݶνΣοΫΛ֬ೝ͠Α͏ ᶃ ᶄ ᶅ ᶆ Α͋͘Δޫܠ BXFTPNF@DPOUSPMMFSSC •before_action ΛνΣοΫ

  8. ɹ͑ͬ class Foo::Bar::Baz::ApplicationController < Foo::Bar::ApplicationController include Foo::Common::Bar::Baz::AwesomeController end Α͋͘Δޫܠ BXFTPNF@DPOUSPMMFSSC

  9. BXFTPNF@DPOUSPMMFSSC ɹ͑ͬ͑ͬ class Foo::Bar::Baz::ApplicationController < Foo::Bar::ApplicationController include Foo::Common::Bar::Baz::AwesomeController end module

    Foo::Common::Bar::Baz::AwesomeController include Common::Bar::Baz::AwesomeController end Α͋͘Δޫܠ
  10. Α͋͘Δޫܠ খ͞ͳमਖ਼΍ௐࠪͳͷʹ ܧঝؔ܎Λ֬ೝ͢Δ͚ͩͰർฐ͢Δ

  11. ͱ͍͏Θ͚Ͱ

  12. ࡞ͬͨ NBLJDBNFMBDUJPO@USBDFS IUUQTHJUIVCDPNNBLJDBNFMBDUJPO@USBDFS

  13.   [2020-09-27T03:25:43.018298 #1] [“APPLIED",:set_turbolinks_location_header_from _session, “/usr/local/bundle/gems/turbolinks-… redirection.rb”, 43] [2020-09-27T03:25:43.019410

    #1] ["APPLIED", :verify_authenticity_token, “/usr/ local/bundle/gems/actionpack-…/ request_forgery_protection.rb”, 211] [2020-09-27T03:25:43.021131 #1] ["APPLIED", :require_login, “/myapp/app/ controllers/awesome_controller.rb", 17] [2020-09-27T03:25:43.022063 #1] ["NO_APPLIED", :set_awesome, “/myapp/app/ controllers/awesome_controller.rb", 25] [2020-09-27T03:25:43.023716 #1] ["APPLIED", :with_readonly, “/myapp/app/ controllers/awesome_controller.rb", 21] [2020-09-27T03:25:43.025547 #1] ["ACTION", :index, “/myapp/app/controllers/ awesome_controller.rb", 7] [2020-09-27T03:25:43.026297 #1] ["APPLIED", :with_readonly, “/myapp/app/ controllers/awesome_controller.rb", 21] [2020-09-27T03:25:43.027203 #1] ["APPLIED", :store_location, “/myapp/app/ controllers/awesome_controller.rb", 27] [2020-09-27T03:25:43.030074 #1] ["APPLIED", :verify_same_origin_request, “/usr/ local/bundle/gems/actionpack-…/ request_forgery_protection.rb”, 240] class AwesomeController < ApplicationController before_action :require_login before_action :set_awesome, only: :show around_action :with_readonly after_action :store_location def index # ... end def show # ... end private def require_login # ... end # ... ˞ϩάͷҰ෦Λলུ͍ͯ͠·͢ɻྫ͸3&"%.&ʹ͋Γ·͢ɻ IUUQTHJUIVCDPNNBLJDBNFMBDUJPO@USBDFS ActionTracer ΞΫηε࣌ʹϑΟϧλΛॻ͖ग़͢
  14.  ˞ϩάͷҰ෦Λলུ͍ͯ͠·͢ɻྫ͸3&"%.&ʹ͋Γ·͢ɻ IUUQTHJUIVCDPNNBLJDBNFMBDUJPO@USBDFS ActionTracer •ΞϓϦέʔγϣϯίʔυ͚ͩͰ͸ͳ͘ GemͷϑΟϧλ΋هࡌ [2020-09-27T03:25:43.018298 #1] [“APPLIED",:set_turbolinks_location_header_from _session,

    “/usr/local/bundle/gems/turbolinks-… redirection.rb”, 43] [2020-09-27T03:25:43.019410 #1] ["APPLIED", :verify_authenticity_token, “/usr/ local/bundle/gems/actionpack-…/ request_forgery_protection.rb”, 211] [2020-09-27T03:25:43.021131 #1] ["APPLIED", :require_login, “/myapp/app/ controllers/awesome_controller.rb", 17] [2020-09-27T03:25:43.022063 #1] ["NO_APPLIED", :set_awesome, “/myapp/app/ controllers/awesome_controller.rb", 25] [2020-09-27T03:25:43.023716 #1] ["APPLIED", :with_readonly, “/myapp/app/ controllers/awesome_controller.rb", 21] [2020-09-27T03:25:43.025547 #1] ["ACTION", :index, “/myapp/app/controllers/ awesome_controller.rb", 7] [2020-09-27T03:25:43.026297 #1] ["APPLIED", :with_readonly, “/myapp/app/ controllers/awesome_controller.rb", 21] [2020-09-27T03:25:43.027203 #1] ["APPLIED", :store_location, “/myapp/app/ controllers/awesome_controller.rb", 27] [2020-09-27T03:25:43.030074 #1] ["APPLIED", :verify_same_origin_request, “/usr/ local/bundle/gems/actionpack-…/ request_forgery_protection.rb”, 240] Turbolinks Rails ΞϓϦέʔγϣϯ ݺ͹ΕͨΞΫγϣϯ
  15.  ˞ϩάͷҰ෦Λলུ͍ͯ͠·͢ɻྫ͸3&"%.&ʹ͋Γ·͢ɻ IUUQTHJUIVCDPNNBLJDBNFMBDUJPO@USBDFS ActionTracer •ϑΝΠϧύεɺߦ਺ [2020-09-27T03:25:43.018298 #1] [“APPLIED",:set_turbolinks_location_header_from _session, “/usr/local/bundle/gems/turbolinks-…

    redirection.rb”, 43] [2020-09-27T03:25:43.019410 #1] ["APPLIED", :verify_authenticity_token, “/usr/ local/bundle/gems/actionpack-…/ request_forgery_protection.rb”, 211] [2020-09-27T03:25:43.021131 #1] ["APPLIED", :require_login, “/myapp/app/ controllers/awesome_controller.rb", 17] [2020-09-27T03:25:43.022063 #1] ["NO_APPLIED", :set_awesome, “/myapp/app/ controllers/awesome_controller.rb", 25] [2020-09-27T03:25:43.023716 #1] ["APPLIED", :with_readonly, “/myapp/app/ controllers/awesome_controller.rb", 21] [2020-09-27T03:25:43.025547 #1] ["ACTION", :index, “/myapp/app/controllers/ awesome_controller.rb", 7] [2020-09-27T03:25:43.026297 #1] ["APPLIED", :with_readonly, “/myapp/app/ controllers/awesome_controller.rb", 21] [2020-09-27T03:25:43.027203 #1] ["APPLIED", :store_location, “/myapp/app/ controllers/awesome_controller.rb", 27] [2020-09-27T03:25:43.030074 #1] ["APPLIED", :verify_same_origin_request, “/usr/ local/bundle/gems/actionpack-…/ request_forgery_protection.rb”, 240]
  16.  ˞ϩάͷҰ෦Λলུ͍ͯ͠·͢ɻྫ͸3&"%.&ʹ͋Γ·͢ɻ IUUQTHJUIVCDPNNBLJDBNFMBDUJPO@USBDFS ActionTracer •ͦͷϑΟϧλ͕ ࣮ࡍʹద༻͞Ε͔ͨ൱͔ •etc … [2020-09-27T03:25:43.018298 #1]

    [“APPLIED",:set_turbolinks_location_header_from _session, “/usr/local/bundle/gems/turbolinks-… redirection.rb”, 43] [2020-09-27T03:25:43.019410 #1] ["APPLIED", :verify_authenticity_token, “/usr/ local/bundle/gems/actionpack-…/ request_forgery_protection.rb”, 211] [2020-09-27T03:25:43.021131 #1] ["APPLIED", :require_login, “/myapp/app/ controllers/awesome_controller.rb", 17] [2020-09-27T03:25:43.022063 #1] ["NO_APPLIED", :set_awesome, “/myapp/app/ controllers/awesome_controller.rb", 25] [2020-09-27T03:25:43.023716 #1] ["APPLIED", :with_readonly, “/myapp/app/ controllers/awesome_controller.rb", 21] [2020-09-27T03:25:43.025547 #1] ["ACTION", :index, “/myapp/app/controllers/ awesome_controller.rb", 7] [2020-09-27T03:25:43.026297 #1] ["APPLIED", :with_readonly, “/myapp/app/ controllers/awesome_controller.rb", 21] [2020-09-27T03:25:43.027203 #1] ["APPLIED", :store_location, “/myapp/app/ controllers/awesome_controller.rb", 27] [2020-09-27T03:25:43.030074 #1] ["APPLIED", :verify_same_origin_request, “/usr/ local/bundle/gems/actionpack-…/ request_forgery_protection.rb”, 240]
  17. ActionTracerͷͭ͘Γ͔ͨ

  18. ActionTracer •ίʔυϕʔε͸খ͍͞ʢ200ߦະຬʣ

  19. TracePoint DMBTT5SBDF1PJOUc3VCZϦϑΝϨϯεϚχϡΞϧ IUUQTEPDTSVCZMBOHPSHKBDMBTT5SBDF1PJOUIUNM

  20. TracePoint •ॲཧΛొ࿥͓͖ͯ͠ɺ֤छΠϕϯτΛ͖͔͚ͬʹ࣮ߦͰ͖Δ •ΠϕϯτྫʢҰ෦ʣ •:line •:call •:return •:c_call •:c_return ࣜͷධՁ Ruby

    Ͱهड़͞Εͨϝιουͷݺͼग़͠ Ruby Ͱهड़͞Εͨϝιουݺͼग़͔͠ΒͷϦλʔϯ C Ͱهड़͞Εͨϝιουͷݺͼग़͠ C Ͱهड़͞Εͨϝιουݺͼग़͔͠ΒͷϦλʔϯ
  21. TracePoint class AwesomeController < ApplicationController def index TracePoint.trace(:call) do |tp|

    p "#{tp.method_id}@#{tp.path}:#{tp.lineno}" end end # ...
  22. None
  23. TracePoint •ॲཧΛొ࿥͓͖ͯ͠ɺ֤छΠϕϯτΛ͖͔͚ͬʹ࣮ߦͰ͖Δ •ΠϕϯτྫʢҰ෦ʣ •:line •:call •:return ࣜͷධՁ Ruby Ͱهड़͞Εͨϝιουͷݺͼग़͠ Ruby

    Ͱهड़͞Εͨϝιουݺͼग़͔͠ΒͷϦλʔϯ ΞϓϦέʔγϣϯίʔυ΋ Rails ͷίʔυ΋۠ผ͞Εͳ͍ ͍͍ײ͡ʹநग़͢ΔͱΑ͍ͷͰ͸
  24. RailsͷίʔυΛ ಡΜͰΈΑ͏

  25. before_action :login_required ͕࣮ߦ͞ΕΔ·Ͱ •Callback ͷొ࿥ •Callback ͷ࣮ߦ •Callback ͷొ࿥ •Callback

    ͷ࣮ߦ
  26. ओͳొ৔ਓ෺ •ActiveSupport::Callbacks •AbstractController::Callbacks

  27. ActiveSupport::Callbacks module ActiveSupport module Callbacks module Conditionals # :nodoc: module

    Filters class Callback #:nodoc:# class CallTemplate # :nodoc: class CallbackSequence # :nodoc: class CallbackChain #:nodoc:# module ClassMethods DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC
  28. AbstractController::Callbacks •AbstractController ͸ ApplicationController ͷ਌Ϋϥε ApplicationController < ActionController::Base < ActionController::Metal

    < AbstractController::Base
  29. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCFEDDDGBDUJPOQBDLMJCBCTUSBDU@DPOUSPMMFSDBMMCBDLTSC module AbstractController module Callbacks # ... included do

    define_callbacks( :process_action, terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc) controller.performed? }, skip_after_callbacks_if_terminated: true ) end # ... AbstractController
  30. module ActiveSupport module Callbacks module Conditionals module Filters class Callback

    class CallTemplate class CallbackSequence class CallbackChain module ClassMethods DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def define_callbacks(*names) options = names.extract_options! names.each do |name| name = name.to_sym set_callbacks name, CallbackChain.new(name, options) module_eval <<-RUBY, __FILE__, __LINE__ + 1 def _run_#{name}_callbacks(&block) run_callbacks #{name.inspect}, &block end def self._#{name}_callbacks get_callbacks(#{name.inspect}) end def self._#{name}_callbacks=(value) set_callbacks(#{name.inspect}, value) end def _#{name}_callbacks __callbacks[#{name.inspect}] end RUBY end end ClassMethods
  31. CallbackChain DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC module ActiveSupport module Callbacks module Conditionals module

    Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods class CallbackChain #:nodoc:# include Enumerable # ... def initialize(name, config) @name = name @config = { scope: [:kind], terminator: default_terminator }.merge!(config) @chain = [] @callbacks = nil @mutex = Mutex.new end # ... end # An Array with a compile method. CallbackΠϯελϯε͕ೖΔ ഑ྻϥΠΫͳΫϥε
  32. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def define_callbacks(*names) options = names.extract_options! names.each do |name|

    name = name.to_sym set_callbacks name, CallbackChain.new(name, options) module_eval <<-RUBY, __FILE__, __LINE__ + 1 def _run_#{name}_callbacks(&block) run_callbacks #{name.inspect}, &block end def self._#{name}_callbacks get_callbacks(#{name.inspect}) end def self._#{name}_callbacks=(value) set_callbacks(#{name.inspect}, value) end def _#{name}_callbacks __callbacks[#{name.inspect}] end RUBY end end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods ClassMethods
  33. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def set_callbacks(name, callbacks) # :nodoc: self.__callbacks = __callbacks.merge(name.to_sym

    => callbacks) end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods ClassMethods
  34. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCFEDDDGBDUJPOQBDLMJCBCTUSBDU@DPOUSPMMFSDBMMCBDLTSC module AbstractController module Callbacks module ClassMethods [:before, :after,

    :around].each do |callback| define_method "#{callback}_action" do |*names, &blk| _insert_callbacks(names, blk) do |name, options| set_callback(:process_action, callback, name, options) end end # ... end end end end AbstractController
  35. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def set_callback(name, *filter_list, &block) type, filters, options =

    normalize_callback_params(filter_list, block) # ... self_chain = get_callbacks name mapped = filters.map do |filter| Callback.build(self_chain, filter, type, options) end __update_callbacks(name) do |target, chain| options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) target.set_callbacks name, chain end end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods ClassMethods
  36. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC class Callback #:nodoc:# # ... def initialize(name,filter,kind,options,chain_config) @chain_config

    = chain_config @name = name # => :process_action @kind = kind # => :before @filter = filter # => :login_required @key = compute_identifier filter # => :login_required @if = Array(options[:if]) # => :[#<Proc:0x0000123456789012>] @unless = Array(options[:unless]) # => [] end # ... module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods Callback ίʔϧόοΫຊମ before_action :login_required, except: :destroy ͷ৔߹
  37. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def set_callback(name, *filter_list, &block) type, filters, options =

    normalize_callback_params(filter_list, block) # ... self_chain = get_callbacks name mapped = filters.map do |filter| Callback.build(self_chain, filter, type, options) end __update_callbacks(name) do |target, chain| options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) target.set_callbacks name, chain end end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods ClassMethods
  38. before_action :login_required ͕࣮ߦ͞ΕΔ·Ͱ •Callback ͷొ࿥ •Callback ͷ࣮ߦ •Callback ͷొ࿥ ɹɹɹɹɹɹɹɹ

  39. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCFEDDDGBDUJPOQBDLMJCBCTUSBDU@DPOUSPMMFSDBMMCBDLTSC AbstractController module AbstractController module Callbacks # ... def

    process_action(*args) run_callbacks(:process_action) do super end end end end
  40. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def run_callbacks(kind) callbacks = __callbacks[kind.to_sym] # ... next_sequence

    = callbacks.compile invoke_sequence = Proc.new do # ... end # Common case: no 'around' callbacks defined if next_sequence.final? # ... else invoke_sequence.call end # ... end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods Callbacks
  41. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC class CallbackChain #:nodoc:# # ... def compile @callbacks

    || @mutex.synchronize do final_sequence = CallbackSequence.new @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| callback.apply callback_sequence end end end # ... end # An Array with a compile method. module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods CallbackChain CallbackΠϯελϯε͕ೖΔ഑ྻΛ ࣋ͭΫϥε
  42. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC class CallbackSequence # :nodoc: def initialize( nested =

    nil, call_template = nil, user_conditions = nil ) @nested = nested @call_template = call_template @user_conditions = user_conditions @before = [] @after = [] end # ... end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods CallbackSequence ॱং௨ΓʹίʔϧόοΫΛอ؅͠ ࣮ߦ͍ͯ͘͠Ϋϥε
  43. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC class CallbackChain #:nodoc:# # ... def compile @callbacks

    || @mutex.synchronize do final_sequence = CallbackSequence.new @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| callback.apply callback_sequence end end end # ... end # An Array with a compile method. module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods CallbackChain CallbackΠϯελϯε͕ೖΔ഑ྻΛ ࣋ͭΫϥε
  44. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def apply(callback_sequence) user_conditions = conditions_lambdas user_callback = CallTemplate.build(@filter,

    self) case kind when :before Filters::Before.build( callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter ) when :after Filters::After.build( # before ͱಉ ) when :around callback_sequence.around( user_callback, user_conditions ) end end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods Callback ίʔϧόοΫຊମ
  45. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC class CallTemplate # :nodoc: # ... def self.build(filter,

    callback) case filter when Symbol new(nil, filter, [], nil) when String new( nil, :instance_exec, [:value], compile_lambda(filter) ) # ... end end # ... end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods CallTemplate ίʔϧόοΫΛ࣮ߦ͢Δ ProcΛ࡞ΔΫϥε
  46. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def apply(callback_sequence) user_conditions = conditions_lambdas user_callback = CallTemplate.build(@filter,

    self) case kind when :before Filters::Before.build( callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter ) when :after Filters::After.build( # before ͱಉ ) when :around callback_sequence.around( user_callback, user_conditions ) end end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods Callback ίʔϧόοΫຊମ
  47. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC module Filters # ... class Before def self.build(

    callback_sequence, user_callback, user_conditions, chain_config, filter ) halted_lambda = chain_config[:terminator] if user_conditions.any? # ... else halting( callback_sequence, user_callback, halted_lambda, filter ) end end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods Filters if, unlessΛνΣοΫ͠ඞཁͳΒ ίʔϧόοΫΛhalt͢ΔΫϥε
  48. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def self.halting( callback_sequence, user_callback, halted_lambda, filter ) callback_sequence.before

    do |env| target = env.target value = env.value halted = env.halted unless halted result_lambda = -> { user_callback.call target, value } env.halted = halted_lambda.call(target, result_lambda) if env.halted target.send :halted_callback_hook, filter end end env end end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods Filters if, unlessΛνΣοΫ͠ඞཁͳΒ ίʔϧόοΫΛhalt͢ΔΫϥε
  49. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC module ActiveSupport module Callbacks module Conditionals module Filters

    class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods def self.halting( callback_sequence, user_callback, halted_lambda, filter ) callback_sequence.before do |env| target = env.target value = env.value halted = env.halted unless halted result_lambda = -> { user_callback.call target, value } env.halted = halted_lambda.call(target, result_lambda) if env.halted target.send :halted_callback_hook, filter end end env end end def apply(callback_sequence) # ... user_callback = CallTemplate.build # ... case kind when :before Filters::Before.build( callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter ) Filters
  50. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def make_lambda lambda do |target, value, &block| target,

    block, method, *arguments = expand(target, value, block) target.send(method, *arguments, &block) end end module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods CallTemplate ίʔϧόοΫΛ࣮ߦ͢Δ ProcΛ࡞ΔΫϥε
  51. DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def expand(target, value, block) result = @arguments.map {

    |arg| case arg when :value; value when :target; target when :block; block || raise(ArgumentError) end } result.unshift @method_name result.unshift @override_block || block result.unshift @override_target || target # target, block, method, *arguments = result # target.send(method, *arguments, &block) result end ϦΫΤετΛड͚ͨίϯτϩʔϥ before_actionͰొ࿥ͨ͠
 Πϯελϯεϝιου module ActiveSupport module Callbacks module Conditionals module Filters class Callback class CallTemplate class CallbackSequence class CallbackChain module ClassMethods CallTemplate ίʔϧόοΫΛ࣮ߦ͢Δ ProcΛ࡞ΔΫϥε AwesomeController.new.send(:login_required) ͱҰॹʂʂ
  52. None
  53. TracePoint ͍Βͳ͘ͳ͍ʁ

  54. औΕͨ class AwesomeController < ApplicationController def index p self.__callbacks[:process_action].class #

    => ActiveSupport::Callbacks::CallbackChain p self.__callbacks[:process_action].send(:chain).first # => #<ActiveSupport::Callbacks::Callback:0x0000123456789001 # @chain_config= # {:scope=>[:kind], # :terminator=>#<Proc:0x0000123456789012>, # :skip_after_callbacks_if_terminated=>true}, # @filter=:require_login, # @if=[#<Proc:0x0000123456789012>], # @key=:require_login, # @kind=:before, # @name=:process_action, # @unless=[]> end end
  55. 5JNFUBCMFc,BJHJPO3BJMT IUUQTLBJHJPOSBJMTPSHUJNFUBCMF Ϡό͍

  56. ͱΓ͋͑ͣ։ൃΛਐΊΔ ࢖Θͳ͔ͬͨΒँΖ͏

  57. AbstractController::Callbacks module AbstractController module Callbacks # ... def process_action(*args) run_callbacks(:process_action)

    do super end end end end ϞϯΩʔύονͰ͜ͷݺͼग़͠ޙʹ ͍͍ײ͡ʹॻ͖ग़ͯ͋͛ͨ͠ΒΑͦ͞͏
  58. ϞϯΩʔύον module ActionTracer module MonkeyPatches module AbstractController module Callbacks def

    process_action(*args) ActionTracer.log(self) do super end end end end end end AbstractController::Callbacks#process_action ΛΦʔόʔϥΠυ
  59. ͍͍ײ͡ʹॻ͖ग़͢ module ActionTracer class << self def log(controller) result =

    yield Filters.build(controller).print result end end end
  60. ͍͍ײ͡ʹॻ͖ग़͢ class ActionTracer::Filters def self.build(controller) # ... raw_filters = controller.__callbacks[:process_action].send(:chain)

    raw_filters.group_by(&:kind).each do |kind, filter| filters[kind] = filter.map(&:raw_filter).map do |f| Filter.new(f, method: f.is_a?(Symbol) ? controller.method(f) : f) end end # ... end def print (@before + @around).map(&:to_a).each do |filter| ActionTracer.logger.info filter end # ... end end
  61. I, [2020-09-23T01:51:32.772847 #2] INFO -- : [:verify_authenticity_token, “/usr/local/bundle/gems/actionpack-5.1.7/lib/action_controller/ metal/request_forgery_protection.rb", 211]

    ͍͍ײ͡ʹॻ͖ग़͢ class ActionTracer::Filter # ... def to_a [@filter, *@method.source_location] end end Ͱ͖ͨ
  62. खݩͷ͓࢓ࣄͰ࢖ͬͯΈΔ •࠷ߴ •ࠓ·ͰͻͱͭͣͭܧঝݩΛͨͲͬͯ֬ೝ͍ͯͨ͠ͷ͸ͳΜͩͬͨͷ͔ •RSpec Ͱ࢖ͬͯ΋ศརͱ͔ɺϢʔεέʔεΛࢥ͍ͭ͘ •ཁ๬΋ग़ͯ͘Δ •PR ʹهࡌ͢ΔͷʹϑΟϧλ໊͚ͩ map ͓ͯ͠खܰʹίϐʔ͍ͨ͠

    •࣮ࡍʹϑΟϧλ͕ద༻͞Ε͔ͨ΋஌Γ͍ͨ
  63. ͦ͜Ͱ TracePoint Α͔ͬͨ

  64. ActiveSupport::Callbacks DBMMCBDLTSCcSBJMTSBJMT IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDUJWF@TVQQPSUDBMMCBDLTSC def expand(target, value, block) result = @arguments.map

    { |arg| case arg when :value; value when :target; target when :block; block || raise(ArgumentError) end } result.unshift @method_name result.unshift @override_block || block result.unshift @override_target || target # target, block, method, *arguments = result # target.send(method, *arguments, &block) result end expand ͕ݺ͹ΕΔͷ͸ ࣮ࡍʹ࣮ߦ͞ΕΔίʔϧόοΫ͚ͩ
  65. module ActionTracer def self.filter_collector @filter_collector ||= TracePoint.new(:return) do |tp| #

    NOTE: ActiveSupport::Callbacks::CallTemplate is a private class if tp.method_id == :expand && tp.defined_class == ActiveSupport::Callbacks::CallTemplate if tp.return_value&.first.is_a? ActionController::Base case tp.return_value[2] # ... when Symbol # filter is a method applied_filters << tp.return_value[2] end end end end end def self.applied_filters @applied_filters ||= [] end end BDUJPO@USBDFSSCcNBLJDBNFMBDUJPO@USBDFS IUUQTHJUIVCDPNNBLJDBNFMBDUJPO@USBDFSCMPCGMJCBDUJPO@USBDFSBDUJPO@USBDFSSC
  66.  ग़ྗͰ͖ͨ I, [2020-09-27T03:25:43.018298 #1] INFO -- : [“APPLIED",:set_turbolinks_location_header_from _session,

    “/usr/local/bundle/gems/turbolinks-redirection.rb”, 43] I, [2020-09-27T03:25:43.019410 #1] INFO -- : ["APPLIED", :verify_authenticity_token, “/usr/local/bundle/gems/actionpack-…/request_forgery_protection.rb”, 211] I, [2020-09-27T03:25:43.021131 #1] INFO -- : ["APPLIED", :require_login, “/myapp/app/controllers/awesome_controller.rb", 17] I, [2020-09-27T03:25:43.022063 #1] INFO -- : ["NO_APPLIED", :set_awesome, “/myapp/app/controllers/awesome_controller.rb", 25] I, [2020-09-27T03:25:43.023716 #1] INFO -- : ["APPLIED", :with_readonly, “/myapp/app/controllers/awesome_controller.rb", 21] I, [2020-09-27T03:25:43.025547 #1] INFO -- : ["ACTION", :index, “/myapp/app/controllers/awesome_controller.rb", 7] I, [2020-09-27T03:25:43.026297 #1] INFO -- : ["APPLIED", :with_readonly, “/myapp/app/controllers/awesome_controller.rb", 21] I, [2020-09-27T03:25:43.027203 #1] INFO -- : ["APPLIED", :store_location, “/myapp/app/controllers/awesome_controller.rb", 27] I, [2020-09-27T03:25:43.030074 #1] INFO -- : ["APPLIED", :verify_same_origin_request, “/usr/local/bundle/gems/actionpack-…/request_forgery_protection.rb”, 240]
  67. ;ΓฦΓ

  68. ;ΓฦΓ •Gem Λ࡞Δͷʹඞཁͳ෺ͬͯԿͩΖ͏ •ٕज़ͱ஌ࣝʁ •ΞΠσΟΞʁ

  69. ٕज़ͱ஌ࣝʁ •ActionTracer Ͱ΍ͬͨ͜ͱ͸΄΅ Rails ͷίʔυϦʔσΟϯά •ٕज़ྗ͕͋Δʹӽͨ͜͠ͱ͸ͳ͍ʢͦΕ͸ͦ͏ʣ •ٕज़͕͋Δ͔Β Gem Λ࡞ΔͷͰ͸ͳ͘࡞Δ͜ͱͰྗ͕͍͍ͭͯ͘ •ඞཁ͸ൃ໌ͱ੒௕ͷ฼

  70. ྫ͑͹ •TracePoint ΛԿ΋ߟ͑ͣʹ࢖͏ͱॏ͘ͳΔ •શͯͷΠϕϯτͰൃՐ͢ΔͷͰ •࣮ߦ࣌ʹΦϓγϣϯΛ౉ͯ͠࢖͍͍͚ͨ࣌ͩ࢖͍͍ͨ •bundle exec rails server -actiontracer

    on Έ͍ͨͳײ͡ʁ
  71. ೉ͦ͠͏ •·ͨϞϯΩʔύον…ʁ •࠷࣮ۙߦ࣌Φϓγϣϯ౉ͨ͠ྫΛݟͨͧ LLVCVOSTQFDPQFOBQJ IUUQTHJUIVCDPNLLVCVOSTQFDPQFOBQJ

  72. rspec-openapi •RSpec ͷϦΫΤετ spec ͔Β OpenAPI ͷεΩʔϚΛ࡞੒͢Δ Gem •͜Εͩʂ LLVCVOSTQFDPQFOBQJ

    IUUQTHJUIVCDPNLLVCVOSTQFDPQFOBQJ
  73. rspec-openapi PQFOBQJSCcLLVCVOSTQFDPQFOBQJ IUUQTHJUIVCDPNLLVCVOSTQFDPQFOBQJCMPCGEEMJCSTQFDPQFOBQJSC require 'rspec/openapi/version' require 'rspec/openapi/hooks' if ENV['OPENAPI'] module

    RSpec::OpenAPI # ... end
  74. ActionTracer BDUJPO@USBDFSSCcNBLJDBNFMBDUJPO@USBDFS IUUQTHJUIVCDPNNBLJDBNFMBDUJPO@USBDFSCMPCCGEBMJCBDUJPO@USBDFSSC require "action_tracer/version" require "action_tracer/railtie" if ENV["ACTION_TRACER"] require

    "action_tracer/filters" require "action_tracer/logger" require "action_tracer/action_tracer" ੈͷதʹ͸ແ਺ͷ͓खຊίʔυ͕͋Δ
  75. ͱʹ͔͘ॻ͘ •౴͑ͷͳ͍ঢ়ଶͰॻ͖࢝ΊΔͱ͏·͘ॻ͚ͳ͍ •Ԛ͍ίʔυΛॻ͜͏ͱ͢ΔͱϞϠοͱ͢Δ •ʮ·ͣ͸Ԛͯ͘΋ॻ͍͔ͯΒ͖Ε͍ʹ͢Δʯͱࣗ෼ʹݴ͍ฉ͔ͤΔ •ॻ͘ͱ೴ΈͦͷϝϞϦ͕ۭ͘ͷͰࢥߟ͕ਐΉ •git ͷྺ࢙͸վมՄೳ

  76. Πϯλʔωοτʹฉ͘ •ʮgem ࡞Γํʯ ͸͡Ίͯͷࣗ࡞HFNʮ)FMMP 5BNBʯΛग़ྗͯ͠ΈΔ!DPF@c2JJUB IUUQTRJJUBDPNDPF@JUFNTFBGFGBCF

  77. ΞΠσΟΞʁ ;0;0ςΫϊϩδʔζͷٕज़ސ໰؛઒ࢯɺদాࢯɺ.BU[ࢯʹฉ͘ΤϯδχΞਓੜɻʙͱʹ͔͘ॻ͘ɺॻ͘͜ͱΛָ͠Ήʙ IUUQTUFDICMPH[P[PDPNFOUSZ@JOUFSWJFX ݱ৔ͷࠓ͋Δ՝୊Λ ͖ͪΜͱΦʔϓϯͳίʔυͰղܾ͢Δ

  78. ɹ૸ͬͯΔϑΟϧλ͕Θ͔Βͳ͍ͷͭΒ͍Ͱ͢Ͷ ɹͭΒ͍Ͱ͢Ͷ… ɹҰཡͰݟ͍ͨͰ͢Ͷ ɹͦΕͩʂʂ ʮݱ৔ͷࠓ͋Δ՝୊ʯ

  79. •ΞϓϦ͕஗͍ •CI ͕஗͍ •ϦϦʔε࡞ۀ͕஗͍ •։ൃ଎౓͕஗͍ •ίʔυ͕ಡΈʹ͍͘ •ίʔυ͕Ԛ͍ •etc etc… ʮݱ৔ͷࠓ͋Δ՝୊ʯ

    ෆద੾ͳΤϥʔϋϯυϦϯά ଐਓԽ͍ͯ͠ΔΤϥʔ؂ࢹ ແବͳSQLͷൃߦ ίʔσΟϯάϧʔϧ͕গͳ͍ ແடংͳϝλϓϩ େྔͷσουίʔυ ࢖ΘΕ͍ͯͳ͍ΞΫγϣϯ େྔͷApplicationController ਖ਼نԽ͞Ε͍ͯͳ͍ςʔϒϧ ൿ఻ͷλϨscope ௒ઈFatModel γεςϜεϖοΫ͕গͳ͍ ແବͳςετσʔλͷ࡞੒ ΦϨΦϨϙϦϞʔϑΟοΫςʔϒϧ ߦ͖ա͗ͨڞ௨Խ
  80. •ͲΜͳݱ৔ʹ΋՝୊͸͋Δ •՝୊ͷΞϓϩʔνํ๏͸৭ʑ •ࠜຊղܾɾԠٸख౰ •ͭΒΈΛݴޠԽ͢ΔͱରԠํ๏͕ੜ·ΕΔͷͰޱʹग़͢ ʮݱ৔ͷࠓ͋Δ՝୊ʯ

  81. ετϨεʹහײʹͳΔ •ϓϩάϥϚͷࡾେඒಙ •ଵଦɾ୹ؾɾၗຫ OE4UBUFPGUIF0OJPOc-BSSZ8BMM IUUQTXXXQFSMDPNQVCTIPXPOJPOIUNM ఘΊͣʹେਓʹͳΒͣʹΧοͱͳͬͯ΍Δ

  82. ࡞Δͷ͸ָ͍͠ •ϥΠϒϥϦͷίʔυΛಡΉػձ͕૿͑Δ •ʮ͜ͷؒݟͨʂʯ͕૿͑Δ •ͦͷύλʔϯ͕ͦͷύλʔϯͰ͋Δཧ༝Λߟ͑Δͷ΋ָ͍͠ •Α͍ύλʔϯ͕ࣗ෼ͷதʹ஝ੵ͞Ε͍͖ͯͦ͏

  83. ࡞Δͷ͸ָ͍͠ •ࣗ෼͕Ұ൪ͷϢʔβʔͰ͋Δಓ۩ •࢖͏ͱ࠷ߴʹؾ͍͍࣋ͪ •ਓ͕࢖ͬͯتΜͰ͘ΕΔͱخ͍͠

  84. ·ͱΊ •࡞Δͷ͸ָ͍͠ •ٕज़ྗ͸ޙ͔Β͍ͭͯ͘Δ •՝୊͸൒ܘ 5m ͷ਎ͷճΓʹ͖ͬͱ͋Δ

  85. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠