identity_cache 0.2.5 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|