cache-machine 0.1.9 → 0.1.10

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/.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.