ContextualMemery

ContextualMemery builds on the memery gem to let you turn memoization on and off at runtime, scoped to a block, for one or more methods, on either an instance or a class.

Where memery memoizes a method for the lifetime of the object, ContextualMemery memoizes a method only for the duration of a block:

instance.banana_time # not memoized
instance.banana_time # not memoized, recomputed

instance.memoized(:banana_time) do
  instance.banana_time # computed
  instance.banana_time # cached
end

instance.banana_time # not memoized again, cache has been cleared

This is handy when you want to memoize a call for the duration of a request, a job, a loop iteration, etc., without permanently memoizing it for the object's whole lifetime.

Installation

Install the gem and add to the application's Gemfile by executing:

bundle add contextual_memery

Usage

Include ContextualMemery in your class:

class Thing
  include ContextualMemery

  def banana_time(*)
    ...
  end

  def handyman
    ...
  end

  def self.take_back(*)
    ...
  end
end

This gives both instances and the class a memoized method.

On an instance

instance = Thing.new

instance.memoized(:banana_time) do
  instance.banana_time
  instance.banana_time # cached
  instance.handyman    # not memoized, still recomputed every call
end

Pass several method names to memoize more than one method at once:

instance.memoized(:banana_time, :douglas) do
  ...
end

Pass :all, or no arguments at all, to memoize every method defined directly on the object (public, protected and private) for the duration of the block:

instance.memoized(:all) do
  ...
end

instance.memoized do
  ...
end

On a class

The exact same API is available on the class itself, for memoizing class (singleton) methods:

Thing.memoized(:take_back) do
  Thing.take_back
  Thing.take_back # cached
end

Scoped to the block, including indirect calls

Memoization is active for any call made from within the block, even indirectly through other methods — it isn't limited to calls written literally inside the do...end:

def do_thing(onion)
  Thing.take_back(onion)
end

Thing.memoized(:take_back) do
  foods.each do |food|
    do_thing(food.skin) # each unique `food.skin` is only computed once
  end
end

Scoping and nesting

  • Memoization is scoped per object: memoizing instance_a never affects instance_b, and memoizing an instance never affects the class (or vice versa).
  • Arguments are still taken into account, exactly like plain memery: banana_time(1) and banana_time(2) are cached separately.
  • The cache is cleared as soon as the outermost memoized block for that object finishes, so nesting memoized blocks on the same object is safe and won't clear the cache out from under an enclosing block.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/braindeaf/contextual_memery.

License

The gem is available as open source under the terms of the MIT License.