identity_cache 0.0.7 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +3 -2
- data/CHANGELOG +13 -1
- data/Gemfile +2 -2
- data/{Gemfile.rails4 → Gemfile.rails41} +2 -2
- data/Gemfile32 +7 -0
- data/README.md +3 -4
- data/identity_cache.gemspec +3 -2
- data/lib/identity_cache/belongs_to_caching.rb +1 -1
- data/lib/identity_cache/cache_hash.rb +2 -2
- data/lib/identity_cache/cache_invalidation.rb +26 -0
- data/lib/identity_cache/cache_key_generation.rb +3 -2
- data/lib/identity_cache/configuration_dsl.rb +38 -32
- data/lib/identity_cache/query_api.rb +52 -52
- data/lib/identity_cache/version.rb +1 -1
- data/lib/identity_cache.rb +20 -8
- data/performance/cache_runner.rb +7 -4
- data/performance/profile.rb +13 -3
- data/test/attribute_cache_test.rb +13 -4
- data/test/cache_fetch_includes_test.rb +1 -44
- data/test/cache_invalidation_test.rb +52 -0
- data/test/denormalized_has_many_test.rb +4 -4
- data/test/fetch_multi_test.rb +54 -0
- data/test/fetch_multi_with_batched_associations_test.rb +13 -35
- data/test/fetch_test.rb +20 -6
- data/test/fixtures/serialized_record +0 -0
- data/test/helpers/active_record_objects.rb +8 -2
- data/test/helpers/database_connection.rb +8 -5
- data/test/helpers/serialization_format.rb +9 -5
- data/test/helpers/update_serialization_format.rb +1 -0
- data/test/index_cache_test.rb +21 -1
- data/test/memoized_cache_proxy_test.rb +9 -17
- data/test/normalized_belongs_to_test.rb +2 -0
- data/test/normalized_has_many_test.rb +4 -4
- data/test/recursive_denormalized_has_many_test.rb +1 -1
- data/test/schema_change_test.rb +2 -2
- data/test/test_helper.rb +7 -3
- metadata +26 -17
- data/test/helpers/cache.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 477c35f8ea40ea8d71d6937d63ab1842a7321871
|
4
|
+
data.tar.gz: 2729aa2c8c4c06dcf42b8be9f167b88aa79cc81b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: efaef756ca74582dfb7b8669933720f482d95da1493a2f2173e88c8f6cdc79861225858ebced572386e499140f9eb41ecf20a519d4db7c8363e7693141816d81
|
7
|
+
data.tar.gz: e11dff95ae3b78d4d57fd305eb961c4bd21e40e34a7fd08df8cf529369be841a0a75ededa64a47943e7328d10c045cb698744b070d4da2d5301ea0cdb652f61d
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.1.
|
1
|
+
2.1.1
|
data/.travis.yml
CHANGED
data/CHANGELOG
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
Unreleased
|
2
|
+
* Consistently use ActiveRecord / Arel APIs to build SQL queries
|
3
|
+
[related #148]
|
4
|
+
|
5
|
+
* Stop expiring cache on after_touch callback. [backwards incompatible]
|
6
|
+
* fetch_multi accepts an array of keys as argument
|
7
|
+
* Fix: SystemStackError when fetching more records than the max stack size
|
8
|
+
* Fix: Bug in fetch_multi in a transaction where results weren't compacted.
|
9
|
+
* Fix: Avoid unused preload on fetch_multi with :includes option for cache miss
|
10
|
+
* Change :embed option value from false to :ids for cache_has_many for clarity
|
11
|
+
* Fix: reload will invalidate the local instance cache
|
12
|
+
|
1
13
|
0.0.7
|
2
14
|
* Fix: Not implemented error for cache_has_one with embed: false
|
3
15
|
* Fix: cache key to change when adding a cache_has_many association with :embed => false
|
@@ -7,7 +19,7 @@
|
|
7
19
|
0.0.6
|
8
20
|
* Fix: bug where previously nil-cached attribute caches weren't expired on record creation
|
9
21
|
* Fix: cache key to not change when adding a non-embedded association.
|
10
|
-
* Perf: Rails 4 Only create CollectionProxy when using it
|
22
|
+
* Perf: Rails 4 Only create CollectionProxy when using it
|
11
23
|
|
12
24
|
0.0.5
|
13
25
|
|
data/Gemfile
CHANGED
data/Gemfile32
ADDED
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# IdentityCache
|
2
|
-
[![Build Status](https://
|
2
|
+
[![Build Status](https://travis-ci.org/Shopify/identity_cache.svg?branch=master)](https://travis-ci.org/Shopify/identity_cache)
|
3
3
|
|
4
4
|
Opt in read through ActiveRecord caching used in production and extracted from Shopify. IdentityCache lets you specify how you want to cache your model objects, at the model level, and adds a number of convenience methods for accessing those objects through the cache. Memcached is used as the backend cache store, and the database is only hit when a copy of the object cannot be found in Memcached.
|
5
5
|
|
@@ -17,7 +17,6 @@ gem 'cityhash' # optional, for faster hashing (C-Ruby only)
|
|
17
17
|
And then execute:
|
18
18
|
|
19
19
|
$ bundle
|
20
|
-
|
21
20
|
|
22
21
|
|
23
22
|
Add the following to your environment/production.rb:
|
@@ -167,7 +166,7 @@ Example:
|
|
167
166
|
#### cache_has_many
|
168
167
|
|
169
168
|
Options:
|
170
|
-
_[:embed]_
|
169
|
+
_[:embed]_ When true, specifies that the association should be included with the parent when caching. This means the associated objects will be loaded already when the parent is loaded from the cache and will not need to be fetched on their own. When :ids, only the id of the associated records will be included with the parent when caching.
|
171
170
|
|
172
171
|
_[:inverse_name]_ Specifies the name of parent object used by the association. This is useful for polymorphic associations when the association is often named something different between the parent and child objects.
|
173
172
|
|
@@ -177,7 +176,7 @@ Example:
|
|
177
176
|
#### cache_has_one
|
178
177
|
|
179
178
|
Options:
|
180
|
-
_[:embed]_
|
179
|
+
_[:embed]_ When true, specifies that the association should be included with the parent when caching. This means the associated objects will be loaded already when the parent is loaded from the cache and will not need to be fetched on their own. No other values are currently implemented.
|
181
180
|
|
182
181
|
_[:inverse_name]_ Specifies the name of parent object used by the association. This is useful for polymorphic associations when the association is often named something different between the parent and child objects.
|
183
182
|
|
data/identity_cache.gemspec
CHANGED
@@ -15,12 +15,13 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = IdentityCache::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency('ar_transaction_changes', '
|
19
|
-
gem.add_dependency('activerecord', '>= 3.2'
|
18
|
+
gem.add_dependency('ar_transaction_changes', '~> 1.0')
|
19
|
+
gem.add_dependency('activerecord', '>= 3.2')
|
20
20
|
|
21
21
|
gem.add_development_dependency('memcached_store', '~> 0.11.2')
|
22
22
|
gem.add_development_dependency('rake')
|
23
23
|
gem.add_development_dependency('mocha', '0.14.0')
|
24
|
+
gem.add_development_dependency('spy')
|
24
25
|
|
25
26
|
if RUBY_PLATFORM == 'java'
|
26
27
|
gem.add_development_dependency 'jruby-openssl'
|
@@ -24,7 +24,7 @@ module IdentityCache
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def build_normalized_belongs_to_cache(association, options)
|
27
|
-
self.class_eval(
|
27
|
+
self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
28
28
|
def #{options[:cached_accessor_name]}
|
29
29
|
if IdentityCache.should_cache? && #{options[:foreign_key]}.present? && !association(:#{association}).loaded?
|
30
30
|
self.#{association} = #{options[:association_class]}.fetch_by_id(#{options[:foreign_key]})
|
@@ -21,12 +21,12 @@ module IdentityCache
|
|
21
21
|
module CacheHash
|
22
22
|
|
23
23
|
if defined?(CityHash)
|
24
|
-
|
24
|
+
|
25
25
|
def memcache_hash(key) #:nodoc:
|
26
26
|
CityHash.hash64(key)
|
27
27
|
end
|
28
28
|
else
|
29
|
-
|
29
|
+
|
30
30
|
def memcache_hash(key) #:nodoc:
|
31
31
|
a = Digest::MD5.digest(key).unpack('LL')
|
32
32
|
(a[0] << 32) | a[1]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module IdentityCache
|
2
|
+
module CacheInvalidation
|
3
|
+
|
4
|
+
CACHE_KEY_NAMES = [:ids_variable_name, :records_variable_name]
|
5
|
+
|
6
|
+
def reload(*)
|
7
|
+
clear_cached_associations
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def clear_cached_associations
|
14
|
+
self.class.send(:all_cached_associations).each do |_, data|
|
15
|
+
CACHE_KEY_NAMES.each do |key|
|
16
|
+
if data[key]
|
17
|
+
instance_variable_name = "@#{data[key]}"
|
18
|
+
if instance_variable_defined?(instance_variable_name)
|
19
|
+
remove_instance_variable(instance_variable_name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -11,9 +11,10 @@ module IdentityCache
|
|
11
11
|
schema_string = schema_to_string(klass.columns)
|
12
12
|
if klass.include?(IdentityCache)
|
13
13
|
klass.send(:all_cached_associations).sort.each do |name, options|
|
14
|
-
|
14
|
+
case options[:embed]
|
15
|
+
when true
|
15
16
|
schema_string << ",#{name}:(#{denormalized_schema_hash(options[:association_class])})"
|
16
|
-
|
17
|
+
when :ids
|
17
18
|
schema_string << ",#{name}:ids"
|
18
19
|
end
|
19
20
|
end
|
@@ -88,34 +88,35 @@ module IdentityCache
|
|
88
88
|
#
|
89
89
|
# == Options
|
90
90
|
#
|
91
|
-
# * embed: If set will cause IdentityCache to keep the
|
92
|
-
# association in the same cache entry as the parent,
|
91
|
+
# * embed: If set to true, will cause IdentityCache to keep the
|
92
|
+
# values for this association in the same cache entry as the parent,
|
93
|
+
# instead of its own.
|
93
94
|
# * inverse_name: The name of the parent in the association if the name is
|
94
95
|
# not the lowercase pluralization of the parent object's class
|
95
96
|
def cache_has_many(association, options = {})
|
96
|
-
options[:embed]
|
97
|
+
options[:embed] = :ids unless options.has_key?(:embed)
|
98
|
+
deprecate_embed_option(options, false, :ids)
|
97
99
|
options[:inverse_name] ||= self.name.underscore.to_sym
|
98
100
|
raise InverseAssociationError unless self.reflect_on_association(association)
|
99
101
|
self.cached_has_manys[association] = options
|
100
102
|
|
101
|
-
|
102
|
-
|
103
|
+
case options[:embed]
|
104
|
+
when true
|
105
|
+
build_recursive_association_cache(association, options)
|
106
|
+
when :ids
|
107
|
+
build_id_embedded_has_many_cache(association, options)
|
103
108
|
else
|
104
|
-
|
109
|
+
raise NotImplementedError
|
105
110
|
end
|
106
111
|
end
|
107
112
|
|
108
113
|
# Will cache an association to the class including IdentityCache.
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
# Embedded associations are more effective in offloading database work,
|
113
|
-
# however they will increase the size of the cache entries and make the
|
114
|
-
# whole entry expire with the change of any of the embedded members
|
114
|
+
# IdentityCache will keep the association values in the same cache entry
|
115
|
+
# as the parent.
|
115
116
|
#
|
116
117
|
# == Example:
|
117
118
|
# class Product
|
118
|
-
# cached_has_one :store, :embed =>
|
119
|
+
# cached_has_one :store, :embed => true
|
119
120
|
# cached_has_one :vendor
|
120
121
|
# end
|
121
122
|
#
|
@@ -124,8 +125,9 @@ module IdentityCache
|
|
124
125
|
#
|
125
126
|
# == Options
|
126
127
|
#
|
127
|
-
# * embed:
|
128
|
-
#
|
128
|
+
# * embed: Only true is supported, which is also the default, so
|
129
|
+
# IdentityCache will keep the values for this association in the same
|
130
|
+
# cache entry as the parent, instead of its own.
|
129
131
|
# * inverse_name: The name of the parent in the association ( only
|
130
132
|
# necessary if the name is not the lowercase pluralization of the
|
131
133
|
# parent object's class)
|
@@ -135,8 +137,8 @@ module IdentityCache
|
|
135
137
|
raise InverseAssociationError unless self.reflect_on_association(association)
|
136
138
|
self.cached_has_ones[association] = options
|
137
139
|
|
138
|
-
if options[:embed]
|
139
|
-
|
140
|
+
if options[:embed] == true
|
141
|
+
build_recursive_association_cache(association, options)
|
140
142
|
else
|
141
143
|
raise NotImplementedError
|
142
144
|
end
|
@@ -181,9 +183,8 @@ module IdentityCache
|
|
181
183
|
private
|
182
184
|
|
183
185
|
def identity_cache_single_value_dynamic_fetcher(fields, values) # :nodoc:
|
184
|
-
sql_on_miss = "SELECT #{quoted_primary_key} FROM #{quoted_table_name} WHERE #{identity_cache_sql_conditions(fields, values)} LIMIT 1"
|
185
186
|
cache_key = rails_cache_index_key_for_fields_and_values(fields, values)
|
186
|
-
id = IdentityCache.fetch(cache_key) {
|
187
|
+
id = IdentityCache.fetch(cache_key) { identity_cache_conditions(fields, values).limit(1).pluck(primary_key).first }
|
187
188
|
unless id.nil?
|
188
189
|
record = fetch_by_id(id)
|
189
190
|
IdentityCache.cache.delete(cache_key) unless record
|
@@ -193,28 +194,27 @@ module IdentityCache
|
|
193
194
|
end
|
194
195
|
|
195
196
|
def identity_cache_multiple_value_dynamic_fetcher(fields, values) # :nodoc
|
196
|
-
sql_on_miss = "SELECT #{quoted_primary_key} FROM #{quoted_table_name} WHERE #{identity_cache_sql_conditions(fields, values)}"
|
197
197
|
cache_key = rails_cache_index_key_for_fields_and_values(fields, values)
|
198
|
-
ids = IdentityCache.fetch(cache_key) {
|
198
|
+
ids = IdentityCache.fetch(cache_key) { identity_cache_conditions(fields, values).pluck(primary_key) }
|
199
199
|
|
200
|
-
ids.empty? ? [] : fetch_multi(
|
200
|
+
ids.empty? ? [] : fetch_multi(ids)
|
201
201
|
end
|
202
202
|
|
203
|
-
def
|
203
|
+
def build_recursive_association_cache(association, options) #:nodoc:
|
204
204
|
options[:association_class] ||= reflect_on_association(association).klass
|
205
205
|
options[:cached_accessor_name] ||= "fetch_#{association}"
|
206
|
-
options[:records_variable_name]
|
206
|
+
options[:records_variable_name] ||= "cached_#{association}"
|
207
207
|
options[:population_method_name] ||= "populate_#{association}_cache"
|
208
208
|
|
209
209
|
|
210
210
|
unless instance_methods.include?(options[:cached_accessor_name].to_sym)
|
211
211
|
self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
212
212
|
def #{options[:cached_accessor_name]}
|
213
|
-
|
213
|
+
fetch_recursively_cached_association('#{options[:records_variable_name]}', :#{association})
|
214
214
|
end
|
215
215
|
|
216
216
|
def #{options[:population_method_name]}
|
217
|
-
|
217
|
+
populate_recursively_cached_association('#{options[:records_variable_name]}', :#{association})
|
218
218
|
end
|
219
219
|
CODE
|
220
220
|
|
@@ -222,7 +222,7 @@ module IdentityCache
|
|
222
222
|
end
|
223
223
|
end
|
224
224
|
|
225
|
-
def
|
225
|
+
def build_id_embedded_has_many_cache(association, options) #:nodoc:
|
226
226
|
singular_association = association.to_s.singularize
|
227
227
|
options[:association_class] ||= reflect_on_association(association).klass
|
228
228
|
options[:cached_accessor_name] ||= "fetch_#{association}"
|
@@ -249,7 +249,7 @@ module IdentityCache
|
|
249
249
|
def #{options[:cached_accessor_name]}
|
250
250
|
if IdentityCache.should_cache? || #{association}.loaded?
|
251
251
|
#{options[:population_method_name]} unless @#{options[:ids_variable_name]} || @#{options[:records_variable_name]}
|
252
|
-
@#{options[:records_variable_name]} ||= #{options[:association_class]}.fetch_multi(
|
252
|
+
@#{options[:records_variable_name]} ||= #{options[:association_class]}.fetch_multi(@#{options[:ids_variable_name]})
|
253
253
|
else
|
254
254
|
#{association}
|
255
255
|
end
|
@@ -265,8 +265,7 @@ module IdentityCache
|
|
265
265
|
|
266
266
|
def attribute_dynamic_fetcher(attribute, fields, values) #:nodoc:
|
267
267
|
cache_key = rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, values)
|
268
|
-
|
269
|
-
IdentityCache.fetch(cache_key) { connection.select_value(sql_on_miss) }
|
268
|
+
IdentityCache.fetch(cache_key) { identity_cache_conditions(fields, values).limit(1).pluck(attribute).first }
|
270
269
|
end
|
271
270
|
|
272
271
|
def add_parent_expiry_hook(options)
|
@@ -289,8 +288,15 @@ module IdentityCache
|
|
289
288
|
CODE
|
290
289
|
end
|
291
290
|
|
292
|
-
def
|
293
|
-
|
291
|
+
def identity_cache_conditions(fields, values)
|
292
|
+
reorder(nil).where(Hash[fields.zip(values)])
|
293
|
+
end
|
294
|
+
|
295
|
+
def deprecate_embed_option(options, old_value, new_value)
|
296
|
+
if options[:embed] == old_value
|
297
|
+
options[:embed] = new_value
|
298
|
+
ActiveSupport::Deprecation.warn("`embed: #{old_value.inspect}` was renamed to `embed: #{new_value.inspect}` for clarity", caller(2))
|
299
|
+
end
|
294
300
|
end
|
295
301
|
end
|
296
302
|
end
|
@@ -4,7 +4,9 @@ module IdentityCache
|
|
4
4
|
|
5
5
|
included do |base|
|
6
6
|
base.after_commit :expire_cache
|
7
|
-
|
7
|
+
if Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("4.0.4")
|
8
|
+
base.after_touch :expire_cache
|
9
|
+
end
|
8
10
|
end
|
9
11
|
|
10
12
|
module ClassMethods
|
@@ -31,7 +33,7 @@ module IdentityCache
|
|
31
33
|
end
|
32
34
|
|
33
35
|
else
|
34
|
-
self.where(
|
36
|
+
self.reorder(nil).where(primary_key => id).first
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
@@ -47,28 +49,30 @@ module IdentityCache
|
|
47
49
|
def fetch_multi(*ids)
|
48
50
|
raise NotImplementedError, "fetching needs the primary index enabled" unless primary_cache_index_enabled
|
49
51
|
options = ids.extract_options!
|
50
|
-
|
51
|
-
|
52
|
+
ids.flatten!(1)
|
53
|
+
records = if IdentityCache.should_cache?
|
52
54
|
require_if_necessary do
|
53
55
|
cache_keys = ids.map {|id| rails_cache_key(id) }
|
54
56
|
key_to_id_map = Hash[ cache_keys.zip(ids) ]
|
57
|
+
key_to_record_map = {}
|
55
58
|
|
56
|
-
coders_by_key = IdentityCache.fetch_multi(
|
59
|
+
coders_by_key = IdentityCache.fetch_multi(cache_keys) do |unresolved_keys|
|
57
60
|
ids = unresolved_keys.map {|key| key_to_id_map[key] }
|
58
|
-
records = find_batch(ids
|
59
|
-
records.compact
|
61
|
+
records = find_batch(ids)
|
62
|
+
found_records = records.compact
|
63
|
+
found_records.each{ |record| record.send(:populate_association_caches) }
|
64
|
+
key_to_record_map = found_records.index_by{ |record| rails_cache_key(record.id) }
|
60
65
|
records.map {|record| coder_from_record(record) }
|
61
66
|
end
|
62
67
|
|
63
|
-
|
64
|
-
prefetch_associations(options[:includes], records) if options[:includes]
|
65
|
-
|
66
|
-
records
|
68
|
+
cache_keys.map{ |key| key_to_record_map[key] || record_from_coder(coders_by_key[key]) }
|
67
69
|
end
|
68
|
-
|
69
70
|
else
|
70
|
-
find_batch(ids
|
71
|
+
find_batch(ids)
|
71
72
|
end
|
73
|
+
records.compact!
|
74
|
+
prefetch_associations(options[:includes], records) if options[:includes]
|
75
|
+
records
|
72
76
|
end
|
73
77
|
|
74
78
|
private
|
@@ -114,7 +118,7 @@ module IdentityCache
|
|
114
118
|
else
|
115
119
|
record_from_coder(coder_or_array)
|
116
120
|
end
|
117
|
-
variable_name = record.class.send(:
|
121
|
+
variable_name = record.class.send(:recursively_embedded_associations)[association_name][:records_variable_name]
|
118
122
|
record.instance_variable_set(:"@#{variable_name}", IdentityCache.map_cached_nil_for(value))
|
119
123
|
end
|
120
124
|
|
@@ -139,14 +143,17 @@ module IdentityCache
|
|
139
143
|
end
|
140
144
|
|
141
145
|
def add_cached_associations_to_coder(record, coder)
|
142
|
-
|
143
|
-
|
144
|
-
|
146
|
+
klass = record.class
|
147
|
+
if klass.include?(IdentityCache)
|
148
|
+
if (recursively_embedded_associations = klass.send(:recursively_embedded_associations)).present?
|
149
|
+
coder[:associations] = recursively_embedded_associations.each_with_object({}) do |(name, options), hash|
|
150
|
+
hash[name] = IdentityCache.map_cached_nil_for(get_embedded_association(record, name, options))
|
151
|
+
end
|
145
152
|
end
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
153
|
+
if (cached_has_manys = klass.cached_has_manys).present?
|
154
|
+
coder[:normalized_has_many] = cached_has_manys.each_with_object({}) do |(name, options), hash|
|
155
|
+
hash[name] = record.instance_variable_get(:"@#{options[:ids_variable_name]}") unless options[:embed] == true
|
156
|
+
end
|
150
157
|
end
|
151
158
|
end
|
152
159
|
end
|
@@ -170,14 +177,14 @@ module IdentityCache
|
|
170
177
|
end
|
171
178
|
|
172
179
|
def resolve_cache_miss(id)
|
173
|
-
object = self.includes(cache_fetch_includes).where(
|
180
|
+
object = self.includes(cache_fetch_includes).reorder(nil).where(primary_key => id).try(:first)
|
174
181
|
object.send(:populate_association_caches) if object
|
175
182
|
object
|
176
183
|
end
|
177
184
|
|
178
|
-
def
|
185
|
+
def recursively_embedded_associations
|
179
186
|
all_cached_associations.select do |cached_association, options|
|
180
|
-
options[:embed]
|
187
|
+
options[:embed] == true
|
181
188
|
end
|
182
189
|
end
|
183
190
|
|
@@ -185,23 +192,19 @@ module IdentityCache
|
|
185
192
|
(cached_has_manys || {}).merge(cached_has_ones || {}).merge(cached_belongs_tos || {})
|
186
193
|
end
|
187
194
|
|
188
|
-
def
|
195
|
+
def embedded_associations
|
189
196
|
all_cached_associations.select do |cached_association, options|
|
190
|
-
options[:
|
197
|
+
options[:embed]
|
191
198
|
end
|
192
199
|
end
|
193
200
|
|
194
|
-
def cache_fetch_includes
|
195
|
-
|
196
|
-
embedded_associations = all_cached_associations.select { |name, options| options[:embed] }
|
197
|
-
|
198
|
-
associations_for_identity_cache = embedded_associations.map do |child_association, options|
|
201
|
+
def cache_fetch_includes
|
202
|
+
associations_for_identity_cache = recursively_embedded_associations.map do |child_association, options|
|
199
203
|
child_class = reflect_on_association(child_association).try(:klass)
|
200
204
|
|
201
|
-
child_includes =
|
202
|
-
|
205
|
+
child_includes = nil
|
203
206
|
if child_class.respond_to?(:cache_fetch_includes, true)
|
204
|
-
child_includes = child_class.send(:cache_fetch_includes
|
207
|
+
child_includes = child_class.send(:cache_fetch_includes)
|
205
208
|
end
|
206
209
|
|
207
210
|
if child_includes.blank?
|
@@ -211,19 +214,15 @@ module IdentityCache
|
|
211
214
|
end
|
212
215
|
end
|
213
216
|
|
214
|
-
associations_for_identity_cache.push(additions) if additions.keys.size > 0
|
215
217
|
associations_for_identity_cache.compact
|
216
218
|
end
|
217
219
|
|
218
|
-
def find_batch(ids
|
219
|
-
@id_column ||= columns.detect {|c| c.name ==
|
220
|
+
def find_batch(ids)
|
221
|
+
@id_column ||= columns.detect {|c| c.name == primary_key}
|
220
222
|
ids = ids.map{ |id| @id_column.type_cast(id) }
|
221
|
-
records = where(
|
223
|
+
records = where(primary_key => ids).includes(cache_fetch_includes).to_a
|
222
224
|
records_by_id = records.index_by(&:id)
|
223
|
-
|
224
|
-
mismatching_ids = records.compact.map(&:id) - ids
|
225
|
-
IdentityCache.logger.error "[IDC id mismatch] fetch_batch_requested=#{ids.inspect} fetch_batch_got=#{mismatchig_ids.inspect} mismatching ids " unless mismatching_ids.empty?
|
226
|
-
records
|
225
|
+
ids.map{ |id| records_by_id[id] }
|
227
226
|
end
|
228
227
|
|
229
228
|
def prefetch_associations(associations, records)
|
@@ -233,7 +232,7 @@ module IdentityCache
|
|
233
232
|
case
|
234
233
|
when details = cached_has_manys[association]
|
235
234
|
|
236
|
-
if details[:embed]
|
235
|
+
if details[:embed] == true
|
237
236
|
child_records = records.map(&details[:cached_accessor_name].to_sym).flatten
|
238
237
|
else
|
239
238
|
ids_to_parent_record = records.each_with_object({}) do |record, hash|
|
@@ -258,14 +257,14 @@ module IdentityCache
|
|
258
257
|
next_level_records = child_records
|
259
258
|
|
260
259
|
when details = cached_belongs_tos[association]
|
261
|
-
if details[:embed]
|
260
|
+
if details[:embed] == true
|
262
261
|
raise ArgumentError.new("Embedded belongs_to associations do not support prefetching yet.")
|
263
262
|
else
|
264
263
|
ids_to_child_record = records.each_with_object({}) do |child_record, hash|
|
265
264
|
parent_id = child_record.send(details[:foreign_key])
|
266
265
|
hash[parent_id] = child_record if parent_id.present?
|
267
266
|
end
|
268
|
-
parent_records = details[:association_class].fetch_multi(
|
267
|
+
parent_records = details[:association_class].fetch_multi(ids_to_child_record.keys)
|
269
268
|
parent_records.each do |parent_record|
|
270
269
|
child_record = ids_to_child_record[parent_record.id]
|
271
270
|
child_record.send(details[:prepopulate_method_name], parent_record)
|
@@ -275,7 +274,7 @@ module IdentityCache
|
|
275
274
|
next_level_records = parent_records
|
276
275
|
|
277
276
|
when details = cached_has_ones[association]
|
278
|
-
if details[:embed]
|
277
|
+
if details[:embed] == true
|
279
278
|
parent_records = records.map(&details[:cached_accessor_name].to_sym)
|
280
279
|
else
|
281
280
|
raise ArgumentError.new("Non-embedded has_one associations do not support prefetching yet.")
|
@@ -317,9 +316,9 @@ module IdentityCache
|
|
317
316
|
private
|
318
317
|
|
319
318
|
def populate_association_caches # :nodoc:
|
320
|
-
self.class.send(:
|
319
|
+
self.class.send(:embedded_associations).each do |cached_association, options|
|
321
320
|
send(options[:population_method_name])
|
322
|
-
reflection = options[:embed] && self.class.reflect_on_association(cached_association)
|
321
|
+
reflection = options[:embed] == true && self.class.reflect_on_association(cached_association)
|
323
322
|
if reflection && reflection.klass.respond_to?(:cached_has_manys)
|
324
323
|
child_objects = Array.wrap(send(options[:cached_accessor_name]))
|
325
324
|
child_objects.each{ |child| child.send(:populate_association_caches) }
|
@@ -327,10 +326,10 @@ module IdentityCache
|
|
327
326
|
end
|
328
327
|
end
|
329
328
|
|
330
|
-
def
|
329
|
+
def fetch_recursively_cached_association(ivar_name, association_name) # :nodoc:
|
331
330
|
ivar_full_name = :"@#{ivar_name}"
|
332
331
|
if IdentityCache.should_cache?
|
333
|
-
|
332
|
+
populate_recursively_cached_association(ivar_name, association_name)
|
334
333
|
assoc = IdentityCache.unmap_cached_nil_for(instance_variable_get(ivar_full_name))
|
335
334
|
assoc.is_a?(ActiveRecord::Associations::CollectionAssociation) ? assoc.reader : assoc
|
336
335
|
else
|
@@ -338,7 +337,7 @@ module IdentityCache
|
|
338
337
|
end
|
339
338
|
end
|
340
339
|
|
341
|
-
def
|
340
|
+
def populate_recursively_cached_association(ivar_name, association_name) # :nodoc:
|
342
341
|
ivar_full_name = :"@#{ivar_name}"
|
343
342
|
|
344
343
|
value = instance_variable_get(ivar_full_name)
|
@@ -405,7 +404,8 @@ module IdentityCache
|
|
405
404
|
end
|
406
405
|
|
407
406
|
def was_new_record? # :nodoc:
|
408
|
-
|
407
|
+
pk = self.class.primary_key
|
408
|
+
!destroyed? && transaction_changed_attributes.has_key?(pk) && transaction_changed_attributes[pk].nil?
|
409
409
|
end
|
410
410
|
end
|
411
411
|
end
|