Subclassing modules

Render problem

Trying to determine all dependencies for #render

module ActionView
  module Helpers
    module RenderingHelper
      def render(options = {}, locals = {}, &block)
        # ...
        view_renderer.render_partial
      end
    end
  end
end
module ActionView
  module Rendering
    # ...
    def view_renderer
      ActionView::Renderer.new(lookup_context)
    end
  end
end

ActionController::Base#render

ActionController::Base
  includes AbstractController::Rendering
    implements #render
      calls #render_to_body
  includes ActionView::Layouts
    includes ActionView::Rendering
      implements #render_to_body
        calls #view_renderer
      implements #view_renderer
        calls #lookup_context
      includes ActionView::ViewPaths
        implements #lookup_context
ResultsController.ancestors
  => [ApplicationController,
      # ... ~ 60 ancestors
      ActionController::Metal,
      AbstractController::Base,
      ActiveSupport::Configurable,
      Object,
      PP::ObjectMixin,
      FriendlyId::ObjectUtils,
      ActiveSupport::Dependencies::Loadable,
      JSON::Ext::Generator::GeneratorMethods::Object,
      Kernel,
      BasicObject]

What's the problem?

  • modules are designed to dry-up closely related group of method used in multiple  places
  • they are usable beyond that, but as complexity grows call stack jumps all around source code
  • module can't specify it's dependencies, so it calls ghost methods
  • ghost implementations can be in host(simple case), inherited(reasonably simple) or can be included from another modules
module RenderingHelper
  # Implements methods that allow rendering from a view context.
  # In order to use this module, all you need is to implement
  # view_renderer that returns an ActionView::Renderer object.
  def render(options = {}, locals = {}, &block)
    view_renderer.render_parital
  end
end

Includable?

class Hero
end

module Wingman
end

class Guest < Module
end

class Host
  include Hero         # ?
  include Hero.new     # ?
  include Wingman      # ?
  include Wingman.new  # ?
  include Guest        # ?
  include Guest.new    # ?
end

Host#include(guest)

  • guest can't be a class
  • guest must be kind of a module
  • checks are performed in short-circuit mode 

Quirks mode: ON

  • include is a three step process
    1. prepend guest to ancestor chain
    2. evaluate guest
    3. run hook on guest
  • evaluation doesn't change context(quirk)
    • Host is active class in guest constructor
class Drivable < Module
  def initialize(vehicle)
    define_method(:drive) { vehicle }
  end

  def transmission
    :manual
  end
end

class Hero
  include Drivable.new(:batmobil)
end

Hero.new.public_methods.grep(/transmission|drive/) # => ???

Technique skeleton

class Guest < Module
  attr_reader :state

  # 2. store private state
  def initialize(state)
    @state = state

    create_methods
  end

  # 4. hook behavior into host class
  def included(host)
    # host.class_eval { }
    # host.send(:include)
  end

  def create_methods
    ctx = self

    # 3. evaluate code in context of host class
    define_method :to_s { ctx.state[:name] }
  end
end

class Host
  # 1. configure Guest dependencies/state
  include Guest.new(name: 'me',
                    renderer: Renderer.new)
end
  1. Host can provide dependencies through constructor
  2. Guest instance has a private state completely inaccessible to host class/instance(due to method lookup)
  3. utilize quirks mode, initiate method definitions from constructor
  4. extend host class from hook method(quirk does not work here anymore)

Under the hood

  • each inheritance prepends parent class to ancestor chain
  • each include prepends guest to ancestor chain
  • each extend prepends guest to singletons class ancestor chain
  • when resolving methods ruby interpreter consults ancestors chain
    • classes and modules are searched in order of appearance
    • objects are skipped

Why are guest instance methods invisible?

I'm the last slide

Questions?

Subclassing modules

By vrabac

Subclassing modules

  • 578