# `MemoWise` [![Tests](https://github.com/panorama-ed/memo_wise/workflows/Main/badge.svg)](https://github.com/panorama-ed/memo_wise/actions?query=workflow%3AMain) [![Code Coverage](https://codecov.io/gh/panorama-ed/memo_wise/branch/main/graph/badge.svg)](https://codecov.io/gh/panorama-ed/memo_wise/branches/main) [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/panorama-ed/memo_wise) [![Inline docs](http://inch-ci.org/github/panorama-ed/memo_wise.svg?branch=main)](http://inch-ci.org/github/panorama-ed/memo_wise) [![Gem Version](https://img.shields.io/gem/v/memo_wise.svg)](https://rubygems.org/gems/memo_wise) [![Gem Downloads](https://img.shields.io/gem/dt/memo_wise.svg)](https://rubygems.org/gems/memo_wise) ## Why `MemoWise`? `MemoWise` is **the wise choice for Ruby memoization**, featuring: * Fast performance of memoized reads (with [benchmarks](#benchmarks)) * Support for [resetting](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise#reset_memo_wise-instance_method) and [presetting](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise#preset_memo_wise-instance_method) memoized values * Support for memoization on frozen objects * Support for memoization of class and module methods * Full [documentation](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise) and [test coverage](https://codecov.io/gh/panorama-ed/memo_wise)! ## Installation Add this line to your application's Gemfile: ```ruby gem 'memo_wise' ``` And then execute: $ bundle install Or install it yourself as: $ gem install memo_wise ## Usage When you `prepend MemoWise` within a class or module, `MemoWise` exposes three methods: - [`memo_wise`](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise#memo_wise-class_method) - [`preset_memo_wise`](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise#preset_memo_wise-instance_method) - [`reset_memo_wise`](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise#reset_memo_wise-instance_method) ```ruby class Example prepend MemoWise def slow_value(x) sleep x x end memo_wise :slow_value end ex = Example.new ex.slow_value(2) # => 2 # Sleeps for 2 seconds before returning ex.slow_value(2) # => 2 # Returns immediately because the result is memoized ex.reset_memo_wise(:slow_value) # Resets all memoized results for slow_value ex.slow_value(2) # => 2 # Sleeps for 2 seconds before returning ex.slow_value(2) # => 2 # Returns immediately because the result is memoized # NOTE: Memoization can also be reset for all methods, or for just one argument. ex.preset_memo_wise(:slow_value, 3) { 4 } # Store 4 as the result for slow_value(3) ex.slow_value(3) # => 4 # Returns immediately because the result is memoized ex.reset_memo_wise # Resets all memoized results for all methods on ex ``` Methods which take implicit or explicit block arguments cannot be memoized. For more usage details, see our detailed [documentation](#documentation). ## Benchmarks Benchmarks measure memoized value retrieval time using [`benchmark-ips`](https://github.com/evanphx/benchmark-ips). All benchmarks are run on Ruby 3.0.0, except as indicated below for specific gems. Benchmarks are run in GitHub Actions and updated in every PR that changes code. |Method arguments|**`memo_wise` (0.1.0)**|`memery` (1.3.0)|`memoist`\* (0.16.2)|`memoized`\* (1.0.2)|`memoizer`\* (1.0.3)| |--|--|--|--|--|--| |`()` (none)|**baseline**|13.17x slower|2.85x slower|1.30x slower|3.05x slower| |`(a, b)`|**baseline**|1.93x slower|2.20x slower|1.97x slower|1.86x slower| |`(a:, b:)`|**baseline**|3.05x slower|2.34x slower|2.27x slower|2.14x slower| |`(a, b:)`|**baseline**|1.50x slower|1.63x slower|1.56x slower|1.48x slower| |`(a, *args)`|**baseline**|1.91x slower|2.13x slower|1.90x slower|1.88x slower| |`(a:, **kwargs)`|**baseline**|3.08x slower|2.37x slower|2.24x slower|2.09x slower| |`(a, *args, b:, **kwargs)`|**baseline**|1.72x slower|1.61x slower|1.56x slower|1.67x slower| _\*Indicates a benchmark run on Ruby 2.7.2 because the gem raises errors in Ruby 3.0.0 due to its incorrect handling of keyword arguments._ You can run benchmarks yourself with: ```bash $ cd benchmarks $ bundle install $ bundle exec ruby benchmarks.rb ``` If your results differ from what's posted here, [let us know](https://github.com/panorama-ed/memo_wise/issues/new)! ## 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Documentation ### Documentation is Automatically Generated We maintain API documentation using [YARD](https://yardoc.org/), which is published automatically at [RubyDoc.info](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise). To edit documentation locally and see it rendered in your browser, run: ```bash bundle exec yard server ``` ### Documentation Examples are Automatically Tested We use [yard-doctest](https://github.com/p0deje/yard-doctest) to test all code examples in our YARD documentation. To run `doctest` locally: ```bash bundle exec yard doctest ``` ### A Note on Testing When testing memoized *module* methods, note that some testing setups will reuse the same instance (which `include`s/`extend`s/`prepend`s the module) across tests, which can result in confusing test failures when this differs from how you use the code in production. For example, Rails view helpers are modules that are commonly tested with a [shared `view` instance](https://github.com/rails/rails/blob/291a3d2ef29a3842d1156ada7526f4ee60dd2b59/actionview/lib/action_view/test_case.rb#L203-L214). Rails initializes a new view instance for each web request so any view helper methods would only be memoized for the duration of that web request, but in tests (such as when using [`rspec-rails`'s `helper`](https://github.com/rspec/rspec-rails/blob/main/lib/rspec/rails/example/helper_example_group.rb#L22-L27)), the memoization may persist across tests. In this case, simply reset the memoization between your tests with something like: ```ruby after(:each) { helper.reset_memo_wise } ``` ## Logo `MemoWise`'s logo was created by [Luci Cooke](https://www.lucicooke.com/). The logo is licensed under a [Creative Commons Attribution-NonCommercial 4.0 International License](https://creativecommons.org/licenses/by-nc/4.0/deed.en). ## Contributing [Bug reports](https://github.com/panorama-ed/memo_wise/issues) and [pull requests](https://github.com/panorama-ed/memo_wise/pulls) are welcome on GitHub at https://github.com/panorama-ed/memo_wise. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/panorama-ed/memo_wise/blob/main/CODE_OF_CONDUCT.md). ## Releasing To make a new release of `MemoWise` to [RubyGems](https://rubygems.org/gems/memo_wise), first install the release dependencies (e.g. `rake`) as follows: ```shell bundle config set --local with 'release' bundle install ``` Then carry out these steps: 1. Update `CHANGELOG.md`: - Add an entry for the upcoming version _x.y.z_ - Move content from _Unreleased_ to the upcoming version _x.y.z_ - Commit with title `Update CHANGELOG.md for x.y.z` 2. Update `lib/memo_wise/version.rb` - Replace with upcoming version _x.y.z_ - Commit with title `Bump version to x.y.z` 3. `bundle exec rake release` ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). ## Code of Conduct Everyone interacting in the `MemoWise` project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/panorama-ed/memo_wise/blob/main/CODE_OF_CONDUCT.md).