has_cache_key 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +8 -0
- data/MIT-LICENSE +20 -0
- data/README.md +41 -0
- data/Rakefile +22 -0
- data/lib/has_cache_key/cache_key.rb +24 -0
- data/lib/has_cache_key/model_ext.rb +87 -0
- data/lib/has_cache_key/possible_values.rb +45 -0
- data/lib/has_cache_key/version.rb +3 -0
- data/lib/has_cache_key.rb +2 -0
- metadata +65 -0
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
HasCacheKey -- automatic cache key management For Rails
|
2
|
+
================================
|
3
|
+
|
4
|
+
Allows you describe cache keys in the models, and provides automatic expiration
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
Add this line to your Gemfile:
|
10
|
+
|
11
|
+
gem 'has_cache_key'
|
12
|
+
|
13
|
+
Usage
|
14
|
+
-----
|
15
|
+
|
16
|
+
Define cache_keys in your model
|
17
|
+
|
18
|
+
class Listing < ActiveRecord::Base
|
19
|
+
# Default cache key for the listing view
|
20
|
+
has_cache_key :id
|
21
|
+
# A cache key for the home page of category in location
|
22
|
+
has_cache_key [:location_id, :category_id], name: :category_in_location_home_page
|
23
|
+
end
|
24
|
+
|
25
|
+
To get a cache key in you can use one of the following:
|
26
|
+
|
27
|
+
|
28
|
+
# Default cache key
|
29
|
+
@listing.cache_key
|
30
|
+
|
31
|
+
# A cache key
|
32
|
+
@listing.cache_key(:category_in_location_home_page)
|
33
|
+
|
34
|
+
# A cache key without a listing instance:
|
35
|
+
Listing.cache_key(:category_in_location_home_page, category_id: 10, location_id: 10)
|
36
|
+
|
37
|
+
|
38
|
+
Your cache keys will be automatically updates after_save and after_destroy according to the attribute changes.
|
39
|
+
|
40
|
+
|
41
|
+
This project uses MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rake'
|
10
|
+
require 'rdoc/task'
|
11
|
+
require 'rspec/core/rake_task'
|
12
|
+
|
13
|
+
RSpec::Core::RakeTask.new(:spec)
|
14
|
+
task :default => :spec
|
15
|
+
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'Has Cache Key'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README.rdoc')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module HasCacheKey
|
2
|
+
class CacheKey
|
3
|
+
attr_reader :name, :keys
|
4
|
+
|
5
|
+
# *keys, options
|
6
|
+
def initialize(*args)
|
7
|
+
options = args.extract_options!
|
8
|
+
keys = args
|
9
|
+
@keys = args.map { |k| k.is_a?(Symbol) ? k : k.to_sym }.freeze
|
10
|
+
@format = options[:format]
|
11
|
+
if options[:name]
|
12
|
+
@name = options[:name]
|
13
|
+
@name = @name.to_sym unless @name.is_a?(Symbol)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def format(values = nil)
|
18
|
+
return @format if !values
|
19
|
+
values = values.symbolize_keys
|
20
|
+
fmt = @format
|
21
|
+
fmt.is_a?(Proc) ? fmt.call(values) : (fmt % values)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'has_cache_key/possible_values'
|
2
|
+
require 'has_cache_key/cache_key'
|
3
|
+
|
4
|
+
module HasCacheKey
|
5
|
+
module ModelExt
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
base.after_save :expire_cache_keys
|
9
|
+
base.after_destroy :expire_cache_keys
|
10
|
+
end
|
11
|
+
|
12
|
+
def expire_cache_keys
|
13
|
+
# First, assemble all keys the listing is involved in, and the values that were affected
|
14
|
+
affected_values_by_key = {}
|
15
|
+
attr_changes = self.changes
|
16
|
+
keys = self.class.cache_keys.map(&:keys)
|
17
|
+
keys.flatten!
|
18
|
+
keys.uniq!
|
19
|
+
|
20
|
+
keys.each do |key|
|
21
|
+
affected_values_by_key[key] = if attr_changes.has_key?(key)
|
22
|
+
# For :updated_at only expire the first value , because the second one never exists
|
23
|
+
if key == :updated_at
|
24
|
+
[attr_changes[key][0]]
|
25
|
+
else
|
26
|
+
attr_changes[key]
|
27
|
+
end
|
28
|
+
else
|
29
|
+
[send(key)]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
self.class.cache_keys.each do |cache_key|
|
34
|
+
PossibleValues.new(affected_values_by_key.slice(*cache_key.keys)).each do |interpolations|
|
35
|
+
expire_fragment_key(cache_key.format(interpolations))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @returns cache key string for the cache key with the given name
|
41
|
+
def cache_key(name = self.class.name.to_s.demodulize.underscore)
|
42
|
+
self.class.cache_key(name, attributes)
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
def expire_fragment_key(key)
|
47
|
+
(@controller ||= ActionController::Base.new).expire_fragment(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
module ClassMethods
|
51
|
+
def cache_keys
|
52
|
+
@cache_keys ||= []
|
53
|
+
end
|
54
|
+
|
55
|
+
def cache_key(name, format_values = nil)
|
56
|
+
(@cache_key_by_name ||= {}.with_indifferent_access)[name].format(format_values)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Adds a cache key association to your model
|
61
|
+
# @param [Array|Symbol] composed_of keys that make up the cache key,
|
62
|
+
# can be also passed as @option composed_of
|
63
|
+
#
|
64
|
+
# @option [Symbol] name unique name which is used to refer to the cache key from your application
|
65
|
+
# defaults to: name.to_s.demodulize.underscore
|
66
|
+
#
|
67
|
+
# @example Expire on any update:
|
68
|
+
# has_cache_key :product_snippet, composed_of: [:updated_at]
|
69
|
+
def has_cache_key(*args)
|
70
|
+
if (options = args.extract_options!)
|
71
|
+
options[:composed_of] ||= args.flatten
|
72
|
+
else
|
73
|
+
options = {}
|
74
|
+
end
|
75
|
+
options[:composed_of] = Array(options[:composed_of])
|
76
|
+
options[:name] ||= name.to_s.demodulize.underscore
|
77
|
+
options[:format] ||= "#{options[:name]}-#{options[:composed_of].map { |k| "%{#{k}}" }.join('-')}"
|
78
|
+
cache_key = HasCacheKey::CacheKey.new(*options[:composed_of], options)
|
79
|
+
(@cache_keys ||= []) << cache_key
|
80
|
+
(@cache_key_by_name ||= {}.with_indifferent_access)[cache_key.name] = cache_key
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
require 'active_record'
|
87
|
+
ActiveRecord::Base.class_eval { include HasCacheKey::ModelExt }
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module HasCacheKey
|
2
|
+
class PossibleValues
|
3
|
+
attr_reader :data
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
# Yields with {slot_name => possible value} for all possible values
|
7
|
+
# It yields exactly this many times:
|
8
|
+
# PRODUCT{i in 0..COUNT(slot))}
|
9
|
+
# COUNT(VALUES(slot)))
|
10
|
+
def each(&block)
|
11
|
+
if @results
|
12
|
+
@results.each &block
|
13
|
+
return self
|
14
|
+
end
|
15
|
+
@results = []
|
16
|
+
cur_v_i = Array.new(@data.keys.length, 0)
|
17
|
+
keys = @data.keys.sort_by(&:to_s)
|
18
|
+
result = {}
|
19
|
+
keys.each do |key|
|
20
|
+
result[key] = @data[key][0]
|
21
|
+
end
|
22
|
+
while true
|
23
|
+
r = result.dup
|
24
|
+
@results << r
|
25
|
+
block.call(r) if block
|
26
|
+
keys.each_with_index do |k, k_i|
|
27
|
+
v_i = ((cur_v_i[k_i] = (1 + cur_v_i[k_i]) % @data[k].length))
|
28
|
+
result[k] = @data[k][v_i]
|
29
|
+
if v_i > 0 # Found next ?
|
30
|
+
break
|
31
|
+
elsif k_i == keys.length - 1 # Last?
|
32
|
+
return
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@results
|
37
|
+
end
|
38
|
+
|
39
|
+
# Iterates over possible all possible values of keys
|
40
|
+
# data hash of {slot_name => [possible values...]}
|
41
|
+
def initialize(data)
|
42
|
+
@data = data
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_cache_key
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Gleb Mazovetskiy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-18 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: &15820680 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.2'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *15820680
|
25
|
+
description: Allows you to define multiple cache keys on the module, and automatically
|
26
|
+
expires them.
|
27
|
+
email: glex.spb@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- lib/has_cache_key/possible_values.rb
|
33
|
+
- lib/has_cache_key/model_ext.rb
|
34
|
+
- lib/has_cache_key/cache_key.rb
|
35
|
+
- lib/has_cache_key/version.rb
|
36
|
+
- lib/has_cache_key.rb
|
37
|
+
- MIT-LICENSE
|
38
|
+
- Rakefile
|
39
|
+
- Gemfile
|
40
|
+
- README.md
|
41
|
+
homepage:
|
42
|
+
licenses: []
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.8.11
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Automatic Cache Key Management for Rails Models. v0.0.1
|
65
|
+
test_files: []
|