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 ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test, :development do
6
+ gem 'rspec'
7
+ gem 'sqlite3'
8
+ end
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
@@ -0,0 +1,3 @@
1
+ module HasCacheKey
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,2 @@
1
+ require 'has_cache_key/version'
2
+ require 'has_cache_key/model_ext'
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: []