Alt Memery allows to memoize methods return values.
The native simplest memoization in Ruby looks like this:
def user
@user ||= User.find(some_id)
endBut if you want to memoize falsy values — you have to use defined? instead of ||=:
def user
return @user if defined?(@user)
@user = User.find(some_id)
endEven worse: if your calculations require multiple lines, you have to use begin/end block:
def user
@user ||= begin
some_id = calculate_id
klass = calculate_klass
klass.find(some_id)
end
endAnd there is no elegant escape if your methods accept arguments.
But with memoization gems, like this one, you can simplify your code:
memoize def user
User.find(some_id)
endAlso, you're getting additional features, like conditions of memoization, time-to-live, handy memoized values flushing, etc.
It's a fork of Memery gem.
Original Memery uses prepend Module.new with memoized methods, not touching original ones.
This approach has advantages, but also has problems, see discussion here:
tycooon#1
So, this fork uses UnboundMethod as I've suggested in the PR above.
Such gems like Memoist override methods.
So, if you want to memoize a method in a child class with the same named memoized method
in a parent class — you have to use something like awkward identifier: argument.
This gem allows you to just memoize methods when you want to.
Note how both method's return values are cached separately and don't interfere with each other.
This gem doesn't change method's arguments (no extra param like reload).
If you need to get unmemoize result of method — just call the #clear_memery_cache! method
with needed memoized method names:
a.clear_memery_cache! :foo, :barWithout arguments, #clear_memery_cache! will clear the whole instance's cache.
This gem doesn't require any global options, it works well with marshalized objects in different proccesses with hashed arguments without performance impact.
Add gem 'alt_memery' to your Gemfile.
Since it's a fork of memery gem, require with:
require 'memery'Or from the Gemfile:
gem 'alt_memery', require: 'memery'class A
include Memery
memoize def call
puts "calculating"
42
end
end
a = A.new
a.call # => 42
a.call # => 42
a.call # => 42
# "calculating" will only be printed once.
a.call { 1 } # => 42
# "calculating" will be printed again because passing a block disables memoization.Alternatively:
class A
include Memery
def call
puts "calculating"
42
end
memoize :call
endMethods with arguments are supported and the memoization will be done based on arguments using an internal hash. So this will work as expected:
class A
include Memery
memoize def call(arg1, arg2)
puts "calculating"
arg1 + arg2
end
end
a = A.new
a.call(1, 5) # => 6
a.call(2, 15) # => 17
a.call(1, 5) # => 6
# "calculating" will be printed twice, once for each unique argument list.For class methods:
class B
class << self
include Memery
memoize def call
puts "calculating"
42
end
end
end
B.call # => 42
B.call # => 42
B.call # => 42
# "calculating" will only be printed once.class A
include Memery
attr_accessor :environment
def call
puts "calculating"
42
end
memoize :call, condition: -> { environment == 'production' }
end
a = A.new
a.environment = 'development'
a.call # => 42
# calculating
a.call # => 42
# calculating
a.call # => 42
# calculating
# Text will be printed every time because result of condition block is `false`.
a.environment = 'production'
a.call # => 42
# calculating
a.call # => 42
a.call # => 42
# Text will be printed only once because there is memoization
# with `true` result of condition block.class A
include Memery
def call
puts "calculating"
42
end
memoize :call, ttl: 3 # seconds
end
a = A.new
a.call # => 42
# calculating
a.call # => 42
a.call # => 42
# Text will be printed again only after 3 seconds of time-to-live.
# 3 seconds later...
a.call # => 42
# calculating
a.call # => 42
a.call # => 42
# another 3 seconds later...
a.call # => 42
# calculating
a.call # => 42
a.call # => 42class A
include Memery
memoize def call
puts "calculating"
42
end
def execute
puts "non-memoized"
end
end
a = A.new
a.memoized?(:call) # => true
a.memoized?(:execute) # => falseIf you want to see memoized method source:
class A
include Memery
memoize def call
puts "calculating"
42
end
end
# This will print memoization logic, don't use it.
# The same for `show-source A#call` in `pry`.
puts A.instance_method(:call).source
# And this will work correctly.
puts A.memoized_methods[:call].sourceBut if a memoized method has been defined in an included module — it'd be a bit harder:
module A
include Memery
memoize def foo
'source'
end
end
module B
include Memery
include A
memoize def foo
"Get this #{super}!"
end
end
class C
include B
end
puts C.instance_method(:foo).owner.memoized_methods[:foo].source
# memoize def foo
# "Get this #{super}!"
# end
puts C.instance_method(:foo).super_method.owner.memoized_methods[:foo].source
# memoize def foo
# 'source'
# endIn Ruby 3.2, a new optimization called "object shape" was introduced,
which can have negative interactions with dynamically added instance variables.
Alt Memery minimizes this impact by introducing only one new instance variable
after initialization (@_memery_memoized_values). If you need to ensure a specific object shape,
you can call clear_memery_cache! in your initializer to set the instance variable ahead of time.
After checking out the repo, run bundle install to install dependencies.
Then, run toys rspec to run the tests.
To install this gem onto your local machine, run toys gem install.
To release a new version, run toys gem release %version%.
See how it works here.
Bug reports and pull requests are welcome on GitHub at https://github.com/AlexWayfer/alt_memery.
The gem is available as open source under the terms of the MIT License.