Skip to content
Open
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
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
19 changes: 15 additions & 4 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
inherit_from: .rubocop_todo.yml

Documentation:
Enabled: false
AllCops:
NewCops: enable
TargetRubyVersion: 2.6

Layout/BlockAlignment:
EnforcedStyleAlignWith: start_of_block

Layout/MultilineMethodCallIndentation:
EnforcedStyle: indented_relative_to_receiver

Metrics/BlockLength:
ExcludedMethods:
- context
- describe

Naming/FileName:
Exclude:
- 'lib/grape-cache.rb'

Style/FrozenStringLiteralComment:
SafeAutoCorrect: true
30 changes: 0 additions & 30 deletions .rubocop_todo.yml

This file was deleted.

11 changes: 11 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,14 @@
source 'https://rubygems.org'

gemspec

group :development, :test do
gem 'bundler'
gem 'pry', '~> 0.13.1'
gem 'rubocop', '~> 1.18.0'
end

group :test do
gem 'rspec', '~> 3.10.0'
gem 'rack-test', '~> 1.1.0'
end
10 changes: 4 additions & 6 deletions grape-cache.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@ Gem::Specification.new do |spec|
spec.require_path = 'lib'

spec.metadata = {
'bug_tracker_uri' => 'https://github.com/netrusov/grape-cache/issues',
'source_code_uri' => 'https://github.com/netrusov/grape-cache'
'bug_tracker_uri' => 'https://github.com/netrusov/grape-cache/issues',
'source_code_uri' => 'https://github.com/netrusov/grape-cache'
}

spec.required_ruby_version = '>= 2.6.0'

spec.add_runtime_dependency 'activesupport'
spec.add_runtime_dependency 'grape', '>= 1.2', '< 2'
spec.add_runtime_dependency 'rack'

spec.add_development_dependency 'bundler'
spec.add_development_dependency 'pry'
spec.add_development_dependency 'rubocop'
end
78 changes: 57 additions & 21 deletions lib/grape/cache.rb
Original file line number Diff line number Diff line change
@@ -1,44 +1,80 @@
# frozen_string_literal: true

require 'rack'
require 'grape'
require 'rack'

require 'active_support/cache'
require 'active_support/concern'
require 'active_support/notifications'

require 'grape/cache/configurable'
require 'grape/cache/extensions/dsl'
require 'grape/cache/extensions/endpoint'
require 'grape/cache/helpers'
require 'grape/cache/dsl'
require 'grape/cache/version'

require 'grape/cache/extensions/dsl'
require 'grape/cache/extensions/instance'
require 'grape/cache/extensions/middleware/formatter'

module Grape
# @nodoc
module Cache
include Grape::Cache::Configurable

# @param key [String] cache key
# @return [Object] response object
# @return [String] serialized response object
def self.read(key)
config
.backend
.read(key)
.then { |result| Grape::Json.load(result) if result }
config.backend.read(key)
end

# @param key [String] cache key
# @param response [Object] response object
# @param value [Object] response object
# @param options [Object] (see ActiveSupport::Cache#write)
# @return [Boolean] operation status
def self.store(key, response, options)
return false unless response
def self.write(key, value, options)
return false unless value

options = options.except(:expires_in) if options[:expires_in]&.<=(0)

Grape::Json
.dump(response)
.then { |value| config.backend.write(key, value, options) }
config.backend.write(key, value, options)
rescue StandardError => e
ActiveSupport::Notifications.instrument('grape_cache.write_error', error: e)
false
end
end
end

Grape::API::Instance.class_exec do
include Grape::Cache::Extensions::DSL
include Grape::Cache::Extensions::Endpoint
# @param env [Object] request env
# @param key [#to_s, Array<#to_s>] any object or array of objects that respond to `#to_s`
# @return [String] cache key
def self.expand_cache_key(env, key)
key = [
env[Rack::REQUEST_METHOD],
env[Rack::PATH_INFO],
'-'
] + Array(key).compact

helpers Grape::Cache::Helpers
ActiveSupport::Cache.expand_cache_key(key)
end

# @param env [Object] request env
# @param key [String] cache key
# @param context [Grape::Cache::DSL] context object
# @yield block which will be called on cache miss
# @return [String, Object] returns cached response or result of executed block
def self.with_cached_response(env, key, context)
env['grape-cache'] = { key: key }

if (value = Grape::Cache.read(key))
env['grape-cache'][:hit] = true
env['grape-cache'][:value] = value
else
env['grape-cache'][:hit] = false
env['grape-cache'][:options] = context.slice(:expires_in, :race_condition_ttl).compact

yield
end
end
end
end

Grape::API::Instance.include(Grape::Cache::Extensions::DSL)
Grape::API::Instance.include(Grape::Cache::Extensions::Instance)
Grape::Middleware::Formatter.prepend(Grape::Cache::Extensions::Middleware::Formatter)
5 changes: 2 additions & 3 deletions lib/grape/cache/configurable.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# frozen_string_literal: true

require 'active_support/cache'
require 'active_support/concern'

module Grape
module Cache
# @nodoc
module Configurable
extend ActiveSupport::Concern

# @nodoc
class Configuration
attr_accessor :backend

Expand Down
39 changes: 22 additions & 17 deletions lib/grape/cache/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module Grape
module Cache
# @nodoc
class DSL
# @private
class CacheControl
Expand All @@ -10,7 +11,7 @@ def initialize(options = {})
end

def max_age
@options[:max_age]
@options[:max_age].to_i
end

def public?
Expand All @@ -26,44 +27,48 @@ def no_cache?
end

def to_s
options = []

options << (public? ? 'public' : 'private')
options << (no_cache? ? 'no-cache' : ['max-age', max_age].join('='))
options << 'must-revalidate' if must_revalidate?

options.join(', ')
[
(public? ? 'public' : 'private'),
(no_cache? ? 'no-cache' : "max-age=#{max_age}"),
('must-revalidate' if must_revalidate?)
].compact.join(', ')
end
end

delegate :[], :[]=, :fetch, to: :@storage
delegate :[], :slice, to: :@store

def initialize
@storage = {
@store = {
expires_in: 0,
cache_control: 'private, max-age=0, must-revalidate'
race_condition_ttl: 5,
cache_control: 'private, no-cache, must-revalidate'
}
end

# @return [void]
def key(value = nil, &block)
self[:key] = value || block
@store[:key] = value || block
end

# @param seconds [Integer] cache TTL
# @return [void]
def expires_in(seconds)
self[:expires_in] = seconds.to_i
@store[:expires_in] = seconds.to_i
end

# @param seconds [Integer] cache race condition TTL
# @return [void]
def race_condition_ttl(seconds)
@store[:race_condition_ttl] = seconds.to_i
end

# @param options [Hash] parameters for "Cache-Control" header
# @option options :public [Boolean] defaults to "false"
# @option options :public [Boolean]
# @option options :must_revalidate [Boolean]
# @option options :max_age [Integer] defaults to "expires_in"
# @option options :max_age [Integer]
# @return [void]
def cache_control(options = {})
self[:cache_control] =
CacheControl.new(options.reverse_merge(max_age: self[:expires_in])).to_s
@store[:cache_control] = CacheControl.new(options).to_s
end
end
end
Expand Down
12 changes: 4 additions & 8 deletions lib/grape/cache/extensions/dsl.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# frozen_string_literal: true

require 'active_support/concern'

require 'grape/cache/dsl'

module Grape
module Cache
module Extensions
# @nodoc
module DSL
extend ActiveSupport::Concern

Expand All @@ -15,10 +12,9 @@ module DSL
#
# @return [void]
def cache(&block)
Grape::Cache::DSL.new.then do |context|
context.instance_exec(&block)
route_setting :cache, context
end
context = Grape::Cache::DSL.new
context.instance_exec(&block)
route_setting(:cache, context)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
module Grape
module Cache
module Extensions
module Endpoint
# @nodoc
module Instance
extend ActiveSupport::Concern

class_methods do
Expand All @@ -15,14 +16,11 @@ def generate_cached_api_method(context, &block)
proc do
header 'Cache-Control', context[:cache_control]

cache_key =
context[:key]
.then { |key| key.is_a?(Proc) ? instance_exec(&key) : key }
.then { |key| expand_cache_key(*key) }
key = context[:key]
key = key.is_a?(Proc) ? instance_exec(&key) : key
key = Grape::Cache.expand_cache_key(env, key)

Grape::Cache.read(cache_key) || instance_exec(&block).tap do |response|
Grape::Cache.store cache_key, (body || response), expires_in: context[:expires_in]
end
Grape::Cache.with_cached_response(env, key, context) { instance_exec(&block) }
end
end

Expand Down
27 changes: 27 additions & 0 deletions lib/grape/cache/extensions/middleware/formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Grape
module Cache
module Extensions
module Middleware
# @nodoc
module Formatter
def fetch_formatter(*)
formatter = super

lambda do |body, env|
cache = env['grape-cache']

return formatter.call(body, env) unless cache
return cache[:value] if cache[:hit]

formatter.call(body, env).tap do |response|
Grape::Cache.write(cache[:key], response, cache[:options])
end
end
end
end
end
end
end
end
20 changes: 0 additions & 20 deletions lib/grape/cache/helpers.rb

This file was deleted.

Loading