Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
ruby: ['3.3', '3.4', '4.0']

name: Ruby ${{ matrix.ruby }}

steps:
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true

- run: bundle exec rake
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
# Used by dotenv library to load environment variables.
.env
.rvmrc
.ruby-version
.tool-versions
.mise.toml

# Because macs
.DS_Store
Expand Down
7 changes: 4 additions & 3 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
require: 'rubocop-rspec'
plugins:
- rubocop-rspec

AllCops:
NewCops: enable
TargetRubyVersion: 2.6
TargetRubyVersion: 3.3

Layout/LineLength:
Max: 120
IgnoredPatterns: ['#.*']
AllowedPatterns: ['#.*']
Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: no_space

Expand Down
6 changes: 0 additions & 6 deletions .travis.yml

This file was deleted.

1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ group :development, :test do
gem 'guard-rspec'
gem 'guard-rubocop'
gem 'pry-byebug'
gem 'rake'
gem 'rspec'
gem 'rubocop'
gem 'rubocop-rspec'
Expand Down
102 changes: 64 additions & 38 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@ PATH
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
byebug (11.1.3)
addressable (2.8.8)
public_suffix (>= 2.0.2, < 8.0)
ast (2.4.3)
bigdecimal (4.0.1)
byebug (13.0.0)
reline (>= 0.6.0)
coderay (1.1.3)
diff-lcs (1.5.1)
ffi (1.17.0-arm64-darwin)
ffi (1.17.0-x86_64-darwin)
ffi (1.17.0-x86_64-linux-gnu)
formatador (1.1.0)
guard (2.18.1)
diff-lcs (1.6.2)
ffi (1.17.3-arm64-darwin)
ffi (1.17.3-x86_64-darwin)
ffi (1.17.3-x86_64-linux-gnu)
formatador (1.2.3)
reline
guard (2.20.1)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
logger (~> 1.6)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
Expand All @@ -31,66 +37,85 @@ GEM
guard-rubocop (1.5.0)
guard (~> 2.0)
rubocop (< 2.0)
json (2.7.2)
language_server-protocol (3.17.0.3)
listen (3.9.0)
io-console (0.8.2)
json (2.18.1)
json-schema (6.1.0)
addressable (~> 2.8)
bigdecimal (>= 3.1, < 5)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
listen (3.10.0)
logger
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
lumberjack (1.2.10)
logger (1.7.0)
lumberjack (1.4.2)
mcp (0.7.1)
json-schema (>= 4.1)
method_source (1.1.0)
nenv (0.3.0)
notiffany (0.1.3)
nenv (~> 0.1)
shellany (~> 0.0)
parallel (1.25.1)
parser (3.3.4.0)
parallel (1.27.0)
parser (3.3.10.2)
ast (~> 2.4.1)
racc
pry (0.14.2)
prism (1.9.0)
pry (0.16.0)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (>= 0.13, < 0.15)
reline (>= 0.6.0)
pry-byebug (3.12.0)
byebug (~> 13.0)
pry (>= 0.13, < 0.17)
public_suffix (7.0.2)
racc (1.8.1)
rainbow (3.1.1)
rake (13.3.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
regexp_parser (2.9.2)
rexml (3.4.4)
rspec (3.13.0)
regexp_parser (2.11.3)
reline (0.6.3)
io-console (~> 0.5)
rspec (3.13.2)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.0)
rspec-core (3.13.6)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.1)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.1)
rspec-mocks (3.13.7)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.1)
rubocop (1.65.1)
rspec-support (3.13.7)
rubocop (1.85.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
mcp (~> 0.6)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.4, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.49.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3)
parser (>= 3.3.1.0)
rubocop-rspec (3.0.3)
rubocop (~> 1.61)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.49.0)
parser (>= 3.3.7.2)
prism (~> 1.7)
rubocop-rspec (3.9.0)
lint_roller (~> 1.1)
rubocop (~> 1.81)
ruby-progressbar (1.13.0)
shellany (0.0.1)
thor (1.3.1)
unicode-display_width (2.5.0)
thor (1.5.0)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)

PLATFORMS
arm64-darwin-23
Expand All @@ -104,9 +129,10 @@ DEPENDENCIES
guard-rspec
guard-rubocop
pry-byebug
rake
rspec
rubocop
rubocop-rspec

BUNDLED WITH
2.5.17
4.0.7
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Cacheable

[![CI](https://github.com/splitwise/cacheable/actions/workflows/ci.yml/badge.svg)](https://github.com/splitwise/cacheable/actions/workflows/ci.yml)

By [Splitwise](https://www.splitwise.com)

Requires Ruby >= 3.3

Cacheable is a gem which adds method caching in Ruby following an [aspect-oriented programming (AOP)](https://en.wikipedia.org/wiki/Aspect-oriented_programming) paradigm. Its core goals are:

* ease of use (method annotation)
Expand Down Expand Up @@ -131,7 +135,7 @@ Fetching data from GitHub

#### Default

By default, Cacheable will construct key a key in the format `[cache_key || class_name, method_name]` without using method arguments.
By default, Cacheable will construct a key in the format `[cache_key || class_name, method_name]` without using method arguments.

If the object responds to `cache_key` its return value will be the first element in the array. `ActiveRecord` provides [`cache_key`](https://api.rubyonrails.org/classes/ActiveRecord/Integration.html#method-i-cache_key) but it can be added to any Ruby object or overwritten. If the object does not respond to it, the name of the class will be used instead. The second element will be the name of the method as a symbol.

Expand Down
6 changes: 3 additions & 3 deletions cacheable.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ Gem::Specification.new do |s|
s.name = 'cacheable'
s.version = Cacheable::VERSION
s.summary = 'Add caching to any Ruby method in a aspect orientated programming approach.'
s.description = 'Add caching simply without modifying your existing code. '\
'Includes configurable options for simple cache invalidation. '\
s.description = 'Add caching simply without modifying your existing code. ' \
'Includes configurable options for simple cache invalidation. ' \
'See README on github for more information.'
s.authors = ['Jess Hottenstein', 'Ryan Laughlin', 'Aaron Rosenberg']
s.email = 'support@splitwise.com'
s.files = Dir['lib/**/*', 'README.md', 'cache-adapters.md']
s.homepage = 'https://github.com/splitwise/cacheable'
s.licenses = 'MIT'
s.required_ruby_version = '>= 3.1.6'
s.required_ruby_version = '>= 3.3'
s.metadata = {'rubygems_mfa_required' => 'true'}
end
2 changes: 1 addition & 1 deletion lib/cacheable/cache_adapters/memory_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def fetch(key, _options = {})
write(key, yield)
end

def delete(key)
def delete(key) # rubocop:disable Naming/PredicateMethod -- mimics the ActiveSupport::Cache::Store#delete interface and isn't a predicate
return false unless exist?(key)

cache.delete key
Expand Down
4 changes: 2 additions & 2 deletions lib/cacheable/method_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def cacheable(*original_method_names, **opts)
private

def method_interceptor_module_name
class_name = name&.gsub(/:/, '') || to_s.gsub(/[^a-zA-Z_0-9]/, '')
class_name = name&.gsub(':', '') || to_s.gsub(/[^a-zA-Z_0-9]/, '')
"#{class_name}Cacher"
end

Expand All @@ -35,7 +35,7 @@ def create_cacheable_methods(original_method_name, opts = {})
end

define_method(method_names[:with_cache_method_name]) do |*args|
Cacheable.cache_adapter.fetch(__send__(method_names[:key_format_method_name], *args), opts[:cache_options]) do
Cacheable.cache_adapter.fetch(__send__(method_names[:key_format_method_name], *args), opts[:cache_options]) do # rubocop:disable Lint/UselessDefaultValueArgument -- not Hash#fetch; second arg is cache options (e.g. expires_in) passed to the adapter
__send__(method_names[:without_cache_method_name], *args)
end
end
Expand Down
11 changes: 0 additions & 11 deletions spec/.rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,12 @@ inherit_from: ../.rubocop.yml
Layout/LineLength:
Max: 170

# This cop does not like rspec dsl syntax https://github.com/bbatsov/rubocop/pull/4237#issuecomment-291408032
Lint/AmbiguousBlockAssociation:
Enabled: false

Metrics/BlockLength:
Enabled: false

RSpec/AnyInstance:
Enabled: false

RSpec/ExampleLength:
Max: 100

# Disabling the ExpectInHook cop can make testing similar preconditions with only slightly test differences easier
RSpec/ExpectInHook:
Enabled: false

RSpec/MessageSpies:
Enabled: false

Expand Down
4 changes: 2 additions & 2 deletions spec/cacheable/cache_adapters/memory_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@

describe '#delete' do
it 'returns false if the value was not found' do
expect(cache.delete(key)).to eq(false)
expect(cache.delete(key)).to be(false)
end

it 'returns true if the value was found' do
cache.fetch(key) { true }
expect(cache.delete(key)).to eq(true)
expect(cache.delete(key)).to be(true)
end

it 'removes the value from the cache' do
Expand Down
15 changes: 9 additions & 6 deletions spec/cacheable/cacheable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
let(:class_definition) do
cacheable_method_name = cacheable_method
cacheable_method_inner_name = cacheable_method_inner
# Capture described_class here because class_exec changes self to
# the anonymous class, where the RSpec helper is not available.
mod = described_class
proc do
include Cacheable # rubocop:disable RSpec/DescribedClass
include mod

define_method(cacheable_method_name) do |arg = nil|
send cacheable_method_inner_name, arg
Expand Down Expand Up @@ -65,8 +68,8 @@
cacheable_object.send(cacheable_method)

expect { cacheable_object.send("clear_#{cacheable_method}_cache") }
.to change { described_class.cache_adapter.read(cacheable_object.cacheable_method_key_format) }.to(nil)
.and not_change { described_class.cache_adapter.read(any_other_cached_value) } # rubocop:disable Layout/MultilineMethodCallIndentation
.to change { described_class.cache_adapter.read(cacheable_object.cacheable_method_key_format) }.to(nil) # rubocop:disable Lint/AmbiguousBlockAssociation
.and not_change { described_class.cache_adapter.read(any_other_cached_value) }
end

it 'allows access to `super` via a module interceptor' do
Expand Down Expand Up @@ -230,7 +233,7 @@

attr_accessor :secret

cacheable custom_key_object_access_cacheable_method, key_format: proc { |c| c.secret }
cacheable custom_key_object_access_cacheable_method, key_format: proc { |obj, _method_name, _args| obj.secret }
end
cacheable_object.secret = 'some_state_on_the_object'

Expand Down Expand Up @@ -376,7 +379,7 @@
cacheable symbol_unless_cache_method, unless: :cache_control_method

def cache_control_method(*_args)
true
'a truthy value skips caching with :unless'
end
end

Expand Down Expand Up @@ -455,7 +458,7 @@ def cache_control_method
calculate_hard_value
end

cacheable :cache_method_with_cache_options, cache_options: cache_options
cacheable :cache_method_with_cache_options, cache_options:
end

expect(described_class.cache_adapter).to receive(:fetch).with(anything, hash_including(cache_options))
Expand Down