djmaze-arid_cache 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,78 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ Bundler.require
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "djmaze-arid_cache"
9
+ gem.summary = %Q{Automates efficient caching of your ActiveRecord collections, gives you counts for free and supports pagination.}
10
+ gem.description = <<-END.gsub(/^\s+/, '')
11
+ Fork of arid_cache which defines caching methods once on a class instead of per object, thus preventing "singleton can't be dumped" from memcached!
12
+
13
+ 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
+
15
+ AridCache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
16
+ END
17
+ gem.email = "maze@strahlungsfrei.de"
18
+ gem.homepage = "http://github.com/djmaze/arid_cache"
19
+ gem.authors = ["Karl Varga", "Martin Honermeyer"]
20
+ gem.add_dependency "will_paginate"
21
+ gem.add_development_dependency "will_paginate"
22
+ gem.add_development_dependency "faker"
23
+ gem.add_development_dependency "machinist"
24
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
25
+ end
26
+ Jeweler::GemcutterTasks.new
27
+ rescue LoadError
28
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
29
+ end
30
+
31
+ require 'spec/rake/spectask'
32
+ Spec::Rake::SpecTask.new(:spec) do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.spec_files = FileList['spec/**/*_spec.rb']
35
+ end
36
+
37
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
38
+ spec.libs << 'lib' << 'spec'
39
+ spec.pattern = 'spec/**/*_spec.rb'
40
+ spec.rcov = true
41
+ end
42
+
43
+ task :spec => :check_dependencies
44
+
45
+ require 'rake/testtask'
46
+ Rake::TestTask.new(:test) do |test|
47
+ test.libs << 'lib' << 'test'
48
+ test.pattern = 'test/**/*_test.rb'
49
+ test.verbose = true
50
+ end
51
+
52
+ task :test => :check_dependencies
53
+ task :default => :test
54
+
55
+ require 'rake/rdoctask'
56
+ Rake::RDocTask.new do |rdoc|
57
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
58
+
59
+ rdoc.rdoc_dir = 'rdoc'
60
+ rdoc.title = "arid_cache #{version}"
61
+ rdoc.rdoc_files.include('README*')
62
+ rdoc.rdoc_files.include('lib/**/*.rb')
63
+ end
64
+
65
+ namespace :release do
66
+ desc "Release a new patch version"
67
+ task :patch do
68
+ Rake::Task['version:bump:patch'].invoke
69
+ Rake::Task['release:current'].invoke
70
+ end
71
+
72
+ desc "Release the current version (after a manual version bump). This rebuilds the gemspec, pushes the updated code, tags it and releases to RubyGems"
73
+ task :current do
74
+ Rake::Task['github:release'].invoke
75
+ Rake::Task['git:release'].invoke
76
+ Rake::Task['gemcutter:release'].invoke
77
+ end
78
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.0
@@ -0,0 +1,104 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{arid_cache}
8
+ s.version = "1.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Karl Varga"]
12
+ s.date = %q{2010-12-17}
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
+ AridCache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
15
+ }
16
+ s.email = %q{kjvarga@gmail.com}
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".gitignore",
23
+ "Gemfile",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "arid_cache.gemspec",
29
+ "init.rb",
30
+ "lib/arid_cache.rb",
31
+ "lib/arid_cache/active_record.rb",
32
+ "lib/arid_cache/cache_proxy.rb",
33
+ "lib/arid_cache/helpers.rb",
34
+ "lib/arid_cache/store.rb",
35
+ "rails/init.rb",
36
+ "spec/arid_cache/arid_cache_spec.rb",
37
+ "spec/arid_cache/cache_proxy_result_spec.rb",
38
+ "spec/arid_cache/cache_proxy_spec.rb",
39
+ "spec/spec.opts",
40
+ "spec/spec_helper.rb",
41
+ "spec/support/ar_query.rb",
42
+ "spec/support/custom_methods.rb",
43
+ "spec/support/matchers.rb",
44
+ "test/arid_cache_test.rb",
45
+ "test/console",
46
+ "test/lib/add_query_counting_to_active_record.rb",
47
+ "test/lib/blueprint.rb",
48
+ "test/lib/db_prepare.rb",
49
+ "test/lib/fix_active_support_file_store_expires_in.rb",
50
+ "test/lib/mock_rails.rb",
51
+ "test/lib/models/company.rb",
52
+ "test/lib/models/empty_user_relation.rb",
53
+ "test/lib/models/user.rb",
54
+ "test/log/.gitignore",
55
+ "test/test_helper.rb"
56
+ ]
57
+ s.homepage = %q{http://github.com/kjvarga/arid_cache}
58
+ s.rdoc_options = ["--charset=UTF-8"]
59
+ s.require_paths = ["lib"]
60
+ s.rubygems_version = %q{1.3.7}
61
+ s.summary = %q{Automates efficient caching of your ActiveRecord collections, gives you counts for free and supports pagination.}
62
+ s.test_files = [
63
+ "spec/arid_cache/arid_cache_spec.rb",
64
+ "spec/arid_cache/cache_proxy_result_spec.rb",
65
+ "spec/arid_cache/cache_proxy_spec.rb",
66
+ "spec/spec_helper.rb",
67
+ "spec/support/ar_query.rb",
68
+ "spec/support/custom_methods.rb",
69
+ "spec/support/matchers.rb",
70
+ "test/arid_cache_test.rb",
71
+ "test/lib/add_query_counting_to_active_record.rb",
72
+ "test/lib/blueprint.rb",
73
+ "test/lib/db_prepare.rb",
74
+ "test/lib/fix_active_support_file_store_expires_in.rb",
75
+ "test/lib/mock_rails.rb",
76
+ "test/lib/models/company.rb",
77
+ "test/lib/models/empty_user_relation.rb",
78
+ "test/lib/models/user.rb",
79
+ "test/test_helper.rb"
80
+ ]
81
+
82
+ if s.respond_to? :specification_version then
83
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
84
+ s.specification_version = 3
85
+
86
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
87
+ s.add_runtime_dependency(%q<will_paginate>, [">= 0"])
88
+ s.add_development_dependency(%q<will_paginate>, [">= 0"])
89
+ s.add_development_dependency(%q<faker>, [">= 0"])
90
+ s.add_development_dependency(%q<machinist>, [">= 0"])
91
+ else
92
+ s.add_dependency(%q<will_paginate>, [">= 0"])
93
+ s.add_dependency(%q<will_paginate>, [">= 0"])
94
+ s.add_dependency(%q<faker>, [">= 0"])
95
+ s.add_dependency(%q<machinist>, [">= 0"])
96
+ end
97
+ else
98
+ s.add_dependency(%q<will_paginate>, [">= 0"])
99
+ s.add_dependency(%q<will_paginate>, [">= 0"])
100
+ s.add_dependency(%q<faker>, [">= 0"])
101
+ s.add_dependency(%q<machinist>, [">= 0"])
102
+ end
103
+ end
104
+
data/init.rb ADDED
@@ -0,0 +1,6 @@
1
+ begin
2
+ require File.join(File.dirname(__FILE__), 'lib', 'arid_cache') # From here
3
+ rescue LoadError
4
+ require 'arid_cache' # From gem
5
+ end
6
+ AridCache.init_rails
@@ -0,0 +1,95 @@
1
+ module AridCache
2
+ module ActiveRecord
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.extend MirrorMethods
6
+ base.send :include, MirrorMethods
7
+ base.class_eval do
8
+ alias_method_chain :method_missing, :arid_cache
9
+ alias_method_chain :respond_to?, :arid_cache
10
+ end
11
+ class << base
12
+ alias_method_chain :method_missing, :arid_cache
13
+ alias_method_chain :respond_to?, :arid_cache
14
+ end
15
+ end
16
+
17
+ module MirrorMethods
18
+ def clear_caches
19
+ AridCache.cache.clear_class_caches(self)
20
+ AridCache.cache.clear_instance_caches(self)
21
+ end
22
+
23
+ def clear_class_caches
24
+ AridCache.cache.clear_class_caches(self)
25
+ end
26
+
27
+ def clear_instance_caches
28
+ AridCache.cache.clear_instance_caches(self)
29
+ end
30
+
31
+ # Return an AridCache key for the given key fragment for this object.
32
+ #
33
+ # Supported options:
34
+ # :auto_expire => true/false # (default false) whether or not to use the <tt>cache_key</tt> method on instance caches
35
+ #
36
+ # Examples:
37
+ # User.arid_cache_key('companies') => 'user-companies'
38
+ # User.first.arid_cache_key('companies') => 'users/1-companies'
39
+ # User.first.arid_cache_key('companies', :auto_expire => true) => 'users/20090120091123-companies'
40
+ #
41
+ def arid_cache_key(key, options={})
42
+ object_key = if self.is_a?(Class)
43
+ self.name.downcase
44
+ elsif options[:auto_expire]
45
+ self.cache_key
46
+ else
47
+ "#{self.class.name.downcase.pluralize}/#{self.id}"
48
+ end
49
+ 'arid-cache-' + object_key + '-' + key.to_s
50
+ end
51
+
52
+ def respond_to_with_arid_cache?(method, include_private = false) #:nodoc:
53
+ if (method.to_s =~ /^cached_.*(_count)?$/).nil?
54
+ respond_to_without_arid_cache?(method, include_private)
55
+ elsif method.to_s =~ /^cached_(.*)_count$/
56
+ AridCache.store.has?(self, "#{$1}_count") || AridCache.store.has?(self, $1) || super("#{$1}_count", include_private) || super($1, include_private)
57
+ elsif method.to_s =~ /^cached_(.*)$/
58
+ AridCache.store.has?(self, $1) || super($1, include_private)
59
+ else
60
+ respond_to_without_arid_cache?(method, include_private)
61
+ end
62
+ end
63
+
64
+ protected
65
+
66
+ def method_missing_with_arid_cache(method, *args, &block) #:nodoc:
67
+ if method.to_s =~ /^cached_(.*)$/
68
+ opts = args.empty? ? {} : args.first
69
+ AridCache.lookup(self, $1, opts, &block)
70
+ else
71
+ method_missing_without_arid_cache(method, *args)
72
+ end
73
+ end
74
+ end
75
+
76
+ module ClassMethods
77
+
78
+ def instance_caches(opts={}, &block)
79
+ AridCache::Store::InstanceCacheConfiguration.new(self, opts).instance_eval(&block) && nil
80
+ end
81
+
82
+ def class_caches(opts={}, &block)
83
+ AridCache::Store::ClassCacheConfiguration.new(self, opts).instance_eval(&block) && nil
84
+ end
85
+
86
+ def is_mysql_adapter?
87
+ @is_mysql_adapter ||= !!(::ActiveRecord::Base.connection.adapter_name =~ /MySQL/i)
88
+ end
89
+
90
+ def is_mysql_adapter=(value)
91
+ @is_mysql_adapter = value
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,368 @@
1
+ module AridCache
2
+ class CacheProxy
3
+ attr_accessor :object, :key, :opts, :blueprint, :cached, :cache_key, :block, :records, :combined_options, :klass
4
+
5
+ # AridCache::CacheProxy::Result
6
+ #
7
+ # This struct is stored in the cache and stores information we need
8
+ # to re-query for results.
9
+ Result = Struct.new(:ids, :klass, :count) do
10
+
11
+ def has_count?
12
+ !count.nil?
13
+ end
14
+
15
+ def has_ids?
16
+ !ids.nil?
17
+ end
18
+
19
+ def klass=(value)
20
+ self['klass'] = value.is_a?(Class) ? value.name : value.class.name
21
+ end
22
+
23
+ def klass
24
+ self['klass'].constantize unless self['klass'].nil?
25
+ end
26
+ end
27
+
28
+ #
29
+ # Managing your caches
30
+ #
31
+
32
+ def self.clear_caches
33
+ Rails.cache.delete_matched(%r[arid-cache-.*])
34
+ end
35
+
36
+ def self.clear_class_caches(object)
37
+ key = (object.is_a?(Class) ? object : object.class).name.downcase + '-'
38
+ Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
39
+ end
40
+
41
+ def self.clear_instance_caches(object)
42
+ key = (object.is_a?(Class) ? object : object.class).name.pluralize.downcase
43
+ Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
44
+ end
45
+
46
+ def initialize(object, key, opts={}, &block)
47
+ self.object = object
48
+ self.key = key
49
+ self.opts = opts.symbolize_keys
50
+ self.blueprint = AridCache.store.find(object, key)
51
+ self.block = block
52
+ self.records = nil
53
+
54
+ # The options from the blueprint merged with the options for this call
55
+ self.combined_options = self.blueprint.nil? ? self.opts : self.blueprint.opts.merge(self.opts)
56
+ self.cache_key = object.arid_cache_key(key, opts_for_cache_key)
57
+ end
58
+
59
+ #
60
+ # Fetching results
61
+ #
62
+
63
+ # Return a count of ids in the cache, or return whatever is in the cache if it is
64
+ # not a CacheProxy::Result
65
+ def fetch_count
66
+ if refresh_cache?
67
+ execute_count
68
+ elsif cached.is_a?(AridCache::CacheProxy::Result)
69
+ cached.has_count? ? cached.count : execute_count
70
+ elsif cached.is_a?(Fixnum)
71
+ cached
72
+ elsif cached.respond_to?(:count)
73
+ cached.count
74
+ else
75
+ cached # what else can we do? return it
76
+ end
77
+ end
78
+
79
+ # Return a list of records using the options provided. If the item in the cache
80
+ # is not a CacheProxy::Result it is returned as-is. If there is nothing in the cache
81
+ # the block defining the cache is exectued. If the :raw option is true, returns the
82
+ # CacheProxy::Result unmodified, ignoring other options, except where those options
83
+ # are used to initialize the cache.
84
+ def fetch
85
+ @raw_result = opts_for_cache_proxy[:raw] == true
86
+
87
+ result = if refresh_cache?
88
+ execute_find(@raw_result)
89
+ elsif cached.is_a?(AridCache::CacheProxy::Result)
90
+ if cached.has_ids? && @raw_result
91
+ self.cached # return it unmodified
92
+ elsif cached.has_ids?
93
+ fetch_from_cache # return a list of active records after applying options
94
+ else # true if we have only calculated the count thus far
95
+ execute_find(@raw_result)
96
+ end
97
+ else
98
+ cached # some base type, return it unmodified
99
+ end
100
+ end
101
+
102
+ # Clear the cached result for this cache only
103
+ def clear_cached
104
+ Rails.cache.delete(self.cache_key, opts_for_cache)
105
+ end
106
+
107
+ # Return the cached result for this object's key
108
+ def cached
109
+ @cached ||= Rails.cache.read(self.cache_key, opts_for_cache)
110
+ end
111
+
112
+ # Return the class of the cached results i.e. if the cached result is a
113
+ # list of Album records, then klass returns Album. If there is nothing
114
+ # in the cache, then the class is inferred to be the class of the object
115
+ # that the cached method is being called on.
116
+ def klass
117
+ @klass ||= if self.cached && self.cached.is_a?(AridCache::CacheProxy::Result)
118
+ self.cached.klass
119
+ else
120
+ object_base_class
121
+ end
122
+ end
123
+
124
+
125
+ private
126
+
127
+ # Return a list of records from the database using the ids from
128
+ # the cached Result.
129
+ #
130
+ # The result is paginated if the :page option is preset, otherwise
131
+ # a regular list of ActiveRecord results is returned.
132
+ #
133
+ # If no :order is specified, the current ordering of the ids is
134
+ # preserved with some fancy SQL.
135
+ def fetch_from_cache
136
+ if paginate?
137
+
138
+ # Return a paginated collection
139
+ if cached.ids.empty?
140
+
141
+ # No ids, return an empty WillPaginate result
142
+ [].paginate(opts_for_paginate)
143
+
144
+ elsif combined_options.include?(:order)
145
+
146
+ # An order has been specified. We have to go to the database
147
+ # and paginate there because the contents of the requested
148
+ # page will be different.
149
+ klass.paginate(cached.ids, { :total_entries => cached.ids.size }.merge(opts_for_find.merge(opts_for_paginate)))
150
+
151
+ else
152
+
153
+ # Order is unchanged. We can paginate in memory and only select
154
+ # those ids that we actually need. This is the most efficient.
155
+ paged_ids = cached.ids.paginate(opts_for_paginate)
156
+ paged_ids.replace(klass.find_all_by_id(paged_ids, opts_for_find(paged_ids)))
157
+
158
+ end
159
+
160
+ elsif cached.ids.empty?
161
+
162
+ # We are returning a regular (non-paginated) result.
163
+ # If we don't have any ids, that's an empty result
164
+ # in any language.
165
+ []
166
+
167
+ elsif combined_options.include?(:order)
168
+
169
+ # An order has been specified, so use it.
170
+ klass.find_all_by_id(cached.ids, opts_for_find)
171
+
172
+ else
173
+
174
+ # No order has been specified, so we have to maintain
175
+ # the current order. We do this by passing some extra
176
+ # SQL which orders by the current array ordering.
177
+ offset, limit = combined_options.delete(:offset) || 0, combined_options.delete(:limit) || cached.count
178
+ ids = cached.ids[offset, limit]
179
+
180
+ klass.find_all_by_id(ids, opts_for_find(ids))
181
+ end
182
+ end
183
+
184
+ def paginate?
185
+ combined_options.include?(:page)
186
+ end
187
+
188
+ def refresh_cache?
189
+ cached.nil? || opts[:force]
190
+ end
191
+
192
+ def get_records
193
+ block = self.block || (blueprint && blueprint.proc)
194
+ self.records = block.nil? ? object.instance_eval(key) : object.instance_eval(&block)
195
+ end
196
+
197
+ # Seed the cache by executing the stored block (or by calling a method on the object).
198
+ # Then apply any options like pagination or ordering before returning the result, which
199
+ # is either some base type, or usually, a list of active records.
200
+ #
201
+ # Options:
202
+ # raw - if true, return the CacheProxy::Result after seeding the cache, ignoring
203
+ # other options. Default is false.
204
+ def execute_find(raw = false)
205
+ get_records
206
+ cached = AridCache::CacheProxy::Result.new
207
+
208
+ if !records.is_a?(Enumerable) || (!records.empty? && !records.first.is_a?(::ActiveRecord::Base))
209
+ cached = records # some base type, cache it as itself
210
+ else
211
+ cached.ids = records.collect(&:id)
212
+ cached.count = records.size
213
+ if records.respond_to?(:proxy_reflection) # association proxy
214
+ cached.klass = records.proxy_reflection.klass
215
+ elsif !records.empty?
216
+ cached.klass = records.first.class
217
+ else
218
+ cached.klass = object_base_class
219
+ end
220
+ end
221
+ Rails.cache.write(cache_key, cached, opts_for_cache)
222
+ self.cached = cached
223
+
224
+ # Return the raw result?
225
+ return self.cached if raw
226
+
227
+ # An order has been specified. We have to go to the database
228
+ # to order because we can't be sure that the current order is the same as the cache.
229
+ if cached.is_a?(AridCache::CacheProxy::Result) && combined_options.include?(:order)
230
+ self.klass = self.cached.klass # TODO used by fetch_from_cache needs refactor
231
+ fetch_from_cache
232
+ else
233
+ process_result_in_memory(records)
234
+ end
235
+ end
236
+
237
+ # Convert records to an array before calling paginate. If we don't do this
238
+ # and the result is a named scope, paginate will trigger an additional query
239
+ # to load the page rather than just using the records we have already fetched.
240
+ #
241
+ # If we are not paginating and the options include :limit (and optionally :offset)
242
+ # apply the limit and offset to the records before returning them.
243
+ #
244
+ # Otherwise we have an issue where all the records are returned the first time
245
+ # the collection is loaded, but on subsequent calls the options_for_find are
246
+ # included and you get different results. Note that with options like :order
247
+ # this cannot be helped. We don't want to modify the query that generates the
248
+ # collection because the idea is to allow getting different perspectives of the
249
+ # cached collection without relying on modifying the collection as a whole.
250
+ #
251
+ # If you do want a specialized, modified, or subset of the collection it's best
252
+ # to define it in a block and have a new cache for it:
253
+ #
254
+ # model.my_special_collection { the_collection(:order => 'new order', :limit => 10) }
255
+ def process_result_in_memory(records)
256
+ if opts.include?(:page)
257
+ records = records.respond_to?(:to_a) ? records.to_a : records
258
+ records.respond_to?(:paginate) ? records.paginate(opts_for_paginate) : records
259
+ elsif opts.include?(:limit)
260
+ records = records.respond_to?(:to_a) ? records.to_a : records
261
+ offset = opts[:offset] || 0
262
+ records[offset, opts[:limit]]
263
+ else
264
+ records
265
+ end
266
+ end
267
+
268
+ def execute_count
269
+ get_records
270
+ cached = AridCache::CacheProxy::Result.new
271
+
272
+ # Just get the count if we can.
273
+ #
274
+ # Because of how AssociationProxy works, if we even look at it, it'll
275
+ # trigger the query. So don't look.
276
+ #
277
+ # Association proxy or named scope. Check for an association first, because
278
+ # it doesn't trigger the select if it's actually named scope. Calling respond_to?
279
+ # on an association proxy will hower trigger a select because it loads up the target
280
+ # and passes the respond_to? on to it.
281
+ if records.respond_to?(:proxy_reflection) || records.respond_to?(:proxy_options)
282
+ cached.count = records.count # just get the count
283
+ cached.klass = object_base_class
284
+ elsif records.is_a?(Enumerable) && (records.empty? || records.first.is_a?(::ActiveRecord::Base))
285
+ cached.ids = records.collect(&:id) # get everything now that we have it
286
+ cached.count = records.size
287
+ cached.klass = records.empty? ? object_base_class : records.first.class
288
+ else
289
+ cached = records # some base type, cache it as itself
290
+ end
291
+
292
+ Rails.cache.write(cache_key, cached, opts_for_cache)
293
+ self.cached = cached
294
+ cached.respond_to?(:count) ? cached.count : cached
295
+ end
296
+
297
+ OPTIONS_FOR_PAGINATE = [:page, :per_page, :total_entries, :finder]
298
+
299
+ # Filter options for paginate, if *klass* is set, we get the :per_page value from it.
300
+ def opts_for_paginate
301
+ paginate_opts = combined_options.reject { |k,v| !OPTIONS_FOR_PAGINATE.include?(k) }
302
+ paginate_opts[:finder] = :find_all_by_id unless paginate_opts.include?(:finder)
303
+ paginate_opts[:per_page] = klass.per_page if klass && !paginate_opts.include?(:per_page)
304
+ paginate_opts
305
+ end
306
+
307
+ OPTIONS_FOR_FIND = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock ]
308
+
309
+ # Preserve the original order of the results if no :order option is specified.
310
+ #
311
+ # @arg ids array of ids to order by unless an :order option is specified. If not
312
+ # specified, cached.ids is used.
313
+ def opts_for_find(ids=nil)
314
+ ids ||= cached.ids
315
+ find_opts = combined_options.reject { |k,v| !OPTIONS_FOR_FIND.include?(k) }
316
+ find_opts[:order] = preserve_order(ids) unless find_opts.include?(:order)
317
+ find_opts
318
+ end
319
+
320
+ OPTIONS_FOR_CACHE = [ :expires_in ]
321
+
322
+ def opts_for_cache
323
+ combined_options.reject { |k,v| !OPTIONS_FOR_CACHE.include?(k) }
324
+ end
325
+
326
+ OPTIONS_FOR_CACHE_KEY = [ :auto_expire ]
327
+
328
+ def opts_for_cache_key
329
+ combined_options.reject { |k,v| !OPTIONS_FOR_CACHE_KEY.include?(k) }
330
+ end
331
+
332
+ OPTIONS_FOR_CACHE_PROXY = [:raw, :clear]
333
+
334
+ # Returns options that affect the cache proxy result
335
+ def opts_for_cache_proxy
336
+ combined_options.reject { |k,v| !OPTIONS_FOR_CACHE_PROXY.include?(k) }
337
+ end
338
+
339
+ def object_base_class #:nodoc:
340
+ object.is_a?(Class) ? object : object.class
341
+ end
342
+
343
+ # Generate an ORDER BY clause that preserves the ordering of the ids in *ids*.
344
+ #
345
+ # The method we use depends on the database adapter because only MySQL
346
+ # supports the ORDER BY FIELD() function. For other databases we use
347
+ # a CASE statement.
348
+ #
349
+ # TODO: is it quicker to sort in memory?
350
+ def preserve_order(ids)
351
+ column = if self.klass.respond_to?(:table_name)
352
+ ::ActiveRecord::Base.connection.quote_table_name(self.klass.table_name) + '.id'
353
+ else
354
+ "id"
355
+ end
356
+
357
+ if ids.empty?
358
+ nil
359
+ elsif ::ActiveRecord::Base.is_mysql_adapter?
360
+ "FIELD(#{column},#{ids.join(',')})"
361
+ else
362
+ order = ''
363
+ ids.each_index { |i| order << "WHEN #{column}=#{ids[i]} THEN #{i+1} " }
364
+ "CASE " + order + " END"
365
+ end
366
+ end
367
+ end
368
+ end