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

Lets enjoy creating gems

makicamel
October 03, 2020

Lets enjoy creating gems

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

makicamel

October 03, 2020
Tweet

More Decks by makicamel

Other Decks in Programming

Transcript

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

    View Slide

  2. w!NBLJDBNFM઒ݪສق

    w3VCZͱϏʔϧɹɹͱ͓ञ͕޷͖
    ࣗݾ঺հ

    View Slide

  3. ͓खݩͷRails ΞϓϦʹ
    [email protected]
    ͍ͭ͋͘Γ·͔͢ʁ
    ಥવͰ͕͢

    View Slide

  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

    View Slide

  5. •֤֊૚͝ͱʹ application_controller.rb ͕͍Δ
    •֤ ApplicationController ͸
    ਌֊૚ͷ ApplicationController Λܧঝ
    ͞Βʹ
    •ڞ௨ॲཧΛϞδϡʔϧԽ֤ͯ͠ॴͰ include
    •ϝλϓϩຬࡌ
    "/%1"%
    ˞೥݄೔ݱࡏ

    View Slide

  6. Α͋͘Δޫܠ
    ɹ͜ͷϑΝΠϧΛमਖ਼͢Δͧ
    [email protected]

    View Slide

  7. ɹݖݶνΣοΫΛ֬ೝ͠Α͏




    Α͋͘Δޫܠ
    [email protected]
    •before_action ΛνΣοΫ

    View Slide

  8. ɹ͑ͬ
    class Foo::Bar::Baz::ApplicationController < Foo::Bar::ApplicationController
    include Foo::Common::Bar::Baz::AwesomeController
    end
    Α͋͘Δޫܠ
    [email protected]

    View Slide

  9. [email protected]
    ɹ͑ͬ͑ͬ
    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
    Α͋͘Δޫܠ

    View Slide

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

    View Slide

  11. ͱ͍͏Θ͚Ͱ

    View Slide

  12. View Slide



  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&"%.&ʹ͋Γ·͢ɻ
    [email protected]
    ActionTracer
    ΞΫηε࣌ʹϑΟϧλΛॻ͖ग़͢

    View Slide


  14. ˞ϩάͷҰ෦Λলུ͍ͯ͠·͢ɻྫ͸3&"%.&ʹ͋Γ·͢ɻ
    [email protected]
    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
    ΞϓϦέʔγϣϯ
    ݺ͹ΕͨΞΫγϣϯ

    View Slide


  15. ˞ϩάͷҰ෦Λলུ͍ͯ͠·͢ɻྫ͸3&"%.&ʹ͋Γ·͢ɻ
    [email protected]
    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]

    View Slide


  16. ˞ϩάͷҰ෦Λলུ͍ͯ͠·͢ɻྫ͸3&"%.&ʹ͋Γ·͢ɻ
    [email protected]
    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]

    View Slide

  17. ActionTracerͷͭ͘Γ͔ͨ

    View Slide

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

    View Slide

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

    View Slide

  20. TracePoint
    •ॲཧΛొ࿥͓͖ͯ͠ɺ֤छΠϕϯτΛ͖͔͚ͬʹ࣮ߦͰ͖Δ
    •ΠϕϯτྫʢҰ෦ʣ
    •:line
    •:call
    •:return
    •:c_call
    •:c_return
    ࣜͷධՁ
    Ruby Ͱهड़͞Εͨϝιουͷݺͼग़͠
    Ruby Ͱهड़͞Εͨϝιουݺͼग़͔͠ΒͷϦλʔϯ
    C Ͱهड़͞Εͨϝιουͷݺͼग़͠
    C Ͱهड़͞Εͨϝιουݺͼग़͔͠ΒͷϦλʔϯ

    View Slide

  21. TracePoint
    class AwesomeController < ApplicationController
    def index
    TracePoint.trace(:call) do |tp|
    p "#{tp.method_id}@#{tp.path}:#{tp.lineno}"
    end
    end
    # ...

    View Slide

  22. View Slide

  23. TracePoint
    •ॲཧΛొ࿥͓͖ͯ͠ɺ֤छΠϕϯτΛ͖͔͚ͬʹ࣮ߦͰ͖Δ
    •ΠϕϯτྫʢҰ෦ʣ
    •:line
    •:call
    •:return
    ࣜͷධՁ
    Ruby Ͱهड़͞Εͨϝιουͷݺͼग़͠
    Ruby Ͱهड़͞Εͨϝιουݺͼग़͔͠ΒͷϦλʔϯ
    ΞϓϦέʔγϣϯίʔυ΋ Rails ͷίʔυ΋۠ผ͞Εͳ͍
    ͍͍ײ͡ʹநग़͢ΔͱΑ͍ͷͰ͸

    View Slide

  24. RailsͷίʔυΛ
    ಡΜͰΈΑ͏

    View Slide

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

    View Slide

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

    View Slide

  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
    [email protected]CBDLTSC

    View Slide

  28. AbstractController::Callbacks
    •AbstractController ͸ ApplicationController ͷ਌Ϋϥε
    ApplicationController
    < ActionController::Base
    < ActionController::Metal
    < AbstractController::Base

    View Slide

  29. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]FSDBMMCBDLTSC
    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

    View Slide

  30. module ActiveSupport
    module Callbacks
    module Conditionals
    module Filters
    class Callback
    class CallTemplate
    class CallbackSequence
    class CallbackChain
    module ClassMethods
    DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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 <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

    View Slide

  31. CallbackChain
    DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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Πϯελϯε͕ೖΔ
    ഑ྻϥΠΫͳΫϥε

    View Slide

  32. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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 <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

    View Slide

  33. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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

    View Slide

  34. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]FSDBMMCBDLTSC
    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

    View Slide

  35. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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

    View Slide

  36. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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])
    # => :[#]
    @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
    ͷ৔߹

    View Slide

  37. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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

    View Slide

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

    View Slide

  39. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]FSDBMMCBDLTSC
    AbstractController
    module AbstractController
    module Callbacks
    # ...
    def process_action(*args)
    run_callbacks(:process_action) do
    super
    end
    end
    end
    end

    View Slide

  40. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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

    View Slide

  41. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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Πϯελϯε͕ೖΔ഑ྻΛ
    ࣋ͭΫϥε

    View Slide

  42. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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
    ॱং௨ΓʹίʔϧόοΫΛอ؅͠
    ࣮ߦ͍ͯ͘͠Ϋϥε

    View Slide

  43. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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Πϯελϯε͕ೖΔ഑ྻΛ
    ࣋ͭΫϥε

    View Slide

  44. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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
    ίʔϧόοΫຊମ

    View Slide

  45. DBMMCBDLTSCcSBJMTSBJMT
    IUUQTHJUIVCDPNSBJMTSBJMTCMPCDBBDUJWFTVQQPSUMJCBDU[email protected]
    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Λ࡞ΔΫϥε

    View Slide

  46. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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
    ίʔϧόοΫຊମ

    View Slide

  47. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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͢ΔΫϥε

    View Slide

  48. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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͢ΔΫϥε

    View Slide

  49. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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

    View Slide

  50. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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Λ࡞ΔΫϥε

    View Slide

  51. DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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)
    ͱҰॹʂʂ

    View Slide


  52. View Slide

  53. View Slide

  54. TracePoint ͍Βͳ͘ͳ͍ʁ

    View Slide

  55. औΕͨ
    class AwesomeController < ApplicationController
    def index
    p self.__callbacks[:process_action].class
    # => ActiveSupport::Callbacks::CallbackChain
    p self.__callbacks[:process_action].send(:chain).first
    # => ## @chain_config=
    # {:scope=>[:kind],
    # :terminator=>#,
    # :skip_after_callbacks_if_terminated=>true},
    # @filter=:require_login,
    # @if=[#],
    # @key=:require_login,
    # @kind=:before,
    # @name=:process_action,
    # @unless=[]>
    end
    end

    View Slide

  56. 5JNFUBCMFc,BJHJPO3BJMT
    IUUQTLBJHJPOSBJMTPSHUJNFUBCMF
    Ϡό͍

    View Slide

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

    View Slide

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

    View Slide

  59. ϞϯΩʔύον
    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
    ΛΦʔόʔϥΠυ

    View Slide

  60. ͍͍ײ͡ʹॻ͖ग़͢
    module ActionTracer
    class << self
    def log(controller)
    result = yield
    Filters.build(controller).print
    result
    end
    end
    end

    View Slide

  61. ͍͍ײ͡ʹॻ͖ग़͢
    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

    View Slide

  62. 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
    Ͱ͖ͨ

    View Slide

  63. खݩͷ͓࢓ࣄͰ࢖ͬͯΈΔ
    •࠷ߴ
    •ࠓ·ͰͻͱͭͣͭܧঝݩΛͨͲͬͯ֬ೝ͍ͯͨ͠ͷ͸ͳΜͩͬͨͷ͔
    •RSpec Ͱ࢖ͬͯ΋ศརͱ͔ɺϢʔεέʔεΛࢥ͍ͭ͘
    •ཁ๬΋ग़ͯ͘Δ
    •PR ʹهࡌ͢ΔͷʹϑΟϧλ໊͚ͩ map ͓ͯ͠खܰʹίϐʔ͍ͨ͠
    •࣮ࡍʹϑΟϧλ͕ద༻͞Ε͔ͨ΋஌Γ͍ͨ

    View Slide

  64. ͦ͜Ͱ TracePoint
    Α͔ͬͨ

    View Slide

  65. ActiveSupport::Callbacks
    DBMMCBDLTSCcSBJMTSBJMT
    [email protected]CBDLTSC
    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 ͕ݺ͹ΕΔͷ͸
    ࣮ࡍʹ࣮ߦ͞ΕΔίʔϧόοΫ͚ͩ

    View Slide

  66. 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
    [email protected]@USBDFS
    [email protected]@[email protected]

    View Slide


  67. ग़ྗͰ͖ͨ
    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]

    View Slide

  68. ;ΓฦΓ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  74. rspec-openapi
    PQFOBQJSCcLLVCVOSTQFDPQFOBQJ
    IUUQTHJUIVCDPNLLVCVOSTQFDPQFOBQJCMPCGEEMJCSTQFDPQFOBQJSC
    require 'rspec/openapi/version'
    require 'rspec/openapi/hooks' if ENV['OPENAPI']
    module RSpec::OpenAPI
    # ...
    end

    View Slide

  75. ActionTracer
    [email protected]@USBDFS
    [email protected]@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"

    ੈͷதʹ͸ແ਺ͷ͓खຊίʔυ͕͋Δ

    View Slide

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

    View Slide

  77. Πϯλʔωοτʹฉ͘
    •ʮgem ࡞Γํʯ
    ͸͡Ίͯͷࣗ࡞HFNʮ)FMMP 5BNBʯΛग़ྗͯ͠ΈΔ[email protected]
    [email protected]

    View Slide

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

    View Slide

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

    View Slide

  80. •ΞϓϦ͕஗͍
    •CI ͕஗͍
    •ϦϦʔε࡞ۀ͕஗͍
    •։ൃ଎౓͕஗͍
    •ίʔυ͕ಡΈʹ͍͘
    •ίʔυ͕Ԛ͍
    •etc etc…
    ʮݱ৔ͷࠓ͋Δ՝୊ʯ
    ෆద੾ͳΤϥʔϋϯυϦϯά
    ଐਓԽ͍ͯ͠ΔΤϥʔ؂ࢹ
    ແବͳSQLͷൃߦ
    ίʔσΟϯάϧʔϧ͕গͳ͍
    ແடংͳϝλϓϩ
    େྔͷσουίʔυ
    ࢖ΘΕ͍ͯͳ͍ΞΫγϣϯ
    େྔͷApplicationController
    ਖ਼نԽ͞Ε͍ͯͳ͍ςʔϒϧ
    ൿ఻ͷλϨscope
    ௒ઈFatModel
    γεςϜεϖοΫ͕গͳ͍
    ແବͳςετσʔλͷ࡞੒
    ΦϨΦϨϙϦϞʔϑΟοΫςʔϒϧ
    ߦ͖ա͗ͨڞ௨Խ

    View Slide

  81. •ͲΜͳݱ৔ʹ΋՝୊͸͋Δ
    •՝୊ͷΞϓϩʔνํ๏͸৭ʑ
    •ࠜຊղܾɾԠٸख౰
    •ͭΒΈΛݴޠԽ͢ΔͱରԠํ๏͕ੜ·ΕΔͷͰޱʹग़͢
    ʮݱ৔ͷࠓ͋Δ՝୊ʯ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide