cache-machine 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,9 @@
1
+ # Packed ge
2
+ *.gem
3
+
4
+ # RubyMine metadata
5
+ .idea
6
+
1
7
  # test database
2
8
  db
3
9
 
data/Gemfile CHANGED
@@ -2,6 +2,7 @@ source "http://rubygems.org"
2
2
 
3
3
  gem "rails"
4
4
  gem "activemodel"
5
+ gem "activesupport"
5
6
 
6
7
  group :development do
7
8
  gem "bundler", "~> 1.0.0"
@@ -94,6 +94,7 @@ PLATFORMS
94
94
 
95
95
  DEPENDENCIES
96
96
  activemodel
97
+ activesupport
97
98
  bundler (~> 1.0.0)
98
99
  jeweler (~> 1.6.2)
99
100
  rails
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.9
1
+ 0.1.10
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{cache-machine}
8
- s.version = "0.1.9"
8
+ s.version = "0.1.10"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Sergei Zinin", "Kevin Goslar"]
12
- s.date = %q{2011-10-13}
12
+ s.date = %q{2011-10-22}
13
13
  s.description = %q{A Ruby on Rails framework to support cache management based on explicitely modeled caching dependencies.}
14
14
  s.email = %q{kgoslar@partyearth.com}
15
15
  s.extra_rdoc_files = [
@@ -29,15 +29,20 @@ Gem::Specification.new do |s|
29
29
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
30
30
  s.add_runtime_dependency(%q<rails>, [">= 0"])
31
31
  s.add_runtime_dependency(%q<activemodel>, [">= 0"])
32
+ s.add_runtime_dependency(%q<activesupport>, [">= 0"])
32
33
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
33
34
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
34
35
  else
35
36
  s.add_dependency(%q<rails>, [">= 0"])
37
+ s.add_dependency(%q<activemodel>, [">= 0"])
38
+ s.add_dependency(%q<activesupport>, [">= 0"])
36
39
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
37
40
  s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
38
41
  end
39
42
  else
40
43
  s.add_dependency(%q<rails>, [">= 0"])
44
+ s.add_dependency(%q<activemodel>, [">= 0"])
45
+ s.add_dependency(%q<activesupport>, [">= 0"])
41
46
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
42
47
  s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
43
48
  end
@@ -4,13 +4,38 @@ module CacheMachine
4
4
  module Cache
5
5
  extend ActiveSupport::Concern
6
6
 
7
+ # Enable cache by default.
8
+ @enabled = true
9
+
7
10
  # Supported by default cache formats.
8
11
  @formats = [nil, :ehtml, :html, :json, :xml]
9
12
 
13
+ # Returns if cache is enabled.
14
+ #
15
+ # @return [ false, true ]
16
+ def self.enabled?
17
+ @enabled
18
+ end
19
+
20
+ # Returns currently set formats.
21
+ #
22
+ # @return [Array<Symbol>]
10
23
  def self.formats
11
24
  @formats
12
25
  end
13
26
 
27
+ # Enables/disables cache.
28
+ #
29
+ # @param [ false, true ] is_enabled
30
+ def self.enabled= is_enabled
31
+ @enabled = is_enabled
32
+ end
33
+
34
+ # Sets default formats.
35
+ #
36
+ # @note Empty format entry will always be present.
37
+ #
38
+ # @param [ Array<Symbol> ] formats
14
39
  def self.formats= formats
15
40
  @formats = [nil] | [*formats]
16
41
  end
@@ -21,46 +46,68 @@ module CacheMachine
21
46
  end
22
47
 
23
48
  module ClassMethods
49
+
24
50
  # Initializes tracking associations to write and reset cache.
25
- # +associations+ parameter is to represent cache map with hash.
26
51
  #
27
- # ==== Examples
28
- # # Cache associated collections
52
+ # @example Cache associated collections.
29
53
  # acts_as_cache_machine_for :cats, :dogs
30
- #
31
- # # Cache result of method to be expired when collection changes.
54
+ # @example Cache result of method to be expired when collection changes.
32
55
  # acts_as_cache_machine_for :cats => :cat_ids
33
- #
34
- # # Cache and expire dependent collections (_mouse_ change invalidates all other collection caches by chain)
56
+ # @example Cache and expire dependent collections (_mouse_ change invalidates all other collection caches by chain)
35
57
  # acts_as_cache_machine_for :mouses => :cats, :cats => [:dogs, :bears], :dogs, :bears
58
+ #
59
+ # @param [ Hash<Symbol, Array> ] associations Cache Map
36
60
  def acts_as_cache_machine_for *associations
61
+ Time.zone ||= ActiveSupport::TimeZone[0]
62
+
37
63
  include CacheMachine::Cache::Map
38
64
  cache_associated(associations)
39
65
  end
40
66
  alias :cache_map :acts_as_cache_machine_for
41
67
 
42
68
  # Returns timestamp of class collection.
69
+ #
70
+ # @example Return timestamp of the class.
71
+ # MyActiveRecordClass.timestamp
72
+ #
73
+ # @param [ Symbol ] format
74
+ #
75
+ # @return [ String ]
43
76
  def timestamp format = nil
44
77
  Rails.cache.fetch(timestamp_key format) { Time.now.to_i.to_s }
45
78
  end
46
79
 
47
80
  # Returns cache key to fetch timestamp from memcached.
81
+ #
82
+ # @param [ Symbol ] format
83
+ #
84
+ # @return [ String ]
48
85
  def timestamp_key format = nil
49
86
  [self.name, format, 'timestamp'].join '_'
50
87
  end
51
88
 
52
- # Returns cache key of +anything+ with timestamp attached.
89
+ # Returns cache key of anything with timestamp attached.
90
+ #
91
+ # @example Return timestamped key of the class.
92
+ # MyActiveRecordClass.timestamped_key
93
+ #
94
+ # @param [Symbol] format
95
+ #
96
+ # @return [ String ]
53
97
  def timestamped_key format = nil
54
98
  [timestamp_key(format), timestamp(format)].join '_'
55
99
  end
56
100
 
57
101
  # Resets timestamp of class collection.
102
+ #
103
+ # @param [ Symbol ] format
58
104
  def reset_timestamp format = nil
59
105
  cache_key = timestamp_key format
60
106
  CacheMachine::Logger.info "CACHE_MACHINE (reset_timestamp): deleting '#{cache_key}'."
61
107
  Rails.cache.delete(cache_key)
62
108
  end
63
109
 
110
+ # Resets all timestams for all formats.
64
111
  def reset_timestamps
65
112
  CacheMachine::Cache.formats.each { |format| reset_timestamp format }
66
113
  end
@@ -1,7 +1,6 @@
1
1
  module CacheMachine
2
2
  module Cache
3
3
 
4
- # Module to write and expire association cache by given map.
5
4
  module Map
6
5
  extend ActiveSupport::Concern
7
6
 
@@ -14,7 +13,10 @@ module CacheMachine
14
13
  end
15
14
 
16
15
  module ClassMethods
16
+
17
17
  # Fills cache map.
18
+ #
19
+ # @param [ Hash<Symbol, Array> ] associations
18
20
  def cache_associated associations
19
21
  [*associations].each do |association|
20
22
  self.cache_map.merge! association.is_a?(Hash) ? association : {association => []}
@@ -22,20 +24,32 @@ module CacheMachine
22
24
  end
23
25
 
24
26
  # Defines timestamp for object.
27
+ #
28
+ # @example Define timestamp to be updated every hour.
29
+ # class MyModel < ActiveRecord::Base
30
+ # include CacheMachine::Cache
31
+ # define_timestamp(:my_timestamp, :expires_in => 1.hour) { my_optional_value }
32
+ # end
33
+ #
34
+ # @param [ String, Symbol ] timestamp_name
35
+ # @param [ Hash ] options
25
36
  def define_timestamp timestamp_name, options = {}, &block
26
- options[:timestamp] = block if block
37
+ if block_given?
38
+ options[:timestamp] = block
39
+ end
27
40
 
28
41
  define_method timestamp_name do
29
42
  fetch_cache_of(timestamp_key_of(timestamp_name), options) do
30
43
  CacheMachine::Logger.info "CACHE_MACHINE (define_timestamp): deleting old timestamp '#{timestamp_name}'."
31
44
  delete_cache_of timestamp_name # Case when cache expired by time.
32
- Time.now.to_i.to_s
45
+ Time.zone.now.to_i.to_s
33
46
  end
34
47
  end
35
48
  end
36
49
 
37
- # Deletes cache of collection with name +association_id+ for each object associated with +record+
38
- # Called only when <tt>has_many :through</tt> collection changed.
50
+ # Deletes cache of collection associated via many-to-many.
51
+ #
52
+ # @param [ ActiveRecord::Base ]
39
53
  def delete_association_cache_on record, reflection
40
54
  pk = record.class.primary_key
41
55
 
@@ -50,8 +64,10 @@ module CacheMachine
50
64
  end
51
65
  end
52
66
 
53
- # Overwrites +has_many+ of +ActiveRecord+ class to hook Cache Machine.
54
- def has_many(association_id, options = {})
67
+ # Hooks association changes.
68
+ #
69
+ # @private
70
+ def has_many(association_id, options = {}) #:nodoc:
55
71
  # Ensure what collection should be tracked.
56
72
  if (should_be_on_hook = self.cache_map.keys.include?(association_id)) && options[:through]
57
73
  # If relation is _many_to_many_ track collection changes.
@@ -62,11 +78,15 @@ module CacheMachine
62
78
  hook_cache_machine_on association_id if should_be_on_hook
63
79
  end
64
80
 
65
- # Overwrites +has_and_belongs_to_many+ of +ActiveRecord+ class to hook Cache Machine.
66
- def has_and_belongs_to_many(association_id, options = {})
81
+ # Hooks association changes.
82
+ #
83
+ # @private
84
+ def has_and_belongs_to_many(association_id, options = {}) #:nodoc:
85
+
67
86
  # Ensure what collection should be tracked.
68
87
  if(should_be_on_hook = self.cache_map.keys.include?(association_id))
69
- # If relation is _many_to_many_ track collection changes.
88
+
89
+ # If relation is many-to-many track collection changes.
70
90
  options[:after_add] = \
71
91
  options[:before_remove] = :delete_association_cache_on
72
92
  end
@@ -76,7 +96,9 @@ module CacheMachine
76
96
 
77
97
  protected
78
98
 
79
- # Hooks Cache Machine on association with name +association_id+.
99
+ # Hooks Cache Machine.
100
+ #
101
+ # @param [ Symbol ] association_id
80
102
  def hook_cache_machine_on association_id
81
103
  reset_cache_proc = Proc.new do |reflection, target_class, &block|
82
104
  block ||= lambda { target_class.delete_association_cache_on self, reflection }
@@ -104,48 +126,65 @@ module CacheMachine
104
126
  end
105
127
 
106
128
  module InstanceMethods
107
- # Returns cache key of +_member+.
108
- # TODO: describe options.
129
+
130
+ # Returns cache key of the member.
131
+ #
132
+ # @param [ Symbol ] _member
133
+ # @param [ Hash ] options
134
+ #
135
+ # @return [ String ]
109
136
  def cache_key_of _member, options = {}
110
- [self.class.name, self.to_param, _member, options[:format], options[:page] || 1].compact.join '_'
137
+ timestamp = instance_eval(&options[:timestamp]) if options.has_key? :timestamp
138
+
139
+ [ self.class.name,
140
+ self.to_param,
141
+ _member,
142
+ options[:format],
143
+ options[:page] || 1,
144
+ timestamp ].flatten.compact.join '/'
111
145
  end
112
146
 
113
- # Fetches cache of +_member+ from cache map.
114
- # TODO: Describe options.
115
- # TODO: Describe timestamp features (we can pass methods or fields as timestamps too).
116
- # Or we can use define_timestamp +:expires_in => 20.hours+.
147
+ # Fetches cache of the member.
148
+ #
149
+ # @example Fetch cache of associated collection to be refreshed every hour.
150
+ # @instance.fetch_cache_of :association, :timestamp => lambda { custom_instance_method },
151
+ # :expires_in => 1.hour
152
+ #
153
+ # @param [ Symbol ] _member
154
+ # @param [ Hash ] options
155
+ #
156
+ # @return [ * ]
117
157
  def fetch_cache_of _member, options = {}, &block
118
- cache_key = if timestamp = options[:timestamp]
119
- # Make key dependent on collection timestamp and optional timestamp.
120
- [timestamped_key_of(_member, options), instance_eval(&timestamp)].join '_'
121
- else
122
- cache_key_of(_member, options)
123
- end
124
-
125
- expires_in = if expires_at = options[:expires_at]
126
- expires_at = expires_at.call if expires_at.kind_of? Proc
158
+ if CacheMachine::Cache::enabled?
159
+ expires_in = if expires_at = options[:expires_at]
160
+ if expires_at.kind_of? Proc
161
+ expires_at = expires_at.call
162
+ end
127
163
 
128
- if expires_at.kind_of? Time
129
- expires_at - Time.now
164
+ if expires_at.kind_of? Time
165
+ expires_at - Time.zone.now
166
+ else
167
+ raise ArgumentError, "expires_at is not a Time"
168
+ end
130
169
  else
131
- raise ArgumentError, "expires_at is not a Time"
170
+ options[:expires_in]
132
171
  end
172
+
173
+ CacheMachine::Logger.info "CACHE_MACHINE (fetch_cache_of): reading '#{cache_key}'."
174
+ Rails.cache.fetch(cache_key_of(_member, options), :expires_in => expires_in, &block)
133
175
  else
134
- options[:expires_in]
176
+ yield
135
177
  end
136
-
137
- CacheMachine::Logger.info "CACHE_MACHINE (fetch_cache_of): reading '#{cache_key}'."
138
- Rails.cache.fetch(cache_key, :expires_in => expires_in, &block)
139
178
  end
140
179
 
141
180
  # Removes all caches using map.
142
181
  def delete_all_caches
143
- self.class.cache_map.keys.each do |cached_collection|
144
- delete_cache_of cached_collection
145
- end
182
+ self.class.cache_map.to_a.flatten.uniq.each &method(:delete_cache_of)
146
183
  end
147
184
 
148
- # Recursively deletes cache by map for +_member+.
185
+ # Recursively deletes cache by map starting from the member.
186
+ #
187
+ # @param [ Symbol ] _member
149
188
  def delete_cache_of _member
150
189
  delete_cache_of_only _member
151
190
  if chain = self.class.cache_map[_member]
@@ -153,7 +192,9 @@ module CacheMachine
153
192
  end
154
193
  end
155
194
 
156
- # Deletes cache of only +_member+ ignoring cache map.
195
+ # Deletes cache of the only member ignoring cache map.
196
+ #
197
+ # @param [ Symbol ] _member
157
198
  def delete_cache_of_only _member
158
199
  CacheMachine::Cache.formats.each do |cache_format|
159
200
  page_nr = 0; begin
@@ -164,24 +205,38 @@ module CacheMachine
164
205
  reset_timestamp_of _member
165
206
  end
166
207
 
167
- # Returns timestamp cache key for +anything+.
208
+ # Returns timestamp cache key for anything.
209
+ #
210
+ # @param [ String, Symbol ] anything
211
+ #
212
+ # @return [ String ]
168
213
  def timestamp_key_of anything
169
- [self.class.name, self.to_param, anything, 'timestamp'].join '_'
214
+ [self.class.name, self.to_param, anything, 'timestamp'].join '/'
170
215
  end
171
216
 
172
- # Returns timestamp of +anything+ from memcached.
217
+ # Returns timestamp of anything from memcached.
218
+ #
219
+ # @param [ String, Symbol ] anything
220
+ #
221
+ # @return [ String ]
173
222
  def timestamp_of anything
174
- key = timestamp_key_of anything
175
- CacheMachine::Logger.info "CACHE_MACHINE (timestamp_of): reading timestamp '#{key}'."
176
- Rails.cache.fetch(key) { Time.now.to_i.to_s }
223
+ if CacheMachine::Cache::enabled?
224
+ key = timestamp_key_of anything
225
+ CacheMachine::Logger.info "CACHE_MACHINE (timestamp_of): reading timestamp '#{key}'."
226
+ Rails.cache.fetch(key) { Time.zone.now.to_i.to_s }
227
+ else
228
+ Time.zone.now.to_i.to_s
229
+ end
177
230
  end
178
231
 
179
232
  # Returns cache key of +anything+ with timestamp attached.
233
+ #
234
+ # @return [ String ]
180
235
  def timestamped_key_of anything, options = {}
181
- [cache_key_of(anything, options), timestamp_of(anything)].join '_'
236
+ [cache_key_of(anything, options), timestamp_of(anything)].join '/'
182
237
  end
183
238
 
184
- # Deletes cache of +anything+ from memory.
239
+ # Deletes cache of anything from memory.
185
240
  def reset_timestamp_of anything
186
241
  cache_key = timestamp_key_of anything
187
242
  CacheMachine::Logger.info "CACHE_MACHINE (reset_timestamp_of): deleting '#{cache_key}'."
@@ -190,8 +245,10 @@ module CacheMachine
190
245
 
191
246
  protected
192
247
 
193
- # Deletes cache of associated collection what contains +record+.
194
- # Called only when <tt>has_many :through</tt> collection changed.
248
+ # Deletes cache of associated collection what contains record.
249
+ # Called only when many-to-many collection changed.
250
+ #
251
+ # @param [ ActiveRecord::Base ] record
195
252
  def delete_association_cache_on record
196
253
 
197
254
  # Find all associations with +record+ by its class.
@@ -1,6 +1,18 @@
1
1
  module CacheMachine
2
2
  module Helpers
3
3
  module CacheHelper
4
+
5
+ # Returns cached EHTML content.
6
+ #
7
+ # @example Return html from cache.
8
+ # = cache_for @instance, :association do
9
+ # %div= @instance.associated_records
10
+ #
11
+ # @param [ ActiveRecord::Base ] record
12
+ # @param [ Symbol ] cacheable
13
+ # @param [ Hash ] options
14
+ #
15
+ # @return [ String ]
4
16
  def cache_for record, cacheable, options = {}, &block
5
17
  record.fetch_cache_of(cacheable, options.merge(:format => :ehtml)) { capture &block }
6
18
  end
@@ -16,7 +16,11 @@ module CacheMachine
16
16
  end
17
17
 
18
18
  # Sets the log level for CacheMachine.
19
- # Call like this in your your code, best in development.rb: ActiveRecord::CacheMachine::Logger.level = :info
19
+ #
20
+ # @example Call like this in your your code, best in development.rb:
21
+ # ActiveRecord::CacheMachine::Logger.level = :info
22
+ #
23
+ # @param [Symbol] value
20
24
  def level= value
21
25
  @@level = LOGGING_LEVELS[value] or raise "CACHE_MACHINE: Unknown log level: '#{value}'."
22
26
  if @@level <= LOGGING_LEVELS[:info]
@@ -25,6 +29,9 @@ module CacheMachine
25
29
  end
26
30
 
27
31
  # Logs the given entry with the given log level.
32
+ #
33
+ # @param [ Symbol ] level
34
+ # @param [ String ] text
28
35
  def write level, text
29
36
  if @@level <= (LOGGING_LEVELS[level] or raise "CACHE_MACHINE: Unknown log level: '#{level}'.")
30
37
  puts text
@@ -0,0 +1,86 @@
1
+ TARGET_TABLE_NAME = "cachers"
2
+ HABTM_TABLE_NAME = "has_and_belongs_to_many_cacheables"
3
+ HABTM_JOINS_TABLE_NAME = [TARGET_TABLE_NAME, HABTM_TABLE_NAME].join('_')
4
+ HABTM_ASSOCIATION_NAME = HABTM_TABLE_NAME.singularize
5
+ HM_TABLE_NAME = "has_many_cacheables"
6
+ HMT_JOINS_TABLE_NAME = "joins"
7
+ HMT_TABLE_NAME = "has_many_through_cacheables"
8
+ HO_TABLE_NAME = "hs_one_cacheables"
9
+ POLY_TABLE_NAME = "polymorphics"
10
+ TARGET_ASSOCIATION_NAME = TARGET_TABLE_NAME.singularize
11
+ HMT_ASSOCIATION_NAME = HMT_TABLE_NAME.singularize
12
+
13
+ class TestMigration < ActiveRecord::Migration
14
+ def self.up
15
+ self.down
16
+ create_table(HABTM_TABLE_NAME)
17
+ create_table(HM_TABLE_NAME) { |t| t.references TARGET_ASSOCIATION_NAME }
18
+ create_table(HMT_TABLE_NAME)
19
+ create_table(HMT_JOINS_TABLE_NAME) { |t| [TARGET_ASSOCIATION_NAME, HMT_ASSOCIATION_NAME].each &t.method(:references) }
20
+ create_table(POLY_TABLE_NAME) { |t| t.references(:polymorhicable); t.string(:polymorhicable_type) }
21
+ create_table(TARGET_TABLE_NAME) { |t| t.string :name; t.integer(:parent_id) }
22
+ create_table(HABTM_JOINS_TABLE_NAME, :id => false) { |t| [TARGET_ASSOCIATION_NAME, HABTM_ASSOCIATION_NAME].each &t.method(:references) }
23
+ end
24
+
25
+ def self.down
26
+ drop_table POLY_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(POLY_TABLE_NAME)
27
+ drop_table TARGET_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(TARGET_TABLE_NAME)
28
+ drop_table HABTM_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HABTM_TABLE_NAME)
29
+ drop_table HM_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HM_TABLE_NAME)
30
+ drop_table HMT_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HMT_TABLE_NAME)
31
+ drop_table HO_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HO_TABLE_NAME)
32
+ drop_table HMT_JOINS_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HMT_JOINS_TABLE_NAME)
33
+ drop_table HABTM_JOINS_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HABTM_JOINS_TABLE_NAME)
34
+ end
35
+ end
36
+ TestMigration.up
37
+
38
+ class HasManyCacheable < ActiveRecord::Base
39
+ set_table_name HM_TABLE_NAME
40
+ belongs_to :cacher, :class_name => 'Cacher'
41
+ end
42
+
43
+ class HasManyThroughCacheable < ActiveRecord::Base
44
+ set_table_name HMT_TABLE_NAME
45
+
46
+ has_many :joins, :class_name => 'Join'
47
+ has_many :cachers, :through => :joins, :class_name => 'Cacher'
48
+ end
49
+
50
+ class Join < ActiveRecord::Base
51
+ set_table_name HMT_JOINS_TABLE_NAME
52
+
53
+ belongs_to :cacher, :class_name => 'Cacher'
54
+ belongs_to :has_many_through_cacheable, :class_name => 'HasManyThroughCacheable'
55
+ end
56
+
57
+ class HasAndBelongsToManyCacheable < ActiveRecord::Base
58
+ set_table_name HABTM_TABLE_NAME
59
+ has_and_belongs_to_many :cachers, :class_name => 'Cacher'
60
+ end
61
+
62
+ class Polymorphic < ActiveRecord::Base
63
+ set_table_name POLY_TABLE_NAME
64
+ belongs_to :polymorhicable, :polymorphic => true
65
+ end
66
+
67
+ class Cacher < ActiveRecord::Base
68
+ set_table_name TARGET_TABLE_NAME
69
+
70
+ cache_map :polymorphics,
71
+ :child_cachers,
72
+ :has_many_cacheables => :dependent_cache,
73
+ :has_many_through_cacheables => :dependent_cache,
74
+ :has_and_belongs_to_many_cacheables => :dependent_cache
75
+
76
+ define_timestamp(:dynamic_timestamp) { execute_timestamp }
77
+
78
+ has_and_belongs_to_many :has_and_belongs_to_many_cacheables, :class_name => 'HasAndBelongsToManyCacheable'
79
+ has_many :has_many_cacheables, :class_name => 'HasManyCacheable'
80
+ has_many :joins, :class_name => 'Join'
81
+ has_many :has_many_through_cacheables, :through => :joins, :class_name => 'HasManyThroughCacheable'
82
+ has_many :polymorphics, :as => :polymorhicable
83
+ has_many :child_cachers, :class_name => 'Cacher', :foreign_key => 'parent_id', :primary_key => 'id'
84
+
85
+ def to_param; name end
86
+ end
@@ -1,258 +1,159 @@
1
1
  require 'spec_helper'
2
+ require 'fixtures'
2
3
 
3
4
  describe CacheMachine do
4
- TARGET_TABLE_NAME = "cachers"
5
- HABTM_TABLE_NAME = "has_and_belongs_to_many_cacheables"
6
- HABTM_JOINS_TABLE_NAME = [TARGET_TABLE_NAME, HABTM_TABLE_NAME].join('_')
7
- HABTM_ASSOCIATION_NAME = HABTM_TABLE_NAME.singularize
8
- HM_TABLE_NAME = "has_many_cacheables"
9
- HMT_JOINS_TABLE_NAME = "joins"
10
- HMT_TABLE_NAME = "has_many_through_cacheables"
11
- HO_TABLE_NAME = "hs_one_cacheables"
12
- POLY_TABLE_NAME = "polymorphics"
13
- TARGET_ASSOCIATION_NAME = TARGET_TABLE_NAME.singularize
14
- HMT_ASSOCIATION_NAME = HMT_TABLE_NAME.singularize
15
-
16
- class TestMigration < ActiveRecord::Migration
17
- def self.up
18
- self.down
19
- create_table(HABTM_TABLE_NAME)
20
- create_table(HM_TABLE_NAME) { |t| t.references TARGET_ASSOCIATION_NAME }
21
- create_table(HMT_TABLE_NAME)
22
- create_table(HMT_JOINS_TABLE_NAME) { |t| [TARGET_ASSOCIATION_NAME, HMT_ASSOCIATION_NAME].each &t.method(:references) }
23
- create_table(POLY_TABLE_NAME) { |t| t.references(:polymorhicable); t.string(:polymorhicable_type) }
24
- create_table(TARGET_TABLE_NAME) { |t| t.string :name; t.integer(:parent_id) }
25
- create_table(HABTM_JOINS_TABLE_NAME, :id => false) { |t| [TARGET_ASSOCIATION_NAME, HABTM_ASSOCIATION_NAME].each &t.method(:references) }
26
- end
5
+ subject { Cacher.create(:name => 'foo') }
27
6
 
28
- def self.down
29
- drop_table POLY_TABLE_NAME if ActiveRecord::Base.connection.tables.include? POLY_TABLE_NAME
30
- drop_table TARGET_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(TARGET_TABLE_NAME)
31
- drop_table HABTM_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HABTM_TABLE_NAME)
32
- drop_table HM_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HM_TABLE_NAME)
33
- drop_table HMT_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HMT_TABLE_NAME)
34
- drop_table HO_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HO_TABLE_NAME)
35
- drop_table HMT_JOINS_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HMT_JOINS_TABLE_NAME)
36
- drop_table HABTM_JOINS_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HABTM_JOINS_TABLE_NAME)
7
+ describe "#cache_key_of" do
8
+ it "generates association cache keys" do
9
+ subject.cache_key_of(:anything).should eql("Cacher/foo/anything/1")
10
+ subject.cache_key_of(:anything, :format => :ehtml).should eql("Cacher/foo/anything/ehtml/1")
11
+ subject.cache_key_of(:anything, :page => 2).should eql("Cacher/foo/anything/2")
12
+ subject.cache_key_of(:anything, :format => :ehtml, :page => 2).should eql("Cacher/foo/anything/ehtml/2")
37
13
  end
38
14
  end
39
- TestMigration.up
40
-
41
- class HasManyCacheable < ActiveRecord::Base
42
- set_table_name HM_TABLE_NAME
43
- belongs_to :cacher, :class_name => 'Cacher'
44
- end
45
-
46
- class HasManyThroughCacheable < ActiveRecord::Base
47
- set_table_name HMT_TABLE_NAME
48
-
49
- has_many :joins, :class_name => 'Join'
50
- has_many :cachers, :through => :joins, :class_name => 'Cacher'
51
- end
52
-
53
- class Join < ActiveRecord::Base
54
- set_table_name HMT_JOINS_TABLE_NAME
55
-
56
- belongs_to :cacher, :class_name => 'Cacher'
57
- belongs_to :has_many_through_cacheable, :class_name => 'HasManyThroughCacheable'
58
- end
59
-
60
- class HasAndBelongsToManyCacheable < ActiveRecord::Base
61
- set_table_name HABTM_TABLE_NAME
62
- has_and_belongs_to_many :cachers, :class_name => 'Cacher'
63
- end
64
-
65
- class Polymorphic < ActiveRecord::Base
66
- set_table_name POLY_TABLE_NAME
67
- belongs_to :polymorhicable, :polymorphic => true
68
- end
69
15
 
70
- class Cacher < ActiveRecord::Base
71
- set_table_name TARGET_TABLE_NAME
72
-
73
- cache_map :polymorphics,
74
- :child_cachers,
75
- :has_many_cacheables => :dependent_cache,
76
- :has_many_through_cacheables => :dependent_cache,
77
- :has_and_belongs_to_many_cacheables => :dependent_cache
78
-
79
- define_timestamp(:dynamic_timestamp) { execute_timestamp }
80
- define_timestamp(:static_timestamp)
81
-
82
- has_and_belongs_to_many :has_and_belongs_to_many_cacheables, :class_name => 'HasAndBelongsToManyCacheable'
83
- has_many :has_many_cacheables, :class_name => 'HasManyCacheable'
84
- has_many :joins, :class_name => 'Join'
85
- has_many :has_many_through_cacheables, :through => :joins, :class_name => 'HasManyThroughCacheable'
86
- has_many :polymorphics, :as => :polymorhicable
87
- has_many :child_cachers, :class_name => 'Cacher', :foreign_key => 'parent_id', :primary_key => 'id'
88
-
89
- def to_param; name end
90
- end
91
-
92
- subject { Cacher.create(:name => 'foo') }
93
-
94
- it "generates association cache keys" do
95
- subject.cache_key_of(:has_many_cacheables, :format => :ehtml).should eql("Cacher_foo_has_many_cacheables_ehtml_1")
96
- end
16
+ describe "#fetch_cache_of" do
17
+ it "stores association cache" do
18
+ subject.fetch_cache_of(:has_many_cacheables) { 'cache' }
19
+ subject.fetch_cache_of(:has_many_cacheables).should == 'cache'
20
+ end
97
21
 
98
- it "stores association cache" do
99
- subject.fetch_cache_of(:has_many_cacheables) { 'cache' }
100
- cached_result = subject.fetch_cache_of(:has_many_cacheables) { 'fresh cache' }
101
- cached_result.should eql('cache')
102
- end
22
+ context "timestamps" do
23
+ it "works with timestamps" do
24
+ subject.should_receive(:cache_key_of).with(:anything, hash_including(:timestamp => :dynamic_timestamp)).and_return "returned stamp"
25
+ Rails.cache.should_receive(:fetch).with("returned stamp", :expires_in => nil).once
26
+ subject.fetch_cache_of(:anything, :timestamp => :dynamic_timestamp)
27
+ end
103
28
 
104
- describe "defines timestamp" do
29
+ it "calls for instance methods" do
30
+ subject.should_receive(:execute_timestamp).once
31
+ subject.fetch_cache_of(:anything, :timestamp => :dynamic_timestamp)
32
+ end
105
33
 
106
- context "dynamic" do
107
- it "works" do
108
- subject.stub!(:execute_timestamp).and_return(1)
109
- subject.fetch_cache_of(:something, :timestamp => :dynamic_timestamp) { "cache" }
110
- sleep(2)
111
- subject.fetch_cache_of(:something, :timestamp => :dynamic_timestamp) { "foo" }.should eql("cache")
112
- subject.stub!(:execute_timestamp).and_return(2)
113
- sleep(2)
114
- subject.fetch_cache_of(:something, :timestamp => :dynamic_timestamp) { "fresh cache" }.should eql("fresh cache")
34
+ it "passes expires_in param" do
35
+ Rails.cache.should_receive(:fetch).with(anything(), :expires_in => 10.minutes).once
36
+ subject.fetch_cache_of(:anything, :expires_in => 10.minutes)
115
37
  end
116
- end
117
38
 
118
- context "static" do
119
- it "works" do
120
- sleep(2)
121
- subject.fetch_cache_of(:something, :timestamp => :static_timestamp) { "cache" }
122
- sleep(2)
123
- subject.fetch_cache_of(:something, :timestamp => :static_timestamp) { "foo" }.should eql("cache")
39
+ it "passes expires_at param" do
40
+ Time.stub_chain("zone.now").and_return(Time.parse('01/01/01'))
41
+ Rails.cache.should_receive(:fetch).with(anything(), :expires_in => 10.minutes).once
42
+ subject.fetch_cache_of(:anything, :expires_at => 10.minutes.from_now)
124
43
  end
125
44
  end
126
45
  end
127
46
 
128
- context "deletes cache" do
129
-
47
+ context "resets cache" do
130
48
  describe "#delete_all_caches" do
131
49
  it "removes all caches using map" do
132
- subject.fetch_cache_of(:polymorphics) { 'cache' }
133
- subject.fetch_cache_of(:dependent_cache) { 'cache' }
134
-
50
+ subject.should_receive(:delete_cache_of).with(:polymorphics).once
51
+ subject.should_receive(:delete_cache_of).with(:child_cachers).once
52
+ subject.should_receive(:delete_cache_of).with(:has_many_cacheables).once
53
+ subject.should_receive(:delete_cache_of).with(:dependent_cache).once
54
+ subject.should_receive(:delete_cache_of).with(:has_many_through_cacheables).once
55
+ subject.should_receive(:delete_cache_of).with(:has_and_belongs_to_many_cacheables).once
135
56
  subject.delete_all_caches
136
-
137
- subject.fetch_cache_of(:polymorphics) { 'new cache' }.should == 'new cache'
138
- subject.fetch_cache_of(:dependent_cache) { 'new cache' }.should == 'new cache'
139
57
  end
140
58
  end
141
59
 
142
- context "of polymorphic associations" do
143
- it "works" do
144
- cached_result = subject.fetch_cache_of(:polymorphics) { 'cache' }
145
- subject.polymorphics.create
146
- cached_result = subject.fetch_cache_of(:polymorphics) { 'new cache' }
147
-
148
- cached_result.should eql('new cache')
60
+ describe "#delete_cache_of" do
61
+ it "resets cache" do
62
+ subject.fetch_cache_of(:anything) { 'cache' }
63
+ subject.delete_cache_of :anything
64
+ subject.fetch_cache_of(:anything).should be_nil
149
65
  end
150
- end
151
66
 
152
- context "of paginated content" do
153
- before :each do
67
+ it "resets cache by map" do
68
+ subject.fetch_cache_of(:dependent_cache) { 'cache' }
154
69
  subject.delete_cache_of :has_many_cacheables
70
+ subject.fetch_cache_of(:dependent_cache).should be_nil
155
71
  end
156
72
 
157
- it "works" do
158
- subject.fetch_cache_of(:has_many_cacheables, :page => 1) { 'page 1' }.should eql('page 1')
159
- subject.fetch_cache_of(:has_many_cacheables, :page => 2) { 'page 2' }.should eql('page 2')
160
- subject.delete_cache_of(:has_many_cacheables)
161
- subject.fetch_cache_of(:has_many_cacheables, :page => 1) { 'fresh page 1' }.should eql('fresh page 1')
162
- subject.fetch_cache_of(:has_many_cacheables, :page => 2) { 'fresh page 2' }.should eql('fresh page 2')
163
- end
164
- end
73
+ context "callbacks" do
74
+ context "on polymorphic associations" do
75
+ it "resets cache on add new item to associated collection" do
76
+ subject.fetch_cache_of(:polymorphics) { 'cache' }
77
+ subject.polymorphics.create
78
+ subject.fetch_cache_of(:polymorphics).should be_nil
79
+ end
80
+ end
165
81
 
166
- context "of self-join association" do
167
- it "works" do
168
- subject.fetch_cache_of(:child_cachers) { 'cache' }
169
- child = Cacher.create(:parent_id => subject.id)
170
- cached_result = subject.fetch_cache_of(:child_cachers) { 'new cache' }
82
+ context "on self-join associations" do
83
+ it "resets cache on add new item to associated collection" do
84
+ subject.fetch_cache_of(:child_cachers) { 'cache' }
85
+ Cacher.create(:parent_id => subject.id)
86
+ subject.fetch_cache_of(:child_cachers).should be_nil
87
+ end
88
+ end
171
89
 
172
- cached_result.should eql('new cache')
173
- end
174
- end
90
+ context "on has_many associations" do
91
+ let(:new_entry) { HasManyCacheable.create }
175
92
 
176
- context "of any member on has_many" do
177
- before :each do
178
- @existing_entry = subject.has_many_cacheables.create
179
- @new_entry = HasManyCacheable.create
180
- subject.fetch_cache_of(:has_many_cacheables) { 'cache' }
181
- end
93
+ before :each do
94
+ @existing_entry = subject.has_many_cacheables.create
95
+ subject.fetch_cache_of(:has_many_cacheables) { 'cache' }
96
+ end
182
97
 
183
- after :each do
184
- cached_result = subject.fetch_cache_of(:has_many_cacheables) { 'fresh cache' }
185
- cached_result.should eql('fresh cache')
186
- subject.delete_cache_of(:has_many_cacheables)
98
+ after :each do
99
+ subject.fetch_cache_of(:has_many_cacheables).should be_nil
100
+ subject.delete_cache_of(:has_many_cacheables)
101
+ end
102
+
103
+ it("on update entry in collection") { @existing_entry.save }
104
+ it("on add new entry in collection") { subject.has_many_cacheables << new_entry }
105
+ it("on destroy intem in collection") { subject.has_many_cacheables.destroy @existing_entry }
106
+ it("on destroy intem") { HasManyCacheable.destroy @existing_entry }
107
+ end
187
108
  end
188
109
 
189
- it("works") { subject.delete_cache_of(:has_many_cacheables) }
190
- it("on update entry in collection") { @existing_entry.save }
191
- it("on add new entry in collection") { subject.has_many_cacheables << @new_entry }
192
- it("on destroy intem in collection") { subject.has_many_cacheables.destroy @existing_entry }
193
- it("on destroy intem") { HasManyCacheable.destroy @existing_entry }
110
+ context "has_many :through associations" do
111
+ let(:new_entry) { HasManyThroughCacheable.create }
194
112
 
195
- context "by chain" do
196
- it "works" do
197
- subject.fetch_cache_of(:dependent_cache) { 'cache' }
198
- subject.has_many_cacheables.create
199
- subject.fetch_cache_of(:dependent_cache) { 'fresh cache' }.should eql('fresh cache')
113
+ before :each do
114
+ @existing_entry = subject.has_many_through_cacheables.create
115
+ subject.fetch_cache_of(:has_many_through_cacheables) { 'cache' }
200
116
  end
201
- end
202
- end
203
117
 
204
- context "of any member on has_many :through" do
205
- before :each do
206
- @existing_entry = subject.has_many_through_cacheables.create
207
- @new_entry = HasManyThroughCacheable.create
208
- subject.fetch_cache_of(:has_many_through_cacheables) { 'cache' }
209
- end
118
+ after :each do
119
+ subject.fetch_cache_of(:has_many_through_cacheables).should be_nil
120
+ subject.delete_cache_of(:has_many_through_cacheables)
121
+ end
210
122
 
211
- after :each do
212
- cached_result = subject.fetch_cache_of(:has_many_through_cacheables) { 'fresh cache' }
213
- cached_result.should eql('fresh cache')
214
- subject.delete_cache_of(:has_many_through_cacheables)
123
+ it("on update entry in collection") { @existing_entry.save }
124
+ it("on add new entry in collection") { subject.has_many_through_cacheables << new_entry }
125
+ it("on destroy intem in collection") { subject.has_many_through_cacheables.destroy @existing_entry }
126
+ it("on destroy intem") { HasManyThroughCacheable.destroy @existing_entry }
215
127
  end
216
128
 
217
- it("works") { subject.delete_cache_of(:has_many_through_cacheables) }
218
- it("on update entry in collection") { @existing_entry.save }
219
- it("on add new entry in collection") { subject.has_many_through_cacheables << @new_entry }
220
- it("on destroy intem in collection") { subject.has_many_through_cacheables.destroy @existing_entry }
221
- it("on destroy intem") { HasManyThroughCacheable.destroy @existing_entry }
129
+ context "has_and_belongs_to_many associations" do
130
+ let(:new_entry) { HasAndBelongsToManyCacheable.create }
131
+ before :each do
132
+ @existing_entry = subject.has_and_belongs_to_many_cacheables.create
133
+ subject.fetch_cache_of(:has_and_belongs_to_many_cacheables) { 'cache' }
134
+ end
222
135
 
223
- context "by chain" do
224
- it "works" do
225
- subject.fetch_cache_of(:dependent_cache) { 'cache' }
226
- subject.has_many_through_cacheables.create
227
- subject.fetch_cache_of(:dependent_cache) { 'fresh cache' }.should eql('fresh cache')
136
+ after :each do
137
+ subject.fetch_cache_of(:has_and_belongs_to_many_cacheables).should be_nil
138
+ subject.delete_cache_of(:has_and_belongs_to_many_cacheables)
228
139
  end
229
- end
230
- end
231
140
 
232
- context "of any member on has_and_belongs_to_many" do
233
- before :each do
234
- @existing_entry = subject.has_and_belongs_to_many_cacheables.create
235
- @new_entry = HasAndBelongsToManyCacheable.create
236
- subject.fetch_cache_of(:has_and_belongs_to_many_cacheables) { 'cache' }
141
+ it("on update entry in collection") { @existing_entry.save }
142
+ it("on add new entry in collection") { subject.has_and_belongs_to_many_cacheables << new_entry }
143
+ it("on destroy intem in collection") { subject.has_and_belongs_to_many_cacheables.destroy @existing_entry }
144
+ it("on destroy intem") { HasAndBelongsToManyCacheable.destroy @existing_entry }
237
145
  end
238
146
 
239
- after :each do
240
- cached_result = subject.fetch_cache_of(:has_and_belongs_to_many_cacheables) { 'fresh cache' }
241
- cached_result.should eql('fresh cache')
242
- subject.delete_cache_of(:has_and_belongs_to_many_cacheables)
243
- end
147
+ context "paginated content" do
148
+ it "works" do
149
+ subject.delete_cache_of :has_many_cacheables
150
+ subject.fetch_cache_of(:has_many_cacheables, :page => 1) { 'page 1' }.should eql('page 1')
151
+ subject.fetch_cache_of(:has_many_cacheables, :page => 2) { 'page 2' }.should eql('page 2')
244
152
 
245
- it("works") { subject.delete_cache_of(:has_and_belongs_to_many_cacheables) }
246
- it("on update entry in collection") { @existing_entry.save }
247
- it("on add new entry in collection") { subject.has_and_belongs_to_many_cacheables << @new_entry }
248
- it("on destroy intem in collection") { subject.has_and_belongs_to_many_cacheables.destroy @existing_entry }
249
- it("on destroy intem") { HasAndBelongsToManyCacheable.destroy @existing_entry }
153
+ subject.delete_cache_of(:has_many_cacheables)
250
154
 
251
- context "by chain" do
252
- it "works" do
253
- subject.fetch_cache_of(:dependent_cache) { 'cache' }
254
- subject.has_and_belongs_to_many_cacheables.create
255
- subject.fetch_cache_of(:dependent_cache) { 'fresh cache' }.should eql('fresh cache')
155
+ subject.fetch_cache_of(:has_many_cacheables, :page => 1).should be_nil
156
+ subject.fetch_cache_of(:has_many_cacheables, :page => 2).should be_nil
256
157
  end
257
158
  end
258
159
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cache-machine
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 9
10
- version: 0.1.9
9
+ - 10
10
+ version: 0.1.10
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sergei Zinin
@@ -16,7 +16,8 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-10-13 00:00:00 Z
19
+ date: 2011-10-22 00:00:00 +08:00
20
+ default_executable:
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
22
23
  name: rails
@@ -47,9 +48,23 @@ dependencies:
47
48
  type: :runtime
48
49
  version_requirements: *id002
49
50
  - !ruby/object:Gem::Dependency
50
- name: bundler
51
+ name: activesupport
51
52
  prerelease: false
52
53
  requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :runtime
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: bundler
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
53
68
  none: false
54
69
  requirements:
55
70
  - - ~>
@@ -61,11 +76,11 @@ dependencies:
61
76
  - 0
62
77
  version: 1.0.0
63
78
  type: :development
64
- version_requirements: *id003
79
+ version_requirements: *id004
65
80
  - !ruby/object:Gem::Dependency
66
81
  name: jeweler
67
82
  prerelease: false
68
- requirement: &id004 !ruby/object:Gem::Requirement
83
+ requirement: &id005 !ruby/object:Gem::Requirement
69
84
  none: false
70
85
  requirements:
71
86
  - - ~>
@@ -77,7 +92,7 @@ dependencies:
77
92
  - 2
78
93
  version: 1.6.2
79
94
  type: :development
80
- version_requirements: *id004
95
+ version_requirements: *id005
81
96
  description: A Ruby on Rails framework to support cache management based on explicitely modeled caching dependencies.
82
97
  email: kgoslar@partyearth.com
83
98
  executables: []
@@ -103,8 +118,10 @@ files:
103
118
  - lib/cache_machine/helpers/cache_helper.rb
104
119
  - lib/cache_machine/logger.rb
105
120
  - lib/cache_machine/railtie.rb
121
+ - spec/fixtures.rb
106
122
  - spec/lib/cache_machine_spec.rb
107
123
  - spec/spec_helper.rb
124
+ has_rdoc: true
108
125
  homepage: http://github.com/partyearth/cache-machine
109
126
  licenses:
110
127
  - MIT
@@ -134,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
151
  requirements: []
135
152
 
136
153
  rubyforge_project:
137
- rubygems_version: 1.8.10
154
+ rubygems_version: 1.5.0
138
155
  signing_key:
139
156
  specification_version: 3
140
157
  summary: A Ruby on Rails framework to support cache management based on explicitely modeled caching dependencies.