arid_cache 0.2.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -265,7 +265,7 @@ For example, assume we have a <tt>named_scope :active</tt> on <tt>User</tt> whic
265
265
 
266
266
  == Compatibility
267
267
 
268
- Tested on Ruby 1.8.6 and 1.8.7. Should be compatible with 1.9.
268
+ Tested on Ruby 1.8.6, 1.8.7 and 1.9.1.
269
269
 
270
270
  For Ruby < 1.8.7 you probably want to include the following to extend the Array class with a <tt>count</tt> method. Otherwise your <tt>cached_<key>_count</tt> calls probably won't work:
271
271
 
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ begin
8
8
  gem.summary = %Q{Automates efficient caching of your ActiveRecord collections, gives you counts for free and supports pagination.}
9
9
  gem.description = <<-END.gsub(/^\s+/, '')
10
10
  AridCache makes caching easy and effective. AridCache supports caching on all your model named scopes, class methods and instance methods right out of the box. AridCache prevents caching logic from cluttering your models and clarifies your logic by making explicit calls to cached result sets.
11
-
11
+
12
12
  AridCache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
13
13
  END
14
14
  gem.email = "kjvarga@gmail.com"
@@ -30,13 +30,13 @@ end
30
30
  # spec.libs << 'lib' << 'spec'
31
31
  # spec.spec_files = FileList['spec/**/*_spec.rb']
32
32
  # end
33
- #
33
+ #
34
34
  # Spec::Rake::SpecTask.new(:rcov) do |spec|
35
35
  # spec.libs << 'lib' << 'spec'
36
36
  # spec.pattern = 'spec/**/*_spec.rb'
37
37
  # spec.rcov = true
38
38
  # end
39
- #
39
+ #
40
40
  # task :spec => :check_dependencies
41
41
 
42
42
  require 'rake/testtask'
@@ -58,3 +58,11 @@ Rake::RDocTask.new do |rdoc|
58
58
  rdoc.rdoc_files.include('README*')
59
59
  rdoc.rdoc_files.include('lib/**/*.rb')
60
60
  end
61
+
62
+ desc "Release a new patch version"
63
+ task :release_new_version do
64
+ Rake::Task['version:bump:patch'].invoke
65
+ Rake::Task['github:release'].invoke
66
+ Rake::Task['git:release'].invoke
67
+ Rake::Task['gemcutter:release'].invoke
68
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.7
1
+ 1.0.0
data/arid_cache.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{arid_cache}
8
- s.version = "0.2.7"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Karl Varga"]
12
- s.date = %q{2010-01-21}
12
+ s.date = %q{2010-07-05}
13
13
  s.description = %q{AridCache makes caching easy and effective. AridCache supports caching on all your model named scopes, class methods and instance methods right out of the box. AridCache prevents caching logic from cluttering your models and clarifies your logic by making explicit calls to cached result sets.
14
14
  AridCache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
15
15
  }
@@ -44,6 +44,7 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
44
44
  "test/lib/blueprint.rb",
45
45
  "test/log/.gitignore",
46
46
  "test/models/company.rb",
47
+ "test/models/empty_user_relation.rb",
47
48
  "test/models/user.rb",
48
49
  "test/test_helper.rb"
49
50
  ]
@@ -61,6 +62,7 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
61
62
  "test/lib/active_support/cache/file_store_extras.rb",
62
63
  "test/lib/blueprint.rb",
63
64
  "test/models/company.rb",
65
+ "test/models/empty_user_relation.rb",
64
66
  "test/models/user.rb",
65
67
  "test/test_helper.rb"
66
68
  ]
@@ -1,25 +1,25 @@
1
1
  module AridCache
2
2
  class CacheProxy
3
3
  attr_accessor :object, :key, :opts, :blueprint, :cached, :cache_key, :block, :records, :combined_options, :klass
4
-
4
+
5
5
  # AridCache::CacheProxy::Result
6
6
  #
7
7
  # This struct is stored in the cache and stores information we need
8
8
  # to re-query for results.
9
9
  Result = Struct.new(:ids, :klass, :count) do
10
-
10
+
11
11
  def has_count?
12
12
  !count.nil?
13
13
  end
14
-
14
+
15
15
  def has_ids?
16
16
  !ids.nil?
17
17
  end
18
-
18
+
19
19
  def klass=(value)
20
20
  self['klass'] = value.is_a?(Class) ? value.name : value.class.name
21
21
  end
22
-
22
+
23
23
  def klass
24
24
  self['klass'].constantize unless self['klass'].nil?
25
25
  end
@@ -27,17 +27,17 @@ module AridCache
27
27
 
28
28
  #
29
29
  # Managing your caches
30
- #
31
-
30
+ #
31
+
32
32
  def self.clear_caches
33
33
  Rails.cache.delete_matched(%r[arid-cache-.*])
34
- end
35
-
34
+ end
35
+
36
36
  def self.clear_class_caches(object)
37
37
  key = (object.is_a?(Class) ? object : object.class).name.downcase + '-'
38
38
  Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
39
- end
40
-
39
+ end
40
+
41
41
  def self.clear_instance_caches(object)
42
42
  key = (object.is_a?(Class) ? object : object.class).name.pluralize.downcase
43
43
  Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
@@ -46,11 +46,11 @@ module AridCache
46
46
  #
47
47
  # Fetching results
48
48
  #
49
-
49
+
50
50
  def self.fetch_count(object, key, opts={}, &block)
51
51
  CacheProxy.new(object, key, opts, &block).fetch_count
52
52
  end
53
-
53
+
54
54
  def self.fetch(object, key, opts={}, &block)
55
55
  CacheProxy.new(object, key, opts, &block).fetch
56
56
  end
@@ -62,14 +62,14 @@ module AridCache
62
62
  self.blueprint = AridCache.store.find(object, key)
63
63
  self.block = block
64
64
  self.records = nil
65
-
65
+
66
66
  # The options from the blueprint merged with the options for this call
67
67
  self.combined_options = self.blueprint.nil? ? self.opts : self.blueprint.opts.merge(self.opts)
68
-
68
+
69
69
  self.cache_key = object.arid_cache_key(key, opts_for_cache_key)
70
70
  self.cached = Rails.cache.read(cache_key, opts_for_cache)
71
71
  end
72
-
72
+
73
73
  def fetch_count
74
74
  if refresh_cache?
75
75
  execute_count
@@ -83,13 +83,16 @@ module AridCache
83
83
  cached # what else can we do? return it
84
84
  end
85
85
  end
86
-
86
+
87
87
  def fetch
88
88
  if refresh_cache?
89
89
  execute_find
90
90
  elsif cached.is_a?(AridCache::CacheProxy::Result)
91
91
  if cached.has_ids?
92
+
93
+ # Infer the class of the objects we are returning
92
94
  self.klass = cached.klass || object_base_class
95
+
93
96
  fetch_from_cache
94
97
  else
95
98
  execute_find
@@ -98,59 +101,90 @@ module AridCache
98
101
  cached # some base type, return it
99
102
  end
100
103
  end
101
-
104
+
102
105
  private
103
106
 
107
+ # Return a list of records from the database using the ids from
108
+ # the cached Result.
109
+ #
110
+ # The result is paginated if the :page option is preset, otherwise
111
+ # a regular list of ActiveRecord results is returned.
112
+ #
113
+ # If no :order is specified, the current ordering of the ids is
114
+ # preserved with some fancy SQL.
104
115
  def fetch_from_cache
105
116
  if paginate?
106
- fetch_and_paginate
107
- elsif limit_or_offset?
108
- fetch_and_limit
109
- else
110
- klass.find_all_by_id(cached.ids, opts_for_find)
111
- end
112
- end
113
-
114
- def fetch_and_paginate
115
- if combined_options.include?(:order) # order and paginate in the database
116
- klass.paginate(cached.ids, { :total_entries => cached.ids.size }.merge(opts_for_find.merge(opts_for_paginate)))
117
- else # paginate in memory
118
- paged_ids = cached.ids.paginate(opts_for_paginate)
119
- paged_ids.replace(klass.find_all_by_id(paged_ids, opts_for_find(paged_ids)))
120
- end
121
- end
122
-
123
- def fetch_and_limit
124
- if combined_options.include?(:order)
117
+
118
+ # Return a paginated collection
119
+
120
+ if cached.ids.empty?
121
+
122
+ # No ids, return an empty WillPaginate result
123
+
124
+ [].paginate(opts_for_paginate)
125
+
126
+ elsif combined_options.include?(:order)
127
+
128
+ # An order has been specified. We have to go to the database
129
+ # and paginate there because the contents of the requested
130
+ # page will be different.
131
+
132
+ klass.paginate(cached.ids, { :total_entries => cached.ids.size }.merge(opts_for_find.merge(opts_for_paginate)))
133
+
134
+ else
135
+
136
+ # Order is unchanged. We can paginate in memory and only select
137
+ # those ids that we actually need. This is the most efficient.
138
+
139
+ paged_ids = cached.ids.paginate(opts_for_paginate)
140
+ paged_ids.replace(klass.find_all_by_id(paged_ids, opts_for_find(paged_ids)))
141
+
142
+ end
143
+
144
+ elsif cached.ids.empty?
145
+
146
+ # We are returning a regular (non-paginated) result.
147
+ # If we don't have any ids, that's an empty result
148
+ # in any language.
149
+
150
+ []
151
+
152
+ elsif combined_options.include?(:order)
153
+
154
+ # An order has been specified, so use it.
155
+
125
156
  klass.find_all_by_id(cached.ids, opts_for_find)
157
+
126
158
  else
159
+
160
+ # No order has been specified, so we have to maintain
161
+ # the current order. We do this by passing some extra
162
+ # SQL which orders by the current array ordering.
163
+
127
164
  offset, limit = combined_options.delete(:offset) || 0, combined_options.delete(:limit) || cached.count
128
165
  ids = cached.ids[offset, limit]
166
+
129
167
  klass.find_all_by_id(ids, opts_for_find(ids))
130
- end
168
+ end
131
169
  end
132
-
170
+
133
171
  def paginate?
134
172
  combined_options.include?(:page)
135
173
  end
136
-
137
- def limit_or_offset?
138
- combined_options.include?(:limit) || combined_options.include?(:offset)
139
- end
140
-
174
+
141
175
  def refresh_cache?
142
176
  cached.nil? || opts[:force]
143
177
  end
144
-
178
+
145
179
  def get_records
146
180
  block = self.block || (blueprint && blueprint.proc)
147
181
  self.records = block.nil? ? object.instance_eval(key) : object.instance_eval(&block)
148
182
  end
149
-
183
+
150
184
  def execute_find
151
- get_records
185
+ get_records
152
186
  cached = AridCache::CacheProxy::Result.new
153
-
187
+
154
188
  if !records.is_a?(Enumerable) || (!records.empty? && !records.first.is_a?(::ActiveRecord::Base))
155
189
  cached = records # some base type, cache it as itself
156
190
  else
@@ -165,7 +199,7 @@ module AridCache
165
199
  end
166
200
  end
167
201
  Rails.cache.write(cache_key, cached, opts_for_cache)
168
-
202
+
169
203
  self.cached = cached
170
204
  return_records(records)
171
205
  end
@@ -187,20 +221,20 @@ module AridCache
187
221
  # If you do want a specialized, modified, or subset of the collection it's best
188
222
  # to define it in a block and have a new cache for it:
189
223
  #
190
- # model.my_special_collection { the_collection(:order => 'new order', :limit => 10) }
224
+ # model.my_special_collection { the_collection(:order => 'new order', :limit => 10) }
191
225
  def return_records(records)
192
226
  if opts.include?(:page)
193
227
  records = records.respond_to?(:to_a) ? records.to_a : records
194
228
  records.respond_to?(:paginate) ? records.paginate(opts_for_paginate) : records
195
229
  elsif opts.include?(:limit)
196
230
  records = records.respond_to?(:to_a) ? records.to_a : records
197
- offset = opts[:offset] || 0
231
+ offset = opts[:offset] || 0
198
232
  records[offset, opts[:limit]]
199
233
  else
200
234
  records
201
- end
235
+ end
202
236
  end
203
-
237
+
204
238
  def execute_count
205
239
  get_records
206
240
  cached = AridCache::CacheProxy::Result.new
@@ -224,14 +258,14 @@ module AridCache
224
258
  else
225
259
  cached = records # some base type, cache it as itself
226
260
  end
227
-
261
+
228
262
  Rails.cache.write(cache_key, cached, opts_for_cache)
229
263
  self.cached = cached
230
264
  cached.respond_to?(:count) ? cached.count : cached
231
265
  end
232
-
266
+
233
267
  OPTIONS_FOR_PAGINATE = [:page, :per_page, :total_entries, :finder]
234
-
268
+
235
269
  # Filter options for paginate, if *klass* is set, we get the :per_page value from it.
236
270
  def opts_for_paginate
237
271
  paginate_opts = combined_options.reject { |k,v| !OPTIONS_FOR_PAGINATE.include?(k) }
@@ -239,11 +273,11 @@ module AridCache
239
273
  paginate_opts[:per_page] = klass.per_page if klass && !paginate_opts.include?(:per_page)
240
274
  paginate_opts
241
275
  end
242
-
276
+
243
277
  OPTIONS_FOR_FIND = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock ]
244
278
 
245
279
  # Preserve the original order of the results if no :order option is specified.
246
- #
280
+ #
247
281
  # @arg ids array of ids to order by unless an :order option is specified. If not
248
282
  # specified, cached.ids is used.
249
283
  def opts_for_find(ids=nil)
@@ -252,40 +286,40 @@ module AridCache
252
286
  find_opts[:order] = preserve_order(ids) unless find_opts.include?(:order)
253
287
  find_opts
254
288
  end
255
-
289
+
256
290
  OPTIONS_FOR_CACHE = [ :expires_in ]
257
-
291
+
258
292
  def opts_for_cache
259
293
  combined_options.reject { |k,v| !OPTIONS_FOR_CACHE.include?(k) }
260
294
  end
261
295
 
262
296
  OPTIONS_FOR_CACHE_KEY = [ :auto_expire ]
263
-
297
+
264
298
  def opts_for_cache_key
265
299
  combined_options.reject { |k,v| !OPTIONS_FOR_CACHE_KEY.include?(k) }
266
300
  end
267
-
301
+
268
302
  def object_base_class #:nodoc:
269
303
  object.is_a?(Class) ? object : object.class
270
304
  end
271
305
 
272
306
  # Generate an ORDER BY clause that preserves the ordering of the ids in *ids*.
273
307
  #
274
- # The method we use depends on the database adapter because only MySQL
308
+ # The method we use depends on the database adapter because only MySQL
275
309
  # supports the ORDER BY FIELD() function. For other databases we use
276
310
  # a CASE statement.
277
- #
311
+ #
278
312
  # TODO: is it quicker to sort in memory?
279
313
  def preserve_order(ids)
280
314
  if ids.empty?
281
315
  nil
282
316
  elsif ::ActiveRecord::Base.is_mysql_adapter?
283
- "FIELD(id,#{ids.join(',')})"
317
+ "FIELD(id,#{ids.join(',')})"
284
318
  else
285
319
  order = ''
286
320
  ids.each_index { |i| order << "WHEN id=#{ids[i]} THEN #{i+1} " }
287
321
  "CASE " + order + " END"
288
322
  end
289
- end
323
+ end
290
324
  end
291
325
  end
@@ -10,50 +10,50 @@ class AridCacheTest < ActiveSupport::TestCase
10
10
  assert_instance_of AridCache::Store, AridCache.store
11
11
  assert_same AridCache::CacheProxy, AridCache.cache
12
12
  end
13
-
13
+
14
14
  test "should respond to methods" do
15
15
  assert User.respond_to?(:clear_caches)
16
- assert @user.respond_to?(:clear_caches)
16
+ assert @user.respond_to?(:clear_caches)
17
17
  assert_instance_of AridCache::Store, AridCache.store
18
18
  assert_same AridCache::CacheProxy, AridCache.cache
19
19
  end
20
-
20
+
21
21
  test "should not clobber model methods" do
22
22
  assert_respond_to @user, :name
23
23
  assert_respond_to Company.first, :name
24
24
  assert_nothing_raised { @user.name }
25
25
  assert_nothing_raised { Company.first.name }
26
26
  end
27
-
27
+
28
28
  test "should not clobber method_missing" do
29
29
  assert_nothing_raised { @user.is_high? }
30
- assert @user.is_high?
30
+ assert @user.is_high?
31
31
  end
32
-
32
+
33
33
  test "should not clobber respond_to?" do
34
34
  assert @user.respond_to?(:respond_not_overridden)
35
35
  end
36
-
36
+
37
37
  test "should raise an error on invalid dynamic caches" do
38
38
  assert_raises ArgumentError do
39
39
  @user.cached_invalid_companies
40
40
  end
41
41
  end
42
-
42
+
43
43
  test "should create dynamic caches given valid arguments" do
44
44
  assert_nothing_raised { @user.cached_companies }
45
45
  end
46
-
46
+
47
47
  test "counts queries correctly" do
48
48
  assert_queries(1) { User.all }
49
49
  end
50
-
50
+
51
51
  test "returns valid results" do
52
52
  @one = @user.cached_companies
53
53
  assert_equal @user.companies, @one
54
54
  assert_equal @user.companies.count, @one.size
55
55
  end
56
-
56
+
57
57
  test "paginates results" do
58
58
  results = @user.cached_companies(:page => 1, :per_page => 3, :order => 'name desc')
59
59
  assert_kind_of WillPaginate::Collection, results
@@ -61,34 +61,34 @@ class AridCacheTest < ActiveSupport::TestCase
61
61
  assert_equal @user.companies.count, results.total_entries
62
62
  assert_equal 1, results.current_page
63
63
  end
64
-
64
+
65
65
  test "should work for different pages" do
66
66
  results = @user.cached_companies(:page => 2, :per_page => 3)
67
67
  assert_kind_of WillPaginate::Collection, results
68
68
  assert results.size <= 3
69
- assert_equal @user.companies.count, results.total_entries
70
- assert_equal 2, results.current_page
71
- end
72
-
69
+ assert_equal @user.companies.count, results.total_entries
70
+ assert_equal 2, results.current_page
71
+ end
72
+
73
73
  test "ignores random parameters" do
74
74
  result = @user.cached_companies(:invalid => :params, 'random' => 'values', :user_id => 3)
75
75
  assert_equal @user.companies, result
76
76
  end
77
-
77
+
78
78
  test "passes on options to find" do
79
79
  actual = @user.cached_companies(:order => 'users.id DESC')
80
80
  expected = @user.companies
81
81
  assert_equal expected, actual
82
82
  assert_equal expected.first, actual.first
83
83
  end
84
-
84
+
85
85
  test "caches the count when it gets records" do
86
86
  assert_queries(1) do
87
87
  @user.cached_companies
88
88
  @user.cached_companies_count
89
89
  end
90
90
  end
91
-
91
+
92
92
  test "gets the count only if it's requested first" do
93
93
  count = @user.companies.count
94
94
  assert_queries(1) do
@@ -100,7 +100,7 @@ class AridCacheTest < ActiveSupport::TestCase
100
100
  assert_equal count, @user.cached_companies_count
101
101
  end
102
102
  end
103
-
103
+
104
104
  test "applies limit and offset" do
105
105
  result = @user.cached_limit_companies do
106
106
  companies
@@ -108,22 +108,22 @@ class AridCacheTest < ActiveSupport::TestCase
108
108
  limit_two_result = @user.cached_limit_companies(:limit => 2)
109
109
  assert_equal 2, limit_two_result.size
110
110
  assert_equal result[0, 2], limit_two_result
111
-
111
+
112
112
  limit_three_result = @user.cached_limit_companies(:limit => 3)
113
113
  assert_equal 3, limit_three_result.size
114
114
  assert_equal result[0, 3], limit_three_result
115
-
115
+
116
116
  limit_two_offset_one_result = @user.cached_limit_companies(:limit => 2, :offset => 1)
117
117
  assert_equal 2, limit_two_offset_one_result.size
118
118
  assert_equal result[1, 2], limit_two_offset_one_result
119
-
119
+
120
120
  offset_one_result = @user.cached_limit_companies(:offset => 1)
121
121
  assert_equal result.size-1, offset_one_result.size
122
122
  assert_equal result[1, result.size], offset_one_result
123
-
123
+
124
124
  assert_equal @user.companies.all(:limit => 2, :offset => 1), @user.cached_limit_companies(:limit => 2, :offset => 1)
125
- assert_equal @user.companies.size, @user.cached_limit_companies.size
126
-
125
+ assert_equal @user.companies.size, @user.cached_limit_companies.size
126
+
127
127
  # Careful of this Rails bug: https://rails.lighthouseapp.com/projects/8994/tickets/1349-named-scope-with-group-by-bug
128
128
  User.cached_successful_limit_companies do
129
129
  User.successful.all
@@ -131,27 +131,27 @@ class AridCacheTest < ActiveSupport::TestCase
131
131
  assert_equal 2, User.cached_successful_limit_companies(:limit => 2).size
132
132
  assert_equal 3, User.cached_successful_limit_companies(:limit => 3).size
133
133
  assert_equal User.successful.all(:limit => 2, :offset => 1), User.cached_successful_limit_companies(:limit => 2, :offset => 1)
134
- assert_equal User.successful.all.size, User.cached_successful_limit_companies.size
134
+ assert_equal User.successful.all.size, User.cached_successful_limit_companies.size
135
135
  end
136
-
136
+
137
137
  test "should requery the database for limits with order" do
138
138
  @user.cached_companies # prime the cache
139
139
  assert_equal @user.companies.find(:all, :limit => 3, :order => 'name DESC'), @user.cached_companies(:limit => 3, :order => 'name DESC')
140
140
  assert_equal @user.companies.find(:all, :limit => 3, :order => 'name ASC'), @user.cached_companies(:limit => 3, :order => 'name ASC')
141
141
  end
142
-
142
+
143
143
  test "should activate will paginate" do
144
144
  assert_nothing_raised do
145
145
  User.paginate(:page => 1)
146
146
  end
147
147
  end
148
-
148
+
149
149
  test "should requery the database for paginate with order" do
150
150
  @user.cached_companies # prime the cache
151
151
  assert_equal @user.companies.paginate(:page => 1, :per_page => 3, :order => 'name DESC'), @user.cached_companies(:page => 1, :per_page => 3, :order => 'name DESC')
152
152
  assert_equal @user.companies.paginate(:page => 1, :per_page => 3, :order => 'name ASC'), @user.cached_companies(:page => 1, :per_page => 3, :order => 'name ASC')
153
153
  end
154
-
154
+
155
155
  test "pagination should not result in an extra query" do
156
156
  assert_queries(1) do
157
157
  @user.cached_big_companies(:page => 1)
@@ -160,7 +160,7 @@ class AridCacheTest < ActiveSupport::TestCase
160
160
  User.cached_companies(:page => 1)
161
161
  end
162
162
  end
163
-
163
+
164
164
  test "should support a 'force' option" do
165
165
  # ActiveRecord caches the result of the proc, so we need to
166
166
  # use different instances of the user to test the force option.
@@ -177,7 +177,7 @@ class AridCacheTest < ActiveSupport::TestCase
177
177
  assert_equal size, uncached_user.cached_companies_count(:force => true)
178
178
  end
179
179
  end
180
-
180
+
181
181
  test "should handle various different model instances" do
182
182
  one = User.first
183
183
  two = User.first :offset => 1
@@ -185,13 +185,13 @@ class AridCacheTest < ActiveSupport::TestCase
185
185
  assert_equal one.companies, one.cached_companies
186
186
  assert_equal two.companies, two.cached_companies
187
187
  end
188
-
188
+
189
189
  test "should handle arrays of non-active record instances" do
190
190
  assert_equal @user.pet_names, @user.cached_pet_names
191
191
  assert_equal @user.pet_names, @user.cached_pet_names
192
192
  assert_equal @user.pet_names.count, @user.cached_pet_names_count
193
193
  end
194
-
194
+
195
195
  test "should empty the Rails cache" do
196
196
  @user.cached_companies
197
197
  User.cached_companies
@@ -199,9 +199,9 @@ class AridCacheTest < ActiveSupport::TestCase
199
199
  assert Rails.cache.exist?(User.arid_cache_key('companies'))
200
200
  User.clear_caches
201
201
  assert !Rails.cache.exist?(@user.arid_cache_key('companies'))
202
- assert !Rails.cache.exist?(User.arid_cache_key('companies'))
202
+ assert !Rails.cache.exist?(User.arid_cache_key('companies'))
203
203
  end
204
-
204
+
205
205
  test "should support expiring caches" do
206
206
  # The first query should put the count in the cache. The second query
207
207
  # should read the count from the cache. The third query should
@@ -213,16 +213,16 @@ class AridCacheTest < ActiveSupport::TestCase
213
213
  User.cached_companies_count(:expires_in => 1.second)
214
214
  end
215
215
  end
216
-
216
+
217
217
  test "should support an auto-expire option" do
218
218
  assert_match %r[users/#{@user.id}-companies], @user.arid_cache_key('companies')
219
219
  assert_equal @user.arid_cache_key('companies'), @user.arid_cache_key('companies', :auto_expire => false)
220
220
  assert_match %r[users/#{@user.id}-\d{14}-companies], @user.arid_cache_key('companies', :auto_expire => true)
221
-
221
+
222
222
  # It doesn't apply to class caches, but shouldn't affect it either
223
223
  assert_equal User.arid_cache_key('companies'), User.arid_cache_key('companies', :auto_expire => true)
224
224
  end
225
-
225
+
226
226
  test "should turn off auto-expire by default" do
227
227
  assert_queries(2) do
228
228
  @user.cached_companies_count
@@ -230,7 +230,7 @@ class AridCacheTest < ActiveSupport::TestCase
230
230
  @user.cached_companies_count
231
231
  end
232
232
  end
233
-
233
+
234
234
  test "should reload auto-expired caches" do
235
235
  assert_queries(2) do
236
236
  @user.cached_companies_count(:auto_expire => true)
@@ -240,29 +240,29 @@ class AridCacheTest < ActiveSupport::TestCase
240
240
  @user.cached_companies_count(:auto_expire => true)
241
241
  end
242
242
  end
243
-
243
+
244
244
  test "should support configuring instance caches" do
245
245
  User.instance_caches { best_companies { companies } }
246
246
  assert_equal @user.companies, @user.cached_best_companies
247
247
  end
248
-
248
+
249
249
  test "instance caches should work on all instances of the class" do
250
250
  User.instance_caches { best_companies { companies } }
251
251
  assert_equal @user.cached_best_companies, User.first.cached_best_companies
252
252
  end
253
-
253
+
254
254
  test "should support configuring class caches" do
255
255
  User.class_caches { successful_users { successful } }
256
256
  assert_equal User.successful, User.cached_successful_users
257
257
  end
258
-
258
+
259
259
  test "should create valid store keys" do
260
260
  assert_equal 'user-key', AridCache.store.send(:class_store_key, User, 'key')
261
261
  assert_equal 'users-key', AridCache.store.send(:instance_store_key, User, 'key')
262
262
  assert_equal AridCache.store.send(:instance_store_key, User, 'key'), AridCache.store.send(:object_store_key, @user, 'key')
263
263
  assert_equal AridCache.store.send(:class_store_key, User, 'key'), AridCache.store.send(:object_store_key, User, 'key')
264
264
  end
265
-
265
+
266
266
  test "configuring caches should not perform any queries" do
267
267
  User.instance_caches do
268
268
  best_companies { companies }
@@ -271,7 +271,7 @@ class AridCacheTest < ActiveSupport::TestCase
271
271
  best_companies(:order => 'name DESC') { companies.find(:all, :order => 'name DESC') }
272
272
  end
273
273
  end
274
-
274
+
275
275
  test "should support options in the cache configuration" do
276
276
  User.instance_caches(:auto_expire => true) do
277
277
  expires_in = 1.second
@@ -289,9 +289,9 @@ class AridCacheTest < ActiveSupport::TestCase
289
289
  # Call it twice to ensure that on the second call the order is applied when retrieving the
290
290
  # records by id
291
291
  assert_equal User.successful.find(:all, :order => 'name DESC'), User.cached_most_successful
292
- assert_equal User.successful.find(:all, :order => 'name DESC'), User.cached_most_successful
292
+ assert_equal User.successful.find(:all, :order => 'name DESC'), User.cached_most_successful
293
293
  end
294
-
294
+
295
295
  test "should not raise an error if all cached ids cannot be found" do
296
296
  @user.cached_companies
297
297
  key = @user.arid_cache_key('companies')
@@ -301,7 +301,7 @@ class AridCacheTest < ActiveSupport::TestCase
301
301
  assert_nothing_raised { @user.cached_companies }
302
302
  assert_equal @user.cached_companies, @user.companies
303
303
  end
304
-
304
+
305
305
  test "should not raise an error if all cached ids cannot be found while paginating" do
306
306
  @user.cached_companies
307
307
  key = @user.arid_cache_key('companies')
@@ -311,7 +311,7 @@ class AridCacheTest < ActiveSupport::TestCase
311
311
  assert_nothing_raised { @user.cached_companies(:page => 1, :order => 'name DESC') }
312
312
  assert_equal @user.cached_companies(:page => 1, :order => 'name DESC'), @user.companies.paginate(:page => 1, :order => 'name DESC')
313
313
  end
314
-
314
+
315
315
  test "should handle empty collections" do
316
316
  @user.cached_empty_collection { [] }
317
317
  assert_nothing_raised { @user.cached_empty_collection }
@@ -337,12 +337,40 @@ class AridCacheTest < ActiveSupport::TestCase
337
337
  end
338
338
  assert_equal @user.companies.all(:order => 'name DESC'), AridCache.cache.fetch(@user, 'my-fancy-key', :order => 'name DESC') { companies }
339
339
  end
340
-
340
+
341
+ test "empty user relation should be empty" do
342
+ assert_equal [], @user.empty_user_relations
343
+ end
344
+
345
+ test "should not query the database for ids when the list is empty" do
346
+ assert_queries(1) do
347
+ @user.cached_empty_user_relations
348
+ result = @user.cached_empty_user_relations
349
+ assert_kind_of Array, result
350
+ end
351
+ end
352
+
353
+ test "should not query the database for ids when the list is empty and we are paginating" do
354
+ assert_queries(1) do
355
+ @user.cached_empty_user_relations(:page => 1)
356
+ result = @user.cached_empty_user_relations(:page => 1)
357
+ assert_kind_of WillPaginate::Collection, result
358
+ end
359
+ end
360
+
361
+ test "should not query the database for ids when the list is empty and we are applying limits etc" do
362
+ assert_queries(1) do
363
+ @user.cached_empty_user_relations(:limit => 10)
364
+ result = @user.cached_empty_user_relations(:limit => 10)
365
+ assert_kind_of Array, result
366
+ end
367
+ end
368
+
341
369
  #
342
370
  # Tests requiring manual verification by looking at the SQL logs.
343
371
  # TODO move these to a separate class.
344
372
  #
345
-
373
+
346
374
  test "should preserve original ordering" do
347
375
  # TODO. This one is hard to test because SQL usually keeps the same ordering
348
376
  # and it's difficult to make it do otherwise. Best to just inspect the queries
@@ -350,13 +378,13 @@ class AridCacheTest < ActiveSupport::TestCase
350
378
  @user.cached_companies
351
379
  @user.cached_companies
352
380
  end
353
-
381
+
354
382
  test "should paginate collections in memory" do
355
383
  # TODO. Tough to test because we can't just count queries
356
384
  # have to look at the SQL in the logs for this one.
357
385
  @user.cached_companies(:page => 2, :per_page => 3)
358
386
  @user.cached_companies(:page => 1, :per_page => 3)
359
- end
387
+ end
360
388
 
361
389
  protected
362
390
 
data/test/db/schema.rb CHANGED
@@ -4,12 +4,16 @@ ActiveRecord::Schema.define do
4
4
  t.column "email", :text
5
5
  t.timestamps
6
6
  end
7
-
7
+
8
8
  create_table "companies", :force => true do |t|
9
9
  t.column "name", :text
10
10
  t.column "owner_id", :integer
11
11
  t.column "country_id", :integer
12
- t.column "employees", :integer
12
+ t.column "employees", :integer
13
13
  t.timestamps
14
14
  end
15
+
16
+ create_table "empty_user_relations", :force => true do |t|
17
+ t.column "user_id", :integer
18
+ end
15
19
  end
@@ -0,0 +1,5 @@
1
+ require 'arid_cache'
2
+
3
+ class EmptyUserRelation < ActiveRecord::Base
4
+ belongs_to :user
5
+ end
data/test/models/user.rb CHANGED
@@ -2,6 +2,7 @@ require 'arid_cache'
2
2
 
3
3
  class User < ActiveRecord::Base
4
4
  has_many :companies, :foreign_key => :owner_id
5
+ has_many :empty_user_relations # This must always return an empty list
5
6
  named_scope :companies, :joins => :companies
6
7
  named_scope :successful, :joins => :companies, :conditions => 'companies.employees > 50', :group => 'users.id'
7
8
 
data/test/test_helper.rb CHANGED
@@ -11,7 +11,7 @@ require 'test/unit' # required by ActiveSupport::TestCase
11
11
  require 'will_paginate'
12
12
  require 'ruby-debug'
13
13
 
14
- # Activate Will Paginate
14
+ # Activate Will Paginate
15
15
  WillPaginate.enable_activerecord
16
16
 
17
17
  # Add support for expiring file-cache store.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arid_cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karl Varga
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-21 00:00:00 +08:00
12
+ date: 2010-07-05 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -90,6 +90,7 @@ files:
90
90
  - test/lib/blueprint.rb
91
91
  - test/log/.gitignore
92
92
  - test/models/company.rb
93
+ - test/models/empty_user_relation.rb
93
94
  - test/models/user.rb
94
95
  - test/test_helper.rb
95
96
  has_rdoc: true
@@ -129,5 +130,6 @@ test_files:
129
130
  - test/lib/active_support/cache/file_store_extras.rb
130
131
  - test/lib/blueprint.rb
131
132
  - test/models/company.rb
133
+ - test/models/empty_user_relation.rb
132
134
  - test/models/user.rb
133
135
  - test/test_helper.rb