class Dry::Core::Memoizable::Memoizer

@api private

Public Class Methods

new(klass, names) click to toggle source

@api private

Calls superclass method
# File lib/dry/core/memoizable.rb, line 56
def initialize(klass, names)
  super()
  names.each do |name|
    define_memoizable(
      method: klass.instance_method(name)
    )
  end
end

Private Instance Methods

declaration(definition, lookup) click to toggle source

@api private

# File lib/dry/core/memoizable.rb, line 117
def declaration(definition, lookup)
  params = []
  binds = []
  defined = {}

  definition.each do |type, name|
    mapped_type = map_bind_type(type, name, lookup, defined) do
      raise ::NotImplementedError, "type: #{type}, name: #{name}"
    end

    if mapped_type
      defined[mapped_type] = true
      bind = name_from_param(name) || make_bind_name(binds.size)

      binds << bind
      params << param(bind, mapped_type)
    end
  end

  [params, binds]
end
define_memoizable(method:) click to toggle source

@api private

# File lib/dry/core/memoizable.rb, line 68
        def define_memoizable(method:) # rubocop:disable Metrics/AbcSize
          parameters = method.parameters

          if parameters.empty?
            key = method.name.hash
            module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
              def #{method.name}                    # def slow_fetch
                if @__memoized__.key?(#{key})       #   if @__memoized__.key?(12345678)
                  @__memoized__[#{key}]             #     @__memoized__[12345678]
                else                                #   else
                  @__memoized__[#{key}] = super     #     @__memoized__[12345678] = super
                end                                 #   end
              end                                   # end
            RUBY
          else
            mapping = parameters.to_h { |k, v = nil| [k, v] }
            params, binds = declaration(parameters, mapping)
            last_param = parameters.last

            if last_param[0].eql?(:block) && !last_param[1].eql?(:&)
              Deprecations.warn(<<~WARN)
                Memoization for block-accepting methods isn't safe.
                Every call creates a new block instance bloating cached results.
                In the future, blocks will still be allowed but won't participate in
                cache key calculation.
              WARN
            end

            m = module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
              def #{method.name}(#{params.join(", ")})                 # def slow_calc(arg1, arg2, arg3)
                key = [:"#{method.name}", #{binds.join(", ")}].hash    #   [:slow_calc, arg1, arg2, arg3].hash
                                                                       #
                if @__memoized__.key?(key)                             #   if @__memoized__.key?(key)
                  @__memoized__[key]                                   #     @__memoized__[key]
                else                                                   #   else
                  @__memoized__[key] = super                           #     @__memoized__[key] = super
                end                                                    #   end
              end                                                      # end
            RUBY

            if respond_to?(:ruby2_keywords, true) && mapping.key?(:reyrest)
              ruby2_keywords(method.name)
            end

            m
          end
        end
make_bind_name(idx) click to toggle source

@api private

# File lib/dry/core/memoizable.rb, line 149
def make_bind_name(idx)
  :"__lv_#{idx}__"
end
map_bind_type(type, name, original_params, defined_types) { || ... } click to toggle source

@api private

# File lib/dry/core/memoizable.rb, line 154
def map_bind_type(type, name, original_params, defined_types) # rubocop:disable Metrics/PerceivedComplexity
  case type
  when :req
    :reqular
  when :rest, :keyreq, :keyrest
    type
  when :block
    if name.eql?(:&)
      # most likely this is a case of delegation
      # rather than actual block
      nil
    else
      type
    end
  when :opt
    if original_params.key?(:rest) || defined_types[:rest]
      nil
    else
      :rest
    end
  when :key
    if original_params.key?(:keyrest) || defined_types[:keyrest]
      nil
    else
      :keyrest
    end
  else
    yield
  end
end
name_from_param(name) click to toggle source

@api private

# File lib/dry/core/memoizable.rb, line 140
def name_from_param(name)
  if PARAM_PLACEHOLDERS.include?(name)
    nil
  else
    name
  end
end
param(name, type) click to toggle source

@api private

# File lib/dry/core/memoizable.rb, line 186
def param(name, type)
  case type
  when :reqular
    name
  when :rest
    "*#{name}"
  when :keyreq
    "#{name}:"
  when :keyrest
    "**#{name}"
  when :block
    "&#{name}"
  end
end