Skip to content

Commit

Permalink
Merge pull request #5 from dribbble/patrick/update-api
Browse files Browse the repository at this point in the history
Update from #call to #fetch for memoization call
  • Loading branch information
pbyrne authored Feb 14, 2024
2 parents 9f2f43c + 8b7e9b2 commit 1454371
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 22 deletions.
9 changes: 6 additions & 3 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ AllCops:
TargetRubyVersion: 2.7
NewCops: enable

Layout/LineLength:
Max: 120

Metrics/BlockLength:
AllowedMethods: ["describe"]

Style/AccessModifierDeclarations:
Enabled: true
EnforcedStyle: inline
Expand All @@ -17,6 +23,3 @@ Style/StringLiterals:
Style/StringLiteralsInInterpolation:
Enabled: true
EnforcedStyle: double_quotes

Layout/LineLength:
Max: 120
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## [Unreleased]

## [0.2.0] - 2024-02-14

- **BREAKING**: Changes `MemoPad::Memo#call` to `MemoPad::Memo#fetch` to more closely match interfaces like `ActiveSupport::Cache`.
- Add `#read` and `#write` methods to allow for lower-level manipulation of the cached values, similarly inspired by the interface of `ActiveSupport::Cache`.

## [0.1.0] - 2024-01-22

- Initial release
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
memo_pad (0.1.0)
memo_pad (0.2.0)

GEM
remote: https://rubygems.org/
Expand Down
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,38 +27,49 @@ If bundler is not being used to manage dependencies, install the gem by executin
## Usage

1. First, `include MemoPad` in your class.
2. Then use `memo_pad.call(name, *args) do; end` to memoize the result of the block.
2. Then use `memo_pad.fetch(name, *args) do; end` to memoize the result of the block.
3. There is no step 3.

```ruby
class Foo
include MemoPad

def expensive_method
memo_pad.call(:expensive_method) do
memo_pad.fetch(:expensive_method) do
# Do some expensive work here
end
end

def expensive_method_with_arguments(foo, bar: nil)
memo_pad.call(:expensive_method, foo, bar) do
memo_pad.fetch(:expensive_method, foo, bar) do
# Do expensive work here, respecting the values of `foo` and `bar`
end
end

def complex_memoization
first_part = memo_pad.call(:complex_memoization_first) do
first_part = memo_pad.fetch(:complex_memoization_first) do
# Some independent expensive work
end

# Maybe some other unmemoized work

memo_pad.call(:complex_memoization_second, first_part) do
memo_pad.fetch(:complex_memoization_second, first_part) do
# Some other expensive work, respecting the value of `first_part`
end
end
end
```

You can directly write a memo, if needed, with `#write`. It has nearly the same signature as `#fetch`, the name and optional list of arguments, with the addition of the `value:` keyword argument to supply the value to write to the memo.

```ruby
def precache_things(things)
things.each do |thing|
memo_pad.write(:has_thing?, thing, value: true)
end
end
```

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
4 changes: 2 additions & 2 deletions lib/memo_pad.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
# Memoize results of complex executions on its memo_pad:
#
# def expensive_method
# memo_pad.call(:expensive_method) do
# memo_pad.fetch(:expensive_method) do
# # perform the expensive work
# end
# end
#
# Pass in any arguments that the memoized result would depend on, if any:
#
# def expensive_with_arguments(foo, bar: nil)
# memo_pad.call(:expensive_with_arguments, foo, bar) do
# memo_pad.fetch(:expensive_with_arguments, foo, bar) do
# # perform the expensive work
# end
# end
Expand Down
19 changes: 16 additions & 3 deletions lib/memo_pad/memo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,22 @@ def initialize
end
end

def call(method_name, *args, &block)
result = cache[method_name].fetch(args, &block)
cache[method_name][args] = result
def fetch(method_name, *args, &block)
read!(method_name, *args)
rescue KeyError
write(method_name, *args, value: block.call)
end

def read(method_name, *args)
cache[method_name].fetch(args, nil)
end

def read!(method_name, *args)
cache[method_name].fetch(args)
end

def write(method_name, *args, value:)
cache[method_name][args] = value
end
end
end
2 changes: 1 addition & 1 deletion lib/memo_pad/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module MemoPad
VERSION = "0.1.0"
VERSION = "0.2.0"
end
100 changes: 93 additions & 7 deletions test/test_memo_pad.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,27 @@ def initialize
@call_tracker = CallTracker.new
end

def no_arguments
memo_pad.fetch(:no_arguments) do
@call_tracker.track(:no_arguments, rand)
end
end

def no_arguments_truthy
memo_pad.call(:no_arguments) do
@call_tracker.track(:no_arguments, true)
memo_pad.fetch(:no_arguments_truthy) do
@call_tracker.track(:no_arguments_truthy, true)
end
end

def no_arguments_falsey
memo_pad.call(:no_arguments) do
@call_tracker.track(:no_arguments, nil)
memo_pad.fetch(:no_arguments_falsey) do
@call_tracker.track(:no_arguments_falsey, nil)
end
end

def with_arguments(foo)
memo_pad.fetch(:with_arguments, foo) do
@call_tracker.track(:with_arguments, foo)
end
end
end
Expand All @@ -48,21 +60,95 @@ def no_arguments_falsey
end
end

describe "#call" do
describe "#fetch" do
subject { ClassWithMemoPad.new }

it "calls the block once for methods with no arguments" do
assert subject.no_arguments_truthy
subject.no_arguments_truthy

assert_equal 1, subject.call_tracker.count(:no_arguments)
assert_equal 1, subject.call_tracker.count(:no_arguments_truthy)
end

it "caches falsey values also" do
refute subject.no_arguments_falsey
subject.no_arguments_falsey

assert_equal 1, subject.call_tracker.count(:no_arguments)
assert_equal 1, subject.call_tracker.count(:no_arguments_falsey)
end
end

describe "#write" do
subject { ClassWithMemoPad.new }
let(:result) { rand }

it "writes the given value to be read later" do
subject.memo_pad.write(:no_arguments, value: result)

assert_equal result, subject.memo_pad.read(:no_arguments)
assert_equal result, subject.no_arguments
end

it "writes the given value for methods with arguments" do
subject.memo_pad.write(:with_arguments, :foo, value: result)

assert_equal result, subject.memo_pad.read(:with_arguments, :foo)
assert_nil subject.memo_pad.read(:with_arguments, :bar)
assert_nil subject.memo_pad.read(:with_arguments)
end
end

describe "#read" do
subject { ClassWithMemoPad.new }
let(:arg) { rand }

it "returns nil if no cached value present" do
assert_nil subject.memo_pad.read(:no_arguments)
end

it "it reads a cached value if present" do
result = subject.no_arguments

assert_equal result, subject.memo_pad.read(:no_arguments)
end

it "reads a cached value for method with arguments" do
result = subject.with_arguments(arg)

assert_equal result, subject.memo_pad.read(:with_arguments, arg)
assert_nil subject.memo_pad.read(:with_arguments, :foo)
assert_nil subject.memo_pad.read(:with_arguments)
end
end

describe "#read!" do
subject { ClassWithMemoPad.new }
let(:arg) { rand }

it "raises KeyError if no cached value present" do
assert_raises(KeyError) do
subject.memo_pad.read!(:no_arguments)
end
end

it "it reads a cached value if present" do
result = subject.no_arguments

assert_equal result, subject.memo_pad.read!(:no_arguments)
end

it "reads a cached value for method with arguments" do
result = subject.with_arguments(arg)

assert_equal result, subject.memo_pad.read!(:with_arguments, arg)

assert_raises(KeyError) do
assert_nil subject.memo_pad.read!(:with_arguments, :foo)
end

assert_raises(KeyError) do
assert_nil subject.memo_pad.read!(:with_arguments)
end
end
end
end

0 comments on commit 1454371

Please sign in to comment.