Class: MemoWise::InternalAPI

Inherits:
Object
  • Object
show all
Defined in:
lib/memo_wise/internal_api.rb

Constant Summary collapse

NONE =
:none
ONE_REQUIRED_POSITIONAL =
:one_required_positional
ONE_REQUIRED_KEYWORD =
:one_required_keyword
MULTIPLE_REQUIRED =
:multiple_required
SPLAT =
:splat
DOUBLE_SPLAT =
:double_splat
SPLAT_AND_DOUBLE_SPLAT =
:splat_and_double_splat

Class Method Summary collapse

Class Method Details

.args_str(method) ⇒ String

Returns the arguments string to use when defining our new memoized version of the method.

Parameters:

  • method (UnboundMethod)

    a method being memoized

Returns:

  • (String)

    the arguments string to use when defining our new memoized version of the method



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/memo_wise/internal_api.rb', line 69

def self.args_str(method)
  case method_arguments(method)
  when SPLAT then "*args"
  when DOUBLE_SPLAT then "**kwargs"
  when ONE_REQUIRED_POSITIONAL, ONE_REQUIRED_KEYWORD, MULTIPLE_REQUIRED
    method.parameters.map do |type, name|
      "#{name}#{':' if type == :keyreq}"
    end.join(", ")
  else
    raise ArgumentError, "Unexpected arguments for #{method.name}"
  end
end

.call_str(method) ⇒ String

Returns the arguments string to use when calling the original method in our new memoized version of the method, i.e. when setting a memoized value.

Parameters:

  • method (UnboundMethod)

    a method being memoized

Returns:

  • (String)

    the arguments string to use when calling the original method in our new memoized version of the method, i.e. when setting a memoized value



86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/memo_wise/internal_api.rb', line 86

def self.call_str(method)
  case method_arguments(method)
  when SPLAT then "*args"
  when DOUBLE_SPLAT then "**kwargs"
  when SPLAT_AND_DOUBLE_SPLAT then "*args, **kwargs"
  when ONE_REQUIRED_POSITIONAL, ONE_REQUIRED_KEYWORD, MULTIPLE_REQUIRED
    method.parameters.map do |type, name|
      type == :req ? name : "#{name}: #{name}"
    end.join(", ")
  else
    raise ArgumentError, "Unexpected arguments for #{method.name}"
  end
end

.create_memo_wise_state!(obj) ⇒ Object

Create initial mutable state to store memoized values if it doesn’t already exist

Parameters:

  • obj (Object)

    Object in which to create mutable state to store future memoized values

Returns:

  • (Object)

    the passed-in obj



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/memo_wise/internal_api.rb', line 12

def self.create_memo_wise_state!(obj)
  # `@_memo_wise` stores memoized results of method calls in a hash keyed on
  # method name. The structure is slightly different for different types of
  # methods. It looks like:
  #   {
  #     zero_arg_method_name: :memoized_result,
  #     single_arg_method_name: { arg1 => :memoized_result, ... },
  #
  #     # This is faster than a single top-level hash key of: [:multi_arg_method_name, arg1, arg2]
  #     multi_arg_method_name: { arg1 => { arg2 => :memoized_result, ... }, ... }
  #   }
  obj.instance_variable_set(:@_memo_wise, {}) unless obj.instance_variable_defined?(:@_memo_wise)

  obj
end

.key_str(method) ⇒ String

Returns the string to use as a hash key when looking up a memoized value, based on the method’s arguments.

Parameters:

  • method (UnboundMethod)

    a method being memoized

Returns:

  • (String)

    the string to use as a hash key when looking up a memoized value, based on the method’s arguments



103
104
105
106
107
108
109
110
# File 'lib/memo_wise/internal_api.rb', line 103

def self.key_str(method)
  case method_arguments(method)
  when SPLAT then "args"
  when DOUBLE_SPLAT then "kwargs"
  else
    raise ArgumentError, "Unexpected arguments for #{method.name}"
  end
end

.method_arguments(method) ⇒ Symbol

Returns one of:

  • :none (example: ‘def foo`)

  • :one_required_positional (example: ‘def foo(a)`)

  • :one_required_keyword (example: ‘def foo(a:)`)

  • :multiple_required (examples: ‘def foo(a, b)`, `def foo(a:, b:)`, `def foo(a, b:)`)

  • :splat (examples: ‘def foo(a=1)`, `def foo(a, *b)`)

  • :double_splat (examples: ‘def foo(a: 1)`, `def foo(a:, **b)`)

  • :splat_and_double_splat (examples: ‘def foo(a=1, b: 2)`, `def foo(a=1, **b)`, `def foo(*a, **b)`).

Parameters:

  • method (UnboundMethod)

    a method to categorize based on the types of arguments it has

Returns:

  • (Symbol)

    one of:

    • :none (example: ‘def foo`)

    • :one_required_positional (example: ‘def foo(a)`)

    • :one_required_keyword (example: ‘def foo(a:)`)

    • :multiple_required (examples: ‘def foo(a, b)`, `def foo(a:, b:)`, `def foo(a, b:)`)

    • :splat (examples: ‘def foo(a=1)`, `def foo(a, *b)`)

    • :double_splat (examples: ‘def foo(a: 1)`, `def foo(a:, **b)`)

    • :splat_and_double_splat (examples: ‘def foo(a=1, b: 2)`, `def foo(a=1, **b)`, `def foo(*a, **b)`)



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/memo_wise/internal_api.rb', line 46

def self.method_arguments(method)
  return NONE if method.arity.zero?

  parameters = method.parameters.map(&:first)

  if parameters == [:req]
    ONE_REQUIRED_POSITIONAL
  elsif parameters == [:keyreq]
    ONE_REQUIRED_KEYWORD
  elsif parameters.all? { |type| type == :req || type == :keyreq }
    MULTIPLE_REQUIRED
  elsif parameters & %i[req opt rest] == parameters.uniq
    SPLAT
  elsif parameters & %i[keyreq key keyrest] == parameters.uniq
    DOUBLE_SPLAT
  else
    SPLAT_AND_DOUBLE_SPLAT
  end
end

.method_visibility(target, method_name) ⇒ :private, ...

Returns visibility of an instance method defined on class ‘target`.

Parameters:

  • target (Class, Module)

    The class to which we are prepending MemoWise to provide memoization.

  • method_name (Symbol)

    Name of existing instance method find the visibility of.

Returns:

  • (:private, :protected, :public)

    Visibility of existing instance method of the class.

Raises:

  • ArgumentError Raises ‘ArgumentError` unless `method_name` is a `Symbol` corresponding to an existing instance method defined on `klass`.



160
161
162
163
164
165
166
167
168
169
170
# File 'lib/memo_wise/internal_api.rb', line 160

def self.method_visibility(target, method_name)
  if target.private_method_defined?(method_name)
    :private
  elsif target.protected_method_defined?(method_name)
    :protected
  elsif target.public_method_defined?(method_name)
    :public
  else
    raise ArgumentError, "#{method_name.inspect} must be a method on #{target}"
  end
end

.original_class_from_singleton(klass) ⇒ Object

Find the original class for which the given class is the corresponding “singleton class”.

See stackoverflow.com/questions/54531270/retrieve-a-ruby-object-from-its-singleton-class

Parameters:

  • klass (Class)

    Singleton class to find the original class of

Returns:

  • Class Original class for which ‘klass` is the singleton class.

Raises:

  • ArgumentError Raises if ‘klass` is not a singleton class.



126
127
128
129
130
# File 'lib/memo_wise/internal_api.rb', line 126

def self.original_class_from_singleton(klass)
  raise ArgumentError, "Must be a singleton class: #{klass.inspect}" unless klass.singleton_class?

  find_attached_object(klass)
end

.original_memo_wised_name(method_name) ⇒ Symbol

Convention we use for renaming the original method when we replace with the memoized version in MemoWise.memo_wise.

Parameters:

  • method_name (Symbol)

    Name for which to return the renaming for the original method

Returns:

  • (Symbol)

    Renamed method to use for the original method with name ‘method_name`



141
142
143
# File 'lib/memo_wise/internal_api.rb', line 141

def self.original_memo_wised_name(method_name)
  :"_memo_wise_original_#{method_name}"
end

.validate_memo_wised!(target, method_name) ⇒ Object

Validates that MemoWise.memo_wise has already been called on ‘method_name`.

Parameters:

  • target (Class, Module)

    The class to which we are prepending MemoWise to provide memoization.

  • method_name (Symbol)

    Name of method to validate has already been setup with MemoWise.memo_wise



179
180
181
182
183
184
185
# File 'lib/memo_wise/internal_api.rb', line 179

def self.validate_memo_wised!(target, method_name)
  original_name = original_memo_wised_name(method_name)

  unless target_class(target).private_method_defined?(original_name)
    raise ArgumentError, "#{method_name} is not a memo_wised method"
  end
end