Module: RedisMemo::MemoizeMethod

Defined in:
lib/redis_memo/memoize_method.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.exclude_anonymous_args(depends_on, ref, args) ⇒ Object

We only look at named method parameters in the dependency block in order to define its dependent memos and ignore anonymous parameters, following the convention that nil or :_ is an anonymous parameter. Example: “‘

def method(param1, param2)
end

memoize_method :method do |_, _, param2|`
  depends_on RedisMemo::Memoizable.new(param2: param2)
end

“‘

`exclude_anonymous_args(depends_on, ref, [1, 2])` returns [2]


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/redis_memo/memoize_method.rb', line 105

def self.exclude_anonymous_args(depends_on, ref, args)
  return [] if depends_on.parameters.empty? or args.empty?

  positional_args = []
  kwargs = {}
  depends_on_args = [ref] + args
  options = depends_on_args.extract_options!

  # Keep track of the splat start index, and the number of positional args before and after the splat,
  # so we can map which args belong to positional args and which args belong to the splat.
  named_splat = false
  splat_index = nil
  num_positional_args_after_splat = 0
  num_positional_args_before_splat = 0

  depends_on.parameters.each_with_index do |param, i|
    # Defined by https://github.com/ruby/ruby/blob/22b8ddfd1049c3fd1e368684c4fd03bceb041b3a/proc.c#L3048-L3059
    case param.first
    when :opt, :req
      if splat_index
        num_positional_args_after_splat += 1
      else
        num_positional_args_before_splat += 1
      end
    when :rest
      named_splat = is_named?(param)
      splat_index = i
    when :key, :keyreq
      kwargs[param.last] = options[param.last] if is_named?(param)
    when :keyrest
      kwargs.merge!(options) if is_named?(param)
    else
      raise(RedisMemo::ArgumentError, "#{param.first} argument isn't supported in the dependency block")
    end
  end

  # Determine the named positional and splat arguments after we know the # of pos. arguments before and after splat
  after_splat_index = depends_on_args.size - num_positional_args_after_splat
  depends_on_args.each_with_index do |arg, i|
    # if the index is within the splat
    if i >= num_positional_args_before_splat && i < after_splat_index
      positional_args << arg if named_splat
    else
      j = i < num_positional_args_before_splat ? i : i - (after_splat_index - splat_index) - 1
      positional_args << arg if is_named?(depends_on.parameters[j])
    end
  end

  if !kwargs.empty?
    positional_args + [kwargs]
  elsif named_splat && !options.empty?
    positional_args + [options]
  else
    positional_args
  end
end

.extract_dependencies(ref, *method_args, &depends_on) ⇒ Object



74
75
76
77
78
79
80
# File 'lib/redis_memo/memoize_method.rb', line 74

def self.extract_dependencies(ref, *method_args, &depends_on)
  dependency = RedisMemo::Memoizable::Dependency.new

  # Resolve the dependency recursively
  dependency.instance_exec(ref, *method_args, &depends_on)
  dependency
end

.get_or_extract_dependencies(ref, *method_args, &depends_on) ⇒ Object



82
83
84
85
86
87
88
89
90
91
# File 'lib/redis_memo/memoize_method.rb', line 82

def self.get_or_extract_dependencies(ref, *method_args, &depends_on)
  if RedisMemo::Cache.local_dependency_cache
    RedisMemo::Cache.local_dependency_cache[ref.class] ||= {}
    RedisMemo::Cache.local_dependency_cache[ref.class][depends_on] ||= {}
    named_args = exclude_anonymous_args(depends_on, ref, method_args)
    RedisMemo::Cache.local_dependency_cache[ref.class][depends_on][named_args] ||= extract_dependencies(ref, *method_args, &depends_on)
  else
    extract_dependencies(ref, *method_args, &depends_on)
  end
end

.method_id(ref, method_name) ⇒ Object



67
68
69
70
71
72
# File 'lib/redis_memo/memoize_method.rb', line 67

def self.method_id(ref, method_name)
  is_class_method = ref.class == Class
  class_name = is_class_method ? ref.name : ref.class.name

  "#{class_name}#{is_class_method ? '::' : '#'}#{method_name}"
end

Instance Method Details

#memoize_method(method_name, method_id: nil, **options, &depends_on) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/redis_memo/memoize_method.rb', line 9

def memoize_method(method_name, method_id: nil, **options, &depends_on)
  method_name_without_memo = :"_redis_memo_#{method_name}_without_memo"
  method_name_with_memo = :"_redis_memo_#{method_name}_with_memo"

  alias_method method_name_without_memo, method_name

  define_method method_name_with_memo do |*args|
    return send(method_name_without_memo, *args) if RedisMemo.without_memo?

    dependent_memos = nil
    if depends_on
      dependency = RedisMemo::MemoizeMethod.get_or_extract_dependencies(self, *args, &depends_on)
      dependent_memos = dependency.memos
    end

    future = RedisMemo::Future.new(
      self,
      case method_id
      when NilClass
        RedisMemo::MemoizeMethod.method_id(self, method_name)
      when String, Symbol
        method_id
      else
        method_id.call(self, *args)
      end,
      args,
      dependent_memos,
      options,
      method_name_without_memo,
    )

    if RedisMemo::Batch.current
      RedisMemo::Batch.current << future
      return future
    end

    future.execute
  rescue RedisMemo::WithoutMemoization
    send(method_name_without_memo, *args)
  end

  alias_method method_name, method_name_with_memo

  @__redis_memo_method_dependencies ||= Hash.new
  @__redis_memo_method_dependencies[method_name] = depends_on

  define_method :dependency_of do |method_name, *method_args|
    method_depends_on = self.class.instance_variable_get(:@__redis_memo_method_dependencies)[method_name]
    unless method_depends_on
      raise(
        RedisMemo::ArgumentError,
        "#{method_name} is not a memoized method"
      )
    end
    RedisMemo::MemoizeMethod.get_or_extract_dependencies(self, *method_args, &method_depends_on)
  end
end