arid_cache 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +27 -9
- data/VERSION +1 -1
- data/arid_cache.gemspec +11 -4
- data/lib/arid_cache/cache_proxy/options.rb +73 -0
- data/lib/arid_cache/cache_proxy/result_processor.rb +199 -0
- data/lib/arid_cache/cache_proxy/utilities.rb +35 -0
- data/lib/arid_cache/cache_proxy.rb +63 -304
- data/lib/arid_cache.rb +1 -1
- data/spec/arid_cache/{cache_proxy_result_spec.rb → cache_proxy/cached_result_spec.rb} +2 -2
- data/spec/arid_cache/cache_proxy/options_spec.rb +80 -0
- data/spec/arid_cache/cache_proxy/result_processor_spec.rb +217 -0
- data/spec/arid_cache/cache_proxy_spec.rb +118 -89
- data/test/console +2 -0
- metadata +13 -6
data/README.rdoc
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
= AridCache
|
2
2
|
|
3
|
-
AridCache makes caching easy and effective. AridCache supports caching on all
|
3
|
+
AridCache makes caching easy and effective. AridCache supports caching on all ActiveRecord class and instance methods right out of the box. AridCache keeps caching logic out of your model methods and clarifies your code by making calls to cached result sets explicit.
|
4
4
|
|
5
|
-
AridCache supports caching large, expensive ActiveRecord collections by caching only the model IDs, provides efficient in-memory pagination of your cached collections, and gives you collection counts for free. Non-ActiveRecord collection data is cached unchanged allowing you to cache the results of
|
5
|
+
AridCache supports caching large, expensive ActiveRecord collections by caching only the model IDs, provides efficient in-memory pagination of your cached collections, and gives you collection counts for free. Non-ActiveRecord collection data is cached unchanged allowing you to cache the results of anything simply by prepending your method call with <tt>cached_</tt>.
|
6
6
|
|
7
|
-
AridCache simplifies caching by supporting auto-expiring cache keys - as well as common options like <tt>:expires_in</tt> - and provides methods to help you manage your caches
|
7
|
+
AridCache simplifies caching by supporting auto-expiring cache keys - as well as common options like <tt>:expires_in</tt> - and provides methods to help you manage your caches.
|
8
8
|
|
9
9
|
== Changes
|
10
10
|
|
11
|
+
v1.3.0: Support limits, ordering and pagination on cached Enumerables
|
11
12
|
v1.2.0: Fix Rails 3 ActiveRecord hooks & remove some Rails dependencies
|
12
13
|
v1.0.5: Support <tt>:raw</tt> and <tt>:clear</tt> options.
|
13
14
|
|
@@ -33,11 +34,27 @@ Then
|
|
33
34
|
|
34
35
|
rake gems:install
|
35
36
|
|
37
|
+
== Features
|
38
|
+
|
39
|
+
* Include the AridCache module in any Class
|
40
|
+
* Rails 2 & 3 compatible with automatic ActiveRecord::Base integration
|
41
|
+
* Supports auto-expiring cache keys
|
42
|
+
* Supports limits, ordering & pagination of cached Enumerables and ActiveRecord collections
|
43
|
+
* Define caches and their options on your class using +instance_caches+ and +class_caches+
|
44
|
+
* Counts for free - if you have already cached the result, you get the count for free
|
45
|
+
* Supports eager-loading and other options to <tt>ActiveRecord::Base#find</tt> like
|
46
|
+
:conditions, :include, :joins, :select, :readonly, :group, :having, :from
|
47
|
+
* Provides methods to clear caches individually, at the instance-level, class-level and globally
|
48
|
+
* Preserves the order of your cached ActiveRecord collections
|
49
|
+
* Optimized to make as few cache and database accesses as absolutely neccessary
|
50
|
+
|
36
51
|
== Introduction
|
37
52
|
|
38
53
|
The name AridCache comes from <b>A</b>ctive<b>R</b>ecord *ID* Cache. It's also very DRY...get it? :)
|
39
54
|
|
40
|
-
Out of the box AridCache supports caching on all your ActiveRecord class and instance methods and named scopes
|
55
|
+
Out of the box AridCache supports caching on all your ActiveRecord class and instance methods and named scopes. If a class or class instance <tt>respond_to?</tt> something, you can cache it.
|
56
|
+
|
57
|
+
AridCache supports limits, pagination and ordering options on cached ActiveRecord collections and any other Enumerable. Options to apply limits are <tt>:limit</tt> and <tt>:offset</tt>. Options for pagination are <tt>:page</tt> and <tt>:per_page</tt> and the <tt>:order</tt> option accepts the same values as ActiveRecord::Base#find. If the cached value is an ActiveRecord collection most other options to ActiveRecord::Base#find are supported too. If the cached value is an Enumerable (e.g. an Array) the value for :order must be a <tt>Proc</tt>. The Proc is passed to Enumerable#sort to do the sorting. Unless the Enumerable is a list of Hashes in which case it can be a String or Symbol giving the hash key to sort by.
|
41
58
|
|
42
59
|
The way you interact with the cache via your model methods is to prepend the method call with <tt>cached_</tt>. The part of the method call after <tt>cached_</tt> serves as the basis for the cache key. For example,
|
43
60
|
|
@@ -46,7 +63,6 @@ The way you interact with the cache via your model methods is to prepend the met
|
|
46
63
|
|
47
64
|
You can also define caches that use compositions of methods or named scopes, or other complex queries, without having to add a new method to your class. This way you can also create different caches that all use the same method. For example,
|
48
65
|
|
49
|
-
# cache key is arid-cache-user-most_active_users
|
50
66
|
User.cached_most_active_users do
|
51
67
|
active.find(:order => 'activity DESC', :limit => 5)
|
52
68
|
end
|
@@ -55,17 +71,19 @@ You can also define caches that use compositions of methods or named scopes, or
|
|
55
71
|
|
56
72
|
If the result of your <tt>cached_</tt> call is an array of ActiveRecords, AridCache only stores the IDs in the cache (because it's a bad idea to store records in the cache).
|
57
73
|
|
58
|
-
On subsequent calls we call <tt>find_all_by_id</tt> on the target class passing in the ActiveRecord IDs that were stored in the cache. AridCache will preserve the original ordering of your collection (you can change this using the <tt>:order</tt>).
|
74
|
+
On subsequent calls we call <tt>find_all_by_id</tt> on the target class passing in the ActiveRecord IDs that were stored in the cache. AridCache will preserve the original ordering of your collection (you can change this using the <tt>:order</tt> option).
|
59
75
|
|
60
76
|
The idea here is to cache collections that are expensive to query. Once the cache is loaded, retrieving the cached records from the database simply involves a <tt>SELECT * FROM table WHERE id IN (ids, ...)</tt>.
|
61
77
|
|
62
78
|
Consider how long it would take to get the top 10 favorited tracks of all time from a database with a million tracks and 100,000 users. Now compare that to selecting 10 tracks by ID from the track table. The performance gain is huge.
|
63
79
|
|
64
|
-
===
|
80
|
+
=== Enumerables
|
81
|
+
|
82
|
+
Cached enumerables support find-like options such as <tt>:limit</tt>, <tt>:offset</tt> and <tt>order</tt> as well as pagination options <tt>:page</tt> and <tt>:per_page</tt>. <tt>:order</tt> must be a <tt>Proc</tt>. It is passed to Enumerable#sort to do the sorting. Unless the enumerable contains hashes in which case <tt>:order</tt> can be a string or symbol hash key to order by.
|
65
83
|
|
66
|
-
|
84
|
+
=== Base Types and Other Collections
|
67
85
|
|
68
|
-
|
86
|
+
Anything that is not an array of ActiveRecords is cached as-is. Numbers, arrays, hashes, nils, whatever.
|
69
87
|
|
70
88
|
=== Example
|
71
89
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.3.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 = "1.
|
8
|
+
s.version = "1.3.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{2011-
|
12
|
+
s.date = %q{2011-04-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
|
}
|
@@ -30,6 +30,9 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
|
|
30
30
|
"lib/arid_cache.rb",
|
31
31
|
"lib/arid_cache/active_record.rb",
|
32
32
|
"lib/arid_cache/cache_proxy.rb",
|
33
|
+
"lib/arid_cache/cache_proxy/options.rb",
|
34
|
+
"lib/arid_cache/cache_proxy/result_processor.rb",
|
35
|
+
"lib/arid_cache/cache_proxy/utilities.rb",
|
33
36
|
"lib/arid_cache/helpers.rb",
|
34
37
|
"lib/arid_cache/inflector.rb",
|
35
38
|
"lib/arid_cache/inflector/inflections.rb",
|
@@ -38,7 +41,9 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
|
|
38
41
|
"rails/init.rb",
|
39
42
|
"spec/arid_cache/active_record_spec.rb",
|
40
43
|
"spec/arid_cache/arid_cache_spec.rb",
|
41
|
-
"spec/arid_cache/
|
44
|
+
"spec/arid_cache/cache_proxy/cached_result_spec.rb",
|
45
|
+
"spec/arid_cache/cache_proxy/options_spec.rb",
|
46
|
+
"spec/arid_cache/cache_proxy/result_processor_spec.rb",
|
42
47
|
"spec/arid_cache/cache_proxy_spec.rb",
|
43
48
|
"spec/spec.opts",
|
44
49
|
"spec/spec_helper.rb",
|
@@ -66,7 +71,9 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
|
|
66
71
|
s.test_files = [
|
67
72
|
"spec/arid_cache/active_record_spec.rb",
|
68
73
|
"spec/arid_cache/arid_cache_spec.rb",
|
69
|
-
"spec/arid_cache/
|
74
|
+
"spec/arid_cache/cache_proxy/cached_result_spec.rb",
|
75
|
+
"spec/arid_cache/cache_proxy/options_spec.rb",
|
76
|
+
"spec/arid_cache/cache_proxy/result_processor_spec.rb",
|
70
77
|
"spec/arid_cache/cache_proxy_spec.rb",
|
71
78
|
"spec/spec_helper.rb",
|
72
79
|
"spec/support/ar_query.rb",
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module AridCache
|
2
|
+
class CacheProxy
|
3
|
+
# A class representing a hash of options with methods to return subsets of
|
4
|
+
# those options.
|
5
|
+
class Options < Hash
|
6
|
+
def initialize(opts={})
|
7
|
+
self.merge!(opts)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Filter options for paginate. Get the :per_page value from the receiver if it's not set.
|
11
|
+
# Set total_entries to +records.size+ if +records+ is supplied
|
12
|
+
def opts_for_paginate(records=nil)
|
13
|
+
paginate_opts = reject { |k,v| !OPTIONS_FOR_PAGINATE.include?(k) }
|
14
|
+
paginate_opts[:finder] = :find_all_by_id unless paginate_opts.include?(:finder)
|
15
|
+
if self[:result_klass].respond_to?(:per_page) && !paginate_opts.include?(:per_page)
|
16
|
+
paginate_opts[:per_page] = self[:result_klass].per_page
|
17
|
+
end
|
18
|
+
paginate_opts[:total_entries] = records.size unless records.nil?
|
19
|
+
paginate_opts
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return options suitable to pass to ActiveRecord::Base#find.
|
23
|
+
# Preserve the original order of the results if no :order option is specified.
|
24
|
+
# If an offset is specified but no limit, ActiveRecord will not apply the offset,
|
25
|
+
# so pass in a limit that is as big as +ids.size+
|
26
|
+
#
|
27
|
+
# @arg ids array of ids to order by unless an :order option is specified.
|
28
|
+
def opts_for_find(ids)
|
29
|
+
find_opts = reject { |k,v| !OPTIONS_FOR_FIND.include?(k) }
|
30
|
+
find_opts[:order] = AridCache::CacheProxy::Utilities.order_by(ids, self[:result_klass]) unless find_opts.include?(:order)
|
31
|
+
find_opts[:limit] = ids.size unless find_opts.include?(:limit)
|
32
|
+
find_opts
|
33
|
+
end
|
34
|
+
|
35
|
+
def opts_for_cache
|
36
|
+
reject { |k,v| !OPTIONS_FOR_CACHE.include?(k) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def opts_for_cache_key
|
40
|
+
reject { |k,v| !OPTIONS_FOR_CACHE_KEY.include?(k) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns options that affect the cache proxy result
|
44
|
+
def opts_for_cache_proxy
|
45
|
+
reject { |k,v| !OPTIONS_FOR_CACHE_PROXY.include?(k) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def force?
|
49
|
+
!!self[:force]
|
50
|
+
end
|
51
|
+
|
52
|
+
def paginate?
|
53
|
+
include?(:page)
|
54
|
+
end
|
55
|
+
|
56
|
+
def raw?
|
57
|
+
!!self[:raw]
|
58
|
+
end
|
59
|
+
|
60
|
+
def count_only?
|
61
|
+
!!self[:count_only]
|
62
|
+
end
|
63
|
+
|
64
|
+
def order_by_proc?
|
65
|
+
include?(:order) && self[:order].is_a?(Proc)
|
66
|
+
end
|
67
|
+
|
68
|
+
def order_by_key?
|
69
|
+
include?(:order) && (self[:order].is_a?(Symbol) || self[:order].is_a?(String))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
module AridCache
|
2
|
+
class CacheProxy
|
3
|
+
# A class representing a result that is to be processed in some way before
|
4
|
+
# being returned to the user.
|
5
|
+
#
|
6
|
+
# Provides methods to introspect the result. The contents could be a base type,
|
7
|
+
# or an enumerable of sorts...any type really. We are only concerned with enumerables,
|
8
|
+
# and especially those containing active records.
|
9
|
+
class ResultProcessor
|
10
|
+
|
11
|
+
def initialize(result, opts={})
|
12
|
+
@result = result
|
13
|
+
@options = opts.is_a?(AridCache::CacheProxy::Options) ? opts : AridCache::CacheProxy::Options.new(opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Return true if the result is an enumerable and it is empty.
|
17
|
+
def is_empty?
|
18
|
+
is_enumerable? && @result.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return true if the result is an enumerable.
|
22
|
+
def is_enumerable?
|
23
|
+
@result.is_a?(Enumerable)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return true if the result is a list of hashes
|
27
|
+
def is_hashes?
|
28
|
+
is_enumerable? && @result.first.is_a?(Hash)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Order in the database if an order clause has been specified and we
|
32
|
+
# have a list of ActiveRecords or a CachedResult.
|
33
|
+
def order_in_database?
|
34
|
+
is_cached_result? || (@options.order_by_key? && is_activerecord?)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return true if the result is an enumerable and the first item is
|
38
|
+
# an active record.
|
39
|
+
def is_activerecord?
|
40
|
+
is_enumerable? && @result.first.is_a?(::ActiveRecord::Base)
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_activerecord_reflection?
|
44
|
+
@result.respond_to?(:proxy_reflection) || @result.respond_to?(:proxy_options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def is_cached_result?
|
48
|
+
@result.is_a?(AridCache::CacheProxy::CachedResult)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return the result to cache. For base types the original result is
|
52
|
+
# returned. ActiveRecords return a CachedResult.
|
53
|
+
def to_cache
|
54
|
+
# Check if it's an association first, because it doesn't trigger the select if it's
|
55
|
+
# a named scope. Calling respond_to? on an association proxy will trigger a select
|
56
|
+
# because it loads up the target and passes the respond_to? on to it.
|
57
|
+
@cached = if is_activerecord_reflection?
|
58
|
+
lazy_cache.klass = @result.proxy_reflection.klass if @result.respond_to?(:proxy_reflection)
|
59
|
+
if @options.count_only?
|
60
|
+
lazy_cache.count = @result.count
|
61
|
+
else
|
62
|
+
lazy_cache.ids = @result.collect { |r| r[:id] }
|
63
|
+
lazy_cache.count = @result.size
|
64
|
+
end
|
65
|
+
lazy_cache
|
66
|
+
elsif is_activerecord? || is_empty?
|
67
|
+
lazy_cache.ids = @result.collect { |r| r[:id] }
|
68
|
+
lazy_cache.count = @result.size
|
69
|
+
lazy_cache.klass = @result.first.class
|
70
|
+
lazy_cache
|
71
|
+
else
|
72
|
+
@result
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Apply any options like pagination or ordering and return the result, which
|
77
|
+
# is either some base type, or usually, a list of active records.
|
78
|
+
def to_result
|
79
|
+
if @options.count_only?
|
80
|
+
get_count
|
81
|
+
elsif @options.raw? || (!is_cached_result? && !is_enumerable?)
|
82
|
+
@result
|
83
|
+
else
|
84
|
+
if is_cached_result?
|
85
|
+
fetch_activerecords(filter_results(@result.ids))
|
86
|
+
elsif order_in_database?
|
87
|
+
fetch_activerecords(filter_results(@result))
|
88
|
+
else
|
89
|
+
filter_results(@result)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def get_count
|
97
|
+
if @cached.is_a?(AridCache::CacheProxy::CachedResult) # use what we put in the cache
|
98
|
+
@cached.count
|
99
|
+
elsif @result.respond_to?(:count)
|
100
|
+
@result.count
|
101
|
+
else
|
102
|
+
@result
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Lazy-initialize a new cached result. Default the klass of the result to
|
107
|
+
# that of the receiver.
|
108
|
+
def lazy_cache
|
109
|
+
return @lazy_cache if @lazy_cache
|
110
|
+
@lazy_cache = AridCache::CacheProxy::CachedResult.new
|
111
|
+
@lazy_cache.klass = @options[:result_klass]
|
112
|
+
@lazy_cache
|
113
|
+
end
|
114
|
+
|
115
|
+
# Return the result after processing it to apply limits or pagination in memory.
|
116
|
+
# Doesn't do anything if we have to order in the databse.
|
117
|
+
#
|
118
|
+
# Options are only applied if the object responds to the appropriate method.
|
119
|
+
# So for example pagination will not happen unless the object responds to :paginate.
|
120
|
+
#
|
121
|
+
# Options:
|
122
|
+
# :order - Ordering is done first, before applying limits or paginating.
|
123
|
+
# If it's a Proc it is passed to Array#sort to do the sorting.
|
124
|
+
# If it is a Symbol or String the results should be Hashes and the
|
125
|
+
# list of Hashes are sorted by the values at the given key.
|
126
|
+
# :limit - limit the array to the specified size
|
127
|
+
# :offset - ignore the first +offset+ items in the array
|
128
|
+
# :page / :per_page - paginate the result. If :limit is specified, the array is
|
129
|
+
# limited before paginating; similarly if :offset is specified the array is offset
|
130
|
+
# before paginating. Pagination only happens if the :page option is passed.
|
131
|
+
def filter_results(records)
|
132
|
+
return records if order_in_database?
|
133
|
+
|
134
|
+
# Order in memory
|
135
|
+
if records.respond_to?(:sort)
|
136
|
+
if @options.order_by_proc?
|
137
|
+
records = records.sort(&@options[:order])
|
138
|
+
elsif @options.order_by_key? && is_hashes?
|
139
|
+
records = records.sort do |a, b|
|
140
|
+
a[@options[:order]] <=> b[@options[:order]]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Limit / Offset
|
146
|
+
if (@options.include?(:offset) || @options.include?(:limit)) && records.respond_to?(:[])
|
147
|
+
records = records[@options[:offset] || 0, @options[:limit] || records.size]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Paginate
|
151
|
+
if @options.paginate? && records.respond_to?(:paginate)
|
152
|
+
# Convert records to an array before calling paginate. If we don't do this
|
153
|
+
# and the result is a named scope, paginate will trigger an additional query
|
154
|
+
# to load the page rather than just using the records we have already fetched.
|
155
|
+
records = records.respond_to?(:to_a) ? records.to_a : records
|
156
|
+
records = records.paginate(@options.opts_for_paginate(records))
|
157
|
+
end
|
158
|
+
records
|
159
|
+
end
|
160
|
+
|
161
|
+
# Return a list of records from the database. +records+ is a list of
|
162
|
+
# ActiveRecords or a list of ActiveRecord ids.
|
163
|
+
#
|
164
|
+
# If no :order is specified, the current order is preserved with some fancy SQL.
|
165
|
+
# If an arder is specified then
|
166
|
+
# order, limit and paginate in the database.
|
167
|
+
def fetch_activerecords(records)
|
168
|
+
if records.empty?
|
169
|
+
if @options.paginate?
|
170
|
+
return records.paginate(@options.opts_for_paginate(records))
|
171
|
+
else
|
172
|
+
return records
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
ids = records.first.is_a?(ActiveRecord) ? records.collect { |record| record[:id] } : records
|
177
|
+
find_opts = @options.opts_for_find(ids)
|
178
|
+
if order_in_database?
|
179
|
+
if @options.paginate?
|
180
|
+
find_opts.merge!(@options.opts_for_paginate(ids))
|
181
|
+
result_klass.paginate(ids, find_opts)
|
182
|
+
else
|
183
|
+
result_klass.find_all_by_id(ids, find_opts)
|
184
|
+
end
|
185
|
+
else
|
186
|
+
# Limits will have already been applied, remove them from the options for find.
|
187
|
+
[:offset, :limit].each { |key| find_opts.delete(key) }
|
188
|
+
result = result_klass.find_all_by_id(ids, find_opts)
|
189
|
+
records.is_a?(::WillPaginate::Collection) ? records.replace(result) : result
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Return the klass to use for building results (only applies to ActiveRecord results)
|
194
|
+
def result_klass
|
195
|
+
@options[:result_klass] = is_cached_result? ? @result.klass : (@cached.is_a?(AridCache::CacheProxy::CachedResult) ? @cached.klass : Utilities.object_class(@options[:receiver]))
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module AridCache
|
2
|
+
class CacheProxy
|
3
|
+
module Utilities
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# Generate an ORDER BY clause that preserves the ordering of the ids in *ids*.
|
7
|
+
#
|
8
|
+
# The method we use depends on the database adapter because only MySQL
|
9
|
+
# supports the ORDER BY FIELD() function. For other databases we use
|
10
|
+
# a CASE statement.
|
11
|
+
def order_by(ids, klass=nil)
|
12
|
+
column = if klass.respond_to?(:table_name)
|
13
|
+
::ActiveRecord::Base.connection.quote_table_name(klass.table_name) + '.id'
|
14
|
+
else
|
15
|
+
"id"
|
16
|
+
end
|
17
|
+
|
18
|
+
if ids.empty?
|
19
|
+
nil
|
20
|
+
elsif ::ActiveRecord::Base.is_mysql_adapter?
|
21
|
+
"FIELD(#{column},#{ids.join(',')})"
|
22
|
+
else
|
23
|
+
order = ''
|
24
|
+
ids.each_index { |i| order << "WHEN #{column}=#{ids[i]} THEN #{i+1} " }
|
25
|
+
"CASE " + order + " END"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return the object's class or the object if it is a class.
|
30
|
+
def object_class(object)
|
31
|
+
object.is_a?(Class) ? object : object.class
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|