identity_cache 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +2 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile +2 -0
- data/Gemfile.rails40 +1 -0
- data/Gemfile.rails41 +1 -0
- data/Gemfile.rails42 +1 -0
- data/README.md +7 -0
- data/identity_cache.gemspec +1 -1
- data/lib/identity_cache.rb +5 -2
- data/lib/identity_cache/belongs_to_caching.rb +13 -5
- data/lib/identity_cache/cache_key_generation.rb +12 -19
- data/lib/identity_cache/configuration_dsl.rb +83 -84
- data/lib/identity_cache/parent_model_expiration.rb +4 -3
- data/lib/identity_cache/query_api.rb +93 -91
- data/lib/identity_cache/version.rb +2 -2
- data/test/attribute_cache_test.rb +42 -63
- data/test/deeply_nested_associated_record_test.rb +1 -0
- data/test/denormalized_has_many_test.rb +18 -0
- data/test/denormalized_has_one_test.rb +15 -5
- data/test/fetch_multi_test.rb +25 -3
- data/test/fetch_test.rb +20 -7
- data/test/fixtures/serialized_record.mysql2 +0 -0
- data/test/fixtures/serialized_record.postgresql +0 -0
- data/test/helpers/active_record_objects.rb +10 -0
- data/test/helpers/database_connection.rb +6 -1
- data/test/helpers/serialization_format.rb +1 -1
- data/test/index_cache_test.rb +50 -25
- data/test/normalized_belongs_to_test.rb +21 -6
- data/test/normalized_has_many_test.rb +44 -0
- data/test/{fetch_multi_with_batched_associations_test.rb → prefetch_normalized_associations_test.rb} +41 -3
- data/test/save_test.rb +14 -14
- data/test/schema_change_test.rb +2 -0
- data/test/test_helper.rb +4 -4
- metadata +11 -10
- data/Gemfile.rails32 +0 -5
- data/test/fixtures/serialized_record +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9c2e392201ed1b9c143a6ebff82f7c7af602a89
|
4
|
+
data.tar.gz: 445f3ce67f61a386db9bd99939d169e03562052b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3012a594fb51bcd37c08e322545b4cf3d9e2fe8ce3d14253df79cb423fd673f143e7dea5b8c7b13499660c56db9e266d3bc0fe6e923a6f1c37a918ba5d63fa4
|
7
|
+
data.tar.gz: a790523fd6989dde3669602a4ddd045032c44808f7e1f6a06f3e01fbf93e9ac137377e77fd360266a11642fdf7c6d5ef21ec1cb157a1d4ea37e2bde88dc5612c
|
data/.travis.yml
CHANGED
@@ -2,7 +2,7 @@ language: ruby
|
|
2
2
|
|
3
3
|
rvm:
|
4
4
|
- 2.1
|
5
|
-
- 2.2
|
5
|
+
- 2.2.3
|
6
6
|
|
7
7
|
gemfile:
|
8
8
|
- Gemfile.rails40
|
@@ -23,6 +23,4 @@ before_script:
|
|
23
23
|
- mysql -e 'create database identity_cache_test'
|
24
24
|
- psql -c 'create database identity_cache_test;' -U postgres
|
25
25
|
|
26
|
-
|
27
|
-
allow_failures:
|
28
|
-
- gemfile: Gemfile.rails42
|
26
|
+
cache: bundler
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
# IdentityCache changelog
|
2
2
|
|
3
|
+
#### 0.2.6 (unreleased)
|
4
|
+
- Add support for includes option on cache_index and fetch_by_id
|
5
|
+
- Use ActiveRecord instantiate
|
6
|
+
- Add association pre-fetching support for fetch_by_id
|
7
|
+
- Remove support for 3.2
|
8
|
+
- Fix N+1 from fetching embedded ids on a cache miss
|
9
|
+
- Raise when trying to cache a through association. Previously it wouldn't be invalidated properly.
|
10
|
+
- Raise if a class method is called on a scope. Previously the scope was ignored.
|
11
|
+
- Raise if a class method is called on a subclass of one that included IdentityCache. This never worked properly.
|
12
|
+
- Fix cache_belongs_to on polymorphic assocations.
|
13
|
+
- Fetching a cache_belongs_to association no longer loads the belongs_to association
|
14
|
+
|
15
|
+
#### 0.2.5
|
16
|
+
|
17
|
+
- Fixed support for namespaced model classes
|
18
|
+
- Added some deduplication for parent cache expiry
|
19
|
+
- Fixed some deprecation warnings in rails 4.2
|
20
|
+
|
21
|
+
#### 0.2.4
|
22
|
+
|
23
|
+
- Refactoring, documentation and test changes
|
24
|
+
|
3
25
|
#### 0.2.3
|
4
26
|
|
5
27
|
- PostgreSQL support
|
data/Gemfile
CHANGED
data/Gemfile.rails40
CHANGED
data/Gemfile.rails41
CHANGED
data/Gemfile.rails42
CHANGED
data/README.md
CHANGED
@@ -125,7 +125,9 @@ IdentityCache tries to figure out both sides of an association whenever it can s
|
|
125
125
|
|
126
126
|
``` ruby
|
127
127
|
class Metafield < ActiveRecord::Base
|
128
|
+
include IdentityCache
|
128
129
|
belongs_to :owner, :polymorphic => true
|
130
|
+
cache_belongs_to :owner
|
129
131
|
end
|
130
132
|
|
131
133
|
class Product < ActiveRecord::Base
|
@@ -183,6 +185,11 @@ _[:inverse_name]_ Specifies the name of parent object used by the association. T
|
|
183
185
|
Example:
|
184
186
|
`cache_has_one :configuration, :embed => true`
|
185
187
|
|
188
|
+
#### cache_belongs_to
|
189
|
+
|
190
|
+
Example:
|
191
|
+
`cache_belongs_to :shop`
|
192
|
+
|
186
193
|
#### cache_attribute
|
187
194
|
|
188
195
|
Options:
|
data/identity_cache.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.version = IdentityCache::VERSION
|
17
17
|
|
18
18
|
gem.add_dependency('ar_transaction_changes', '~> 1.0')
|
19
|
-
gem.add_dependency('activerecord', '>=
|
19
|
+
gem.add_dependency('activerecord', '>= 4.0.4')
|
20
20
|
gem.add_development_dependency('memcached', '~> 1.8.0')
|
21
21
|
|
22
22
|
gem.add_development_dependency('memcached_store', '~> 0.12.6')
|
data/lib/identity_cache.rb
CHANGED
@@ -27,6 +27,9 @@ module IdentityCache
|
|
27
27
|
super "Inverse name for association could not be determined. Please use the :inverse_name option to specify the inverse association name for this cache."
|
28
28
|
end
|
29
29
|
end
|
30
|
+
class UnsupportedScopeError < StandardError; end
|
31
|
+
class UnsupportedAssociationError < StandardError; end
|
32
|
+
class DerivedModelError < StandardError; end
|
30
33
|
|
31
34
|
class << self
|
32
35
|
include IdentityCache::CacheHash
|
@@ -38,7 +41,7 @@ module IdentityCache
|
|
38
41
|
self.cache_namespace = "IDC:#{CACHE_VERSION}:".freeze
|
39
42
|
|
40
43
|
def included(base) #:nodoc:
|
41
|
-
raise AlreadyIncludedError if base.
|
44
|
+
raise AlreadyIncludedError if base.include?(IdentityCache::ConfigurationDSL)
|
42
45
|
|
43
46
|
base.send(:include, ArTransactionChanges) unless base.include?(ArTransactionChanges)
|
44
47
|
base.send(:include, IdentityCache::BelongsToCaching)
|
@@ -112,7 +115,7 @@ module IdentityCache
|
|
112
115
|
return {} if keys.size == 0
|
113
116
|
|
114
117
|
result = if should_use_cache?
|
115
|
-
fetch_in_batches(keys) do |missed_keys|
|
118
|
+
fetch_in_batches(keys.uniq) do |missed_keys|
|
116
119
|
results = yield missed_keys
|
117
120
|
results.map {|e| map_cached_nil_for e }
|
118
121
|
end
|
@@ -9,6 +9,7 @@ module IdentityCache
|
|
9
9
|
|
10
10
|
module ClassMethods
|
11
11
|
def cache_belongs_to(association, options = {})
|
12
|
+
ensure_base_model
|
12
13
|
raise NotImplementedError if options[:embed]
|
13
14
|
|
14
15
|
unless association_reflection = reflect_on_association(association)
|
@@ -20,25 +21,32 @@ module IdentityCache
|
|
20
21
|
|
21
22
|
options[:embed] = false
|
22
23
|
options[:cached_accessor_name] = "fetch_#{association}"
|
23
|
-
options[:
|
24
|
-
options[:
|
24
|
+
options[:records_variable_name] = "cached_#{association}"
|
25
|
+
options[:association_reflection] = association_reflection
|
25
26
|
options[:prepopulate_method_name] = "prepopulate_fetched_#{association}"
|
26
27
|
|
27
28
|
build_normalized_belongs_to_cache(association, options)
|
28
29
|
end
|
29
30
|
|
31
|
+
private
|
32
|
+
|
30
33
|
def build_normalized_belongs_to_cache(association, options)
|
34
|
+
foreign_key = options[:association_reflection].foreign_key
|
31
35
|
self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
32
36
|
def #{options[:cached_accessor_name]}
|
33
|
-
if IdentityCache.should_use_cache? && #{
|
34
|
-
|
37
|
+
if IdentityCache.should_use_cache? && #{foreign_key}.present? && !association(:#{association}).loaded?
|
38
|
+
if instance_variable_defined?(:@#{options[:records_variable_name]})
|
39
|
+
@#{options[:records_variable_name]}
|
40
|
+
else
|
41
|
+
@#{options[:records_variable_name]} = association(:#{association}).klass.fetch_by_id(#{foreign_key})
|
42
|
+
end
|
35
43
|
else
|
36
44
|
#{association}
|
37
45
|
end
|
38
46
|
end
|
39
47
|
|
40
48
|
def #{options[:prepopulate_method_name]}(record)
|
41
|
-
|
49
|
+
@#{options[:records_variable_name]} = record
|
42
50
|
end
|
43
51
|
CODE
|
44
52
|
end
|
@@ -13,7 +13,7 @@ module IdentityCache
|
|
13
13
|
klass.send(:all_cached_associations).sort.each do |name, options|
|
14
14
|
case options[:embed]
|
15
15
|
when true
|
16
|
-
schema_string << ",#{name}:(#{denormalized_schema_hash(options[:
|
16
|
+
schema_string << ",#{name}:(#{denormalized_schema_hash(options[:association_reflection].klass)})"
|
17
17
|
when :ids
|
18
18
|
schema_string << ",#{name}:ids"
|
19
19
|
end
|
@@ -35,12 +35,13 @@ module IdentityCache
|
|
35
35
|
"#{rails_cache_key_namespace}blob:#{base_class.name}:#{rails_cache_key_prefix}:"
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
def rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, values, unique)
|
39
|
+
unique_indicator = unique ? '' : 's'
|
40
|
+
"#{rails_cache_key_namespace}" \
|
41
|
+
"attr#{unique_indicator}" \
|
42
|
+
":#{base_class.name}" \
|
43
|
+
":#{attribute}" \
|
44
|
+
":#{rails_cache_string_for_fields_and_values(fields, values)}"
|
44
45
|
end
|
45
46
|
|
46
47
|
def rails_cache_key_namespace
|
@@ -58,20 +59,12 @@ module IdentityCache
|
|
58
59
|
self.class.rails_cache_key(id)
|
59
60
|
end
|
60
61
|
|
61
|
-
def
|
62
|
-
self.class.
|
63
|
-
end
|
64
|
-
|
65
|
-
def secondary_cache_index_key_for_previous_values(fields) # :nodoc:
|
66
|
-
self.class.rails_cache_index_key_for_fields_and_values(fields, old_values_for_fields(fields))
|
67
|
-
end
|
68
|
-
|
69
|
-
def attribute_cache_key_for_attribute_and_current_values(attribute, fields) # :nodoc:
|
70
|
-
self.class.rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, current_values_for_fields(fields))
|
62
|
+
def attribute_cache_key_for_attribute_and_current_values(attribute, fields, unique) # :nodoc:
|
63
|
+
self.class.rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, current_values_for_fields(fields), unique)
|
71
64
|
end
|
72
65
|
|
73
|
-
def attribute_cache_key_for_attribute_and_previous_values(attribute, fields) # :nodoc:
|
74
|
-
self.class.rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, old_values_for_fields(fields))
|
66
|
+
def attribute_cache_key_for_attribute_and_previous_values(attribute, fields, unique) # :nodoc:
|
67
|
+
self.class.rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, old_values_for_fields(fields), unique)
|
75
68
|
end
|
76
69
|
|
77
70
|
def current_values_for_fields(fields) # :nodoc:
|
@@ -3,15 +3,15 @@ module IdentityCache
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do |base|
|
6
|
+
base.class_attribute :cached_model
|
6
7
|
base.class_attribute :cache_indexes
|
7
|
-
base.class_attribute :cache_attributes
|
8
8
|
base.class_attribute :cached_has_manys
|
9
9
|
base.class_attribute :cached_has_ones
|
10
10
|
base.class_attribute :primary_cache_index_enabled
|
11
11
|
|
12
|
+
base.cached_model = base
|
12
13
|
base.cached_has_manys = {}
|
13
14
|
base.cached_has_ones = {}
|
14
|
-
base.cache_attributes = []
|
15
15
|
base.cache_indexes = []
|
16
16
|
base.primary_cache_index_enabled = true
|
17
17
|
end
|
@@ -42,26 +42,29 @@ module IdentityCache
|
|
42
42
|
def cache_index(*fields)
|
43
43
|
raise NotImplementedError, "Cache indexes need an enabled primary index" unless primary_cache_index_enabled
|
44
44
|
options = fields.extract_options!
|
45
|
-
|
45
|
+
unique = options[:unique] || false
|
46
|
+
cache_attribute_by_alias('primary_key', 'id', by: fields, unique: unique)
|
46
47
|
|
47
48
|
field_list = fields.join("_and_")
|
48
49
|
arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
|
49
50
|
|
50
|
-
if
|
51
|
+
if unique
|
51
52
|
self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__ + 1)
|
52
|
-
def fetch_by_#{field_list}(#{arg_list})
|
53
|
-
|
53
|
+
def fetch_by_#{field_list}(#{arg_list}, options={})
|
54
|
+
id = fetch_#{primary_key}_by_#{field_list}(#{arg_list})
|
55
|
+
id && fetch_by_id(id, options)
|
54
56
|
end
|
55
57
|
|
56
58
|
# exception throwing variant
|
57
|
-
def fetch_by_#{field_list}!(#{arg_list})
|
58
|
-
fetch_by_#{field_list}(#{arg_list}) or raise ActiveRecord::RecordNotFound
|
59
|
+
def fetch_by_#{field_list}!(#{arg_list}, options={})
|
60
|
+
fetch_by_#{field_list}(#{arg_list}, options) or raise ActiveRecord::RecordNotFound
|
59
61
|
end
|
60
62
|
CODE
|
61
63
|
else
|
62
64
|
self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__ + 1)
|
63
|
-
def fetch_by_#{field_list}(#{arg_list})
|
64
|
-
|
65
|
+
def fetch_by_#{field_list}(#{arg_list}, options={})
|
66
|
+
ids = fetch_#{primary_key}_by_#{field_list}(#{arg_list})
|
67
|
+
ids.empty? ? ids : fetch_multi(ids, options)
|
65
68
|
end
|
66
69
|
CODE
|
67
70
|
end
|
@@ -78,9 +81,13 @@ module IdentityCache
|
|
78
81
|
#
|
79
82
|
# == Example:
|
80
83
|
# class Product
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
+
# include IdentityCache
|
85
|
+
# has_many :options
|
86
|
+
# has_many :orders
|
87
|
+
# has_many :buyers
|
88
|
+
# cache_has_many :options, embed: :ids
|
89
|
+
# cache_has_many :orders
|
90
|
+
# cache_has_many :buyers, inverse_name: 'line_item'
|
84
91
|
# end
|
85
92
|
#
|
86
93
|
# == Parameters
|
@@ -94,13 +101,11 @@ module IdentityCache
|
|
94
101
|
# * inverse_name: The name of the parent in the association if the name is
|
95
102
|
# not the lowercase pluralization of the parent object's class
|
96
103
|
def cache_has_many(association, options = {})
|
104
|
+
ensure_base_model
|
97
105
|
options = options.slice(:embed, :inverse_name)
|
98
106
|
options[:embed] = :ids unless options.has_key?(:embed)
|
99
107
|
deprecate_embed_option(options, false, :ids)
|
100
|
-
options
|
101
|
-
unless self.reflect_on_association(association)
|
102
|
-
raise AssociationError, "Association named '#{association}' was not found on #{self.class}"
|
103
|
-
end
|
108
|
+
ensure_cacheable_association(association, options)
|
104
109
|
self.cached_has_manys[association] = options
|
105
110
|
|
106
111
|
case options[:embed]
|
@@ -119,8 +124,8 @@ module IdentityCache
|
|
119
124
|
#
|
120
125
|
# == Example:
|
121
126
|
# class Product
|
122
|
-
#
|
123
|
-
#
|
127
|
+
# cache_has_one :store, embed: true
|
128
|
+
# cache_has_one :vendor
|
124
129
|
# end
|
125
130
|
#
|
126
131
|
# == Parameters
|
@@ -135,12 +140,10 @@ module IdentityCache
|
|
135
140
|
# necessary if the name is not the lowercase pluralization of the
|
136
141
|
# parent object's class)
|
137
142
|
def cache_has_one(association, options = {})
|
143
|
+
ensure_base_model
|
138
144
|
options = options.slice(:embed, :inverse_name)
|
139
145
|
options[:embed] = true unless options.has_key?(:embed)
|
140
|
-
options
|
141
|
-
unless self.reflect_on_association(association)
|
142
|
-
raise AssociationError, "Association named '#{association}' was not found on #{self.class}"
|
143
|
-
end
|
146
|
+
ensure_cacheable_association(association, options)
|
144
147
|
self.cached_has_ones[association] = options
|
145
148
|
|
146
149
|
if options[:embed] == true
|
@@ -155,8 +158,9 @@ module IdentityCache
|
|
155
158
|
#
|
156
159
|
# == Example:
|
157
160
|
# class Product
|
158
|
-
#
|
159
|
-
#
|
161
|
+
# include IdentityCache
|
162
|
+
# cache_attribute :quantity, by: :name
|
163
|
+
# cache_attribute :quantity, by: [:name, :vendor]
|
160
164
|
# end
|
161
165
|
#
|
162
166
|
# == Parameters
|
@@ -165,52 +169,41 @@ module IdentityCache
|
|
165
169
|
# == Options
|
166
170
|
#
|
167
171
|
# * by: Other attribute or attributes in the model to keep values indexed. Default is :id
|
172
|
+
# * unique: if the index would only have unique values. Default is true
|
168
173
|
def cache_attribute(attribute, options = {})
|
169
|
-
|
170
|
-
fields = Array(options[:by])
|
171
|
-
|
172
|
-
self.cache_attributes.push [attribute, fields]
|
173
|
-
|
174
|
-
field_list = fields.join("_and_")
|
175
|
-
arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
|
176
|
-
|
177
|
-
self.instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
178
|
-
def fetch_#{attribute}_by_#{field_list}(#{arg_list})
|
179
|
-
attribute_dynamic_fetcher(#{attribute.inspect}, #{fields.inspect}, [#{arg_list}])
|
180
|
-
end
|
181
|
-
CODE
|
174
|
+
cache_attribute_by_alias(attribute.inspect, attribute, options)
|
182
175
|
end
|
183
176
|
|
184
177
|
def disable_primary_cache_index
|
185
|
-
|
178
|
+
ensure_base_model
|
186
179
|
self.primary_cache_index_enabled = false
|
187
180
|
end
|
188
181
|
|
189
182
|
private
|
190
183
|
|
191
|
-
def
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
end
|
184
|
+
def cache_attribute_by_alias(attribute, alias_name, options)
|
185
|
+
ensure_base_model
|
186
|
+
options[:by] ||= :id
|
187
|
+
alias_name = alias_name.to_sym
|
188
|
+
unique = options[:unique].nil? ? true : !!options[:unique]
|
189
|
+
fields = Array(options[:by])
|
198
190
|
|
199
|
-
|
200
|
-
end
|
191
|
+
self.cache_indexes.push [alias_name, fields, unique]
|
201
192
|
|
202
|
-
|
203
|
-
|
204
|
-
ids = IdentityCache.fetch(cache_key) { identity_cache_conditions(fields, values).pluck(primary_key) }
|
193
|
+
field_list = fields.join("_and_")
|
194
|
+
arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
|
205
195
|
|
206
|
-
|
196
|
+
self.instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
197
|
+
def fetch_#{alias_name}_by_#{field_list}(#{arg_list})
|
198
|
+
attribute_dynamic_fetcher(#{attribute}, #{fields.inspect}, [#{arg_list}], #{unique})
|
199
|
+
end
|
200
|
+
CODE
|
207
201
|
end
|
208
202
|
|
209
203
|
def build_recursive_association_cache(association, options) #:nodoc:
|
210
|
-
options[:
|
204
|
+
options[:association_reflection] = reflect_on_association(association)
|
211
205
|
options[:cached_accessor_name] = "fetch_#{association}"
|
212
206
|
options[:records_variable_name] = "cached_#{association}"
|
213
|
-
options[:population_method_name] = "populate_#{association}_cache"
|
214
207
|
|
215
208
|
|
216
209
|
unless instance_methods.include?(options[:cached_accessor_name].to_sym)
|
@@ -218,10 +211,6 @@ module IdentityCache
|
|
218
211
|
def #{options[:cached_accessor_name]}
|
219
212
|
fetch_recursively_cached_association('#{options[:records_variable_name]}', :#{association})
|
220
213
|
end
|
221
|
-
|
222
|
-
def #{options[:population_method_name]}
|
223
|
-
populate_recursively_cached_association('#{options[:records_variable_name]}', :#{association})
|
224
|
-
end
|
225
214
|
CODE
|
226
215
|
|
227
216
|
options[:only_on_foreign_key_change] = false
|
@@ -231,32 +220,24 @@ module IdentityCache
|
|
231
220
|
|
232
221
|
def build_id_embedded_has_many_cache(association, options) #:nodoc:
|
233
222
|
singular_association = association.to_s.singularize
|
234
|
-
options[:
|
223
|
+
options[:association_reflection] = reflect_on_association(association)
|
235
224
|
options[:cached_accessor_name] = "fetch_#{association}"
|
236
225
|
options[:ids_name] = "#{singular_association}_ids"
|
237
226
|
options[:cached_ids_name] = "fetch_#{options[:ids_name]}"
|
238
227
|
options[:ids_variable_name] = "cached_#{options[:ids_name]}"
|
239
228
|
options[:records_variable_name] = "cached_#{association}"
|
240
|
-
options[:population_method_name] = "populate_#{association}_cache"
|
241
229
|
options[:prepopulate_method_name] = "prepopulate_fetched_#{association}"
|
242
230
|
|
243
231
|
self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
244
232
|
attr_reader :#{options[:ids_variable_name]}
|
245
233
|
|
246
234
|
def #{options[:cached_ids_name]}
|
247
|
-
|
248
|
-
@#{options[:ids_variable_name]}
|
249
|
-
end
|
250
|
-
|
251
|
-
def #{options[:population_method_name]}
|
252
|
-
@#{options[:ids_variable_name]} = #{options[:ids_name]}
|
253
|
-
association_cache.delete(:#{association})
|
235
|
+
@#{options[:ids_variable_name]} ||= #{options[:ids_name]}
|
254
236
|
end
|
255
237
|
|
256
238
|
def #{options[:cached_accessor_name]}
|
257
239
|
if IdentityCache.should_use_cache? || #{association}.loaded?
|
258
|
-
|
259
|
-
@#{options[:records_variable_name]} ||= #{options[:association_class]}.fetch_multi(@#{options[:ids_variable_name]})
|
240
|
+
@#{options[:records_variable_name]} ||= #{options[:association_reflection].klass}.fetch_multi(#{options[:cached_ids_name]})
|
260
241
|
else
|
261
242
|
#{association}
|
262
243
|
end
|
@@ -271,30 +252,26 @@ module IdentityCache
|
|
271
252
|
add_parent_expiry_hook(options)
|
272
253
|
end
|
273
254
|
|
274
|
-
def attribute_dynamic_fetcher(attribute, fields, values) #:nodoc:
|
275
|
-
|
276
|
-
|
255
|
+
def attribute_dynamic_fetcher(attribute, fields, values, unique_index) #:nodoc:
|
256
|
+
raise_if_scoped
|
257
|
+
cache_key = rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, values, unique_index)
|
258
|
+
IdentityCache.fetch(cache_key) do
|
259
|
+
query = reorder(nil).where(Hash[fields.zip(values)])
|
260
|
+
query = query.limit(1) if unique_index
|
261
|
+
results = query.pluck(attribute)
|
262
|
+
unique_index ? results.first : results
|
263
|
+
end
|
277
264
|
end
|
278
265
|
|
279
266
|
def add_parent_expiry_hook(options)
|
280
|
-
child_class = options[:
|
281
|
-
raise InverseAssociationError unless child_class.reflect_on_association(options[:inverse_name])
|
267
|
+
child_class = options[:association_reflection].klass
|
282
268
|
|
283
269
|
child_class.send(:include, ArTransactionChanges) unless child_class.include?(ArTransactionChanges)
|
284
270
|
child_class.send(:include, ParentModelExpiration) unless child_class.include?(ParentModelExpiration)
|
285
271
|
|
286
272
|
child_class.parent_expiration_entries[options[:inverse_name]] << [self, options[:only_on_foreign_key_change]]
|
287
273
|
|
288
|
-
child_class.
|
289
|
-
after_commit :expire_parent_caches
|
290
|
-
if Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("4.0.4")
|
291
|
-
after_touch :expire_parent_caches
|
292
|
-
end
|
293
|
-
CODE
|
294
|
-
end
|
295
|
-
|
296
|
-
def identity_cache_conditions(fields, values)
|
297
|
-
reorder(nil).where(Hash[fields.zip(values)])
|
274
|
+
child_class.after_commit :expire_parent_caches
|
298
275
|
end
|
299
276
|
|
300
277
|
def deprecate_embed_option(options, old_value, new_value)
|
@@ -303,6 +280,28 @@ module IdentityCache
|
|
303
280
|
ActiveSupport::Deprecation.warn("`embed: #{old_value.inspect}` was renamed to `embed: #{new_value.inspect}` for clarity", caller(2))
|
304
281
|
end
|
305
282
|
end
|
283
|
+
|
284
|
+
def ensure_base_model
|
285
|
+
if self != cached_model
|
286
|
+
raise DerivedModelError, "IdentityCache class methods must be called on the same model that includes IdentityCache"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def ensure_cacheable_association(association, options)
|
291
|
+
unless association_reflection = self.reflect_on_association(association)
|
292
|
+
raise AssociationError, "Association named '#{association}' was not found on #{self.class}"
|
293
|
+
end
|
294
|
+
if association_reflection.options[:through]
|
295
|
+
raise UnsupportedAssociationError, "caching through associations isn't supported"
|
296
|
+
end
|
297
|
+
options[:inverse_name] ||= association_reflection.inverse_of.name if association_reflection.inverse_of
|
298
|
+
options[:inverse_name] ||= self.name.underscore.to_sym
|
299
|
+
child_class = association_reflection.klass
|
300
|
+
raise InverseAssociationError unless child_class.reflect_on_association(options[:inverse_name])
|
301
|
+
unless options[:embed] == true || child_class.include?(IdentityCache)
|
302
|
+
raise UnsupportedAssociationError, "associated class #{child_class} must include IdentityCache to be cached without full embedding"
|
303
|
+
end
|
304
|
+
end
|
306
305
|
end
|
307
306
|
end
|
308
307
|
end
|