identity_cache 0.4.1 → 1.1.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.
Files changed (94) hide show
  1. checksums.yaml +5 -5
  2. data/.github/probots.yml +2 -0
  3. data/.github/workflows/ci.yml +92 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +5 -0
  6. data/CAVEATS.md +25 -0
  7. data/CHANGELOG.md +73 -19
  8. data/Gemfile +5 -1
  9. data/LICENSE +1 -1
  10. data/README.md +49 -27
  11. data/Rakefile +14 -5
  12. data/dev.yml +12 -16
  13. data/gemfiles/Gemfile.latest-release +8 -0
  14. data/gemfiles/Gemfile.min-supported +7 -0
  15. data/gemfiles/Gemfile.rails-edge +7 -0
  16. data/identity_cache.gemspec +29 -10
  17. data/lib/identity_cache.rb +78 -51
  18. data/lib/identity_cache/belongs_to_caching.rb +12 -40
  19. data/lib/identity_cache/cache_fetcher.rb +6 -5
  20. data/lib/identity_cache/cache_hash.rb +2 -2
  21. data/lib/identity_cache/cache_invalidation.rb +4 -11
  22. data/lib/identity_cache/cache_key_generation.rb +17 -65
  23. data/lib/identity_cache/cache_key_loader.rb +128 -0
  24. data/lib/identity_cache/cached.rb +7 -0
  25. data/lib/identity_cache/cached/association.rb +87 -0
  26. data/lib/identity_cache/cached/attribute.rb +123 -0
  27. data/lib/identity_cache/cached/attribute_by_multi.rb +37 -0
  28. data/lib/identity_cache/cached/attribute_by_one.rb +88 -0
  29. data/lib/identity_cache/cached/belongs_to.rb +100 -0
  30. data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
  31. data/lib/identity_cache/cached/prefetcher.rb +61 -0
  32. data/lib/identity_cache/cached/primary_index.rb +96 -0
  33. data/lib/identity_cache/cached/recursive/association.rb +109 -0
  34. data/lib/identity_cache/cached/recursive/has_many.rb +9 -0
  35. data/lib/identity_cache/cached/recursive/has_one.rb +9 -0
  36. data/lib/identity_cache/cached/reference/association.rb +16 -0
  37. data/lib/identity_cache/cached/reference/has_many.rb +105 -0
  38. data/lib/identity_cache/cached/reference/has_one.rb +100 -0
  39. data/lib/identity_cache/configuration_dsl.rb +53 -215
  40. data/lib/identity_cache/encoder.rb +95 -0
  41. data/lib/identity_cache/expiry_hook.rb +36 -0
  42. data/lib/identity_cache/fallback_fetcher.rb +2 -1
  43. data/lib/identity_cache/load_strategy/eager.rb +28 -0
  44. data/lib/identity_cache/load_strategy/lazy.rb +71 -0
  45. data/lib/identity_cache/load_strategy/load_request.rb +20 -0
  46. data/lib/identity_cache/load_strategy/multi_load_request.rb +27 -0
  47. data/lib/identity_cache/mem_cache_store_cas.rb +53 -0
  48. data/lib/identity_cache/memoized_cache_proxy.rb +137 -58
  49. data/lib/identity_cache/parent_model_expiration.rb +46 -11
  50. data/lib/identity_cache/query_api.rb +102 -408
  51. data/lib/identity_cache/railtie.rb +8 -0
  52. data/lib/identity_cache/record_not_found.rb +6 -0
  53. data/lib/identity_cache/should_use_cache.rb +1 -0
  54. data/lib/identity_cache/version.rb +3 -2
  55. data/lib/identity_cache/with_primary_index.rb +136 -0
  56. data/lib/identity_cache/without_primary_index.rb +24 -3
  57. data/performance/cache_runner.rb +25 -73
  58. data/performance/cpu.rb +4 -3
  59. data/performance/externals.rb +4 -3
  60. data/performance/profile.rb +6 -5
  61. data/railgun.yml +16 -0
  62. metadata +60 -73
  63. data/.travis.yml +0 -30
  64. data/Gemfile.rails42 +0 -6
  65. data/Gemfile.rails50 +0 -6
  66. data/test/attribute_cache_test.rb +0 -110
  67. data/test/cache_fetch_includes_test.rb +0 -46
  68. data/test/cache_hash_test.rb +0 -14
  69. data/test/cache_invalidation_test.rb +0 -139
  70. data/test/deeply_nested_associated_record_test.rb +0 -19
  71. data/test/denormalized_has_many_test.rb +0 -211
  72. data/test/denormalized_has_one_test.rb +0 -160
  73. data/test/fetch_multi_test.rb +0 -308
  74. data/test/fetch_test.rb +0 -258
  75. data/test/fixtures/serialized_record.mysql2 +0 -0
  76. data/test/fixtures/serialized_record.postgresql +0 -0
  77. data/test/helpers/active_record_objects.rb +0 -106
  78. data/test/helpers/database_connection.rb +0 -72
  79. data/test/helpers/serialization_format.rb +0 -42
  80. data/test/helpers/update_serialization_format.rb +0 -24
  81. data/test/identity_cache_test.rb +0 -29
  82. data/test/index_cache_test.rb +0 -161
  83. data/test/memoized_attributes_test.rb +0 -49
  84. data/test/memoized_cache_proxy_test.rb +0 -107
  85. data/test/normalized_belongs_to_test.rb +0 -107
  86. data/test/normalized_has_many_test.rb +0 -231
  87. data/test/normalized_has_one_test.rb +0 -9
  88. data/test/prefetch_associations_test.rb +0 -364
  89. data/test/readonly_test.rb +0 -109
  90. data/test/recursive_denormalized_has_many_test.rb +0 -131
  91. data/test/save_test.rb +0 -82
  92. data/test/schema_change_test.rb +0 -112
  93. data/test/serialization_format_change_test.rb +0 -16
  94. data/test/test_helper.rb +0 -140
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module IdentityCache
3
+ module Cached
4
+ module Recursive
5
+ class HasMany < Association # :nodoc:
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module IdentityCache
3
+ module Cached
4
+ module Recursive
5
+ class HasOne < Association # :nodoc:
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module IdentityCache
3
+ module Cached
4
+ module Reference
5
+ class Association < Cached::Association # :nodoc:
6
+ def embedded_by_reference?
7
+ true
8
+ end
9
+
10
+ def embedded_recursively?
11
+ false
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+ module IdentityCache
3
+ module Cached
4
+ module Reference
5
+ class HasMany < Association # :nodoc:
6
+ def initialize(name, reflection:)
7
+ super
8
+ @cached_ids_name = "fetch_#{ids_name}"
9
+ @ids_variable_name = :"@#{ids_cached_reader_name}"
10
+ end
11
+
12
+ attr_reader :cached_ids_name, :ids_variable_name
13
+
14
+ def build
15
+ reflection.active_record.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
16
+ attr_reader :#{ids_cached_reader_name}
17
+
18
+ def #{cached_ids_name}
19
+ #{ids_variable_name} ||= #{ids_name}
20
+ end
21
+
22
+ def #{cached_accessor_name}
23
+ assoc = association(:#{name})
24
+ if assoc.klass.should_use_cache? && !assoc.loaded? && assoc.target.blank?
25
+ #{records_variable_name} ||= #{reflection.class_name}.fetch_multi(#{cached_ids_name})
26
+ else
27
+ #{name}.to_a
28
+ end
29
+ end
30
+ RUBY
31
+
32
+ ParentModelExpiration.add_parent_expiry_hook(self)
33
+ end
34
+
35
+ def read(record)
36
+ record.public_send(cached_ids_name)
37
+ end
38
+
39
+ def write(record, ids)
40
+ record.instance_variable_set(ids_variable_name, ids)
41
+ end
42
+
43
+ def clear(record)
44
+ [ids_variable_name, records_variable_name].each do |ivar|
45
+ if record.instance_variable_defined?(ivar)
46
+ record.remove_instance_variable(ivar)
47
+ end
48
+ end
49
+ end
50
+
51
+ def fetch(records)
52
+ fetch_async(LoadStrategy::Eager, records) { |child_records| child_records }
53
+ end
54
+
55
+ def fetch_async(load_strategy, records)
56
+ fetch_embedded_async(load_strategy, records) do
57
+ ids_to_parent_record = records.each_with_object({}) do |record, hash|
58
+ child_ids = record.send(cached_ids_name)
59
+ child_ids.each do |child_id|
60
+ hash[child_id] = record
61
+ end
62
+ end
63
+
64
+ load_strategy.load_multi(
65
+ reflection.klass.cached_primary_index,
66
+ ids_to_parent_record.keys
67
+ ) do |child_records_by_id|
68
+ parent_record_to_child_records = Hash.new { |h, k| h[k] = [] }
69
+
70
+ child_records_by_id.each do |id, child_record|
71
+ parent_record = ids_to_parent_record.fetch(id)
72
+ parent_record_to_child_records[parent_record] << child_record
73
+ end
74
+
75
+ parent_record_to_child_records.each do |parent, children|
76
+ parent.instance_variable_set(records_variable_name, children)
77
+ end
78
+
79
+ yield child_records_by_id.values.compact
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def embedded_fetched?(records)
87
+ record = records.first
88
+ super || record.instance_variable_defined?(ids_variable_name)
89
+ end
90
+
91
+ def singular_name
92
+ name.to_s.singularize
93
+ end
94
+
95
+ def ids_name
96
+ "#{singular_name}_ids"
97
+ end
98
+
99
+ def ids_cached_reader_name
100
+ "cached_#{ids_name}"
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+ module IdentityCache
3
+ module Cached
4
+ module Reference
5
+ class HasOne < Association # :nodoc:
6
+ def initialize(name, reflection:)
7
+ super
8
+ @cached_id_name = "fetch_#{id_name}"
9
+ @id_variable_name = :"@#{id_cached_reader_name}"
10
+ end
11
+
12
+ attr_reader :cached_id_name, :id_variable_name
13
+
14
+ def build
15
+ reflection.active_record.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
16
+ attr_reader :#{id_cached_reader_name}
17
+
18
+ def #{cached_id_name}
19
+ return #{id_variable_name} if defined?(#{id_variable_name})
20
+ #{id_variable_name} = association(:#{name}).scope.ids.first
21
+ end
22
+
23
+ def #{cached_accessor_name}
24
+ assoc = association(:#{name})
25
+ if assoc.klass.should_use_cache? && !assoc.loaded?
26
+ #{records_variable_name} ||= #{reflection.class_name}.fetch(#{cached_id_name}) if #{cached_id_name}
27
+ else
28
+ #{name}
29
+ end
30
+ end
31
+ RUBY
32
+
33
+ ParentModelExpiration.add_parent_expiry_hook(self)
34
+ end
35
+
36
+ def read(record)
37
+ record.public_send(cached_id_name)
38
+ end
39
+
40
+ def write(record, id)
41
+ record.instance_variable_set(id_variable_name, id)
42
+ end
43
+
44
+ def clear(record)
45
+ [id_variable_name, records_variable_name].each do |ivar|
46
+ if record.instance_variable_defined?(ivar)
47
+ record.remove_instance_variable(ivar)
48
+ end
49
+ end
50
+ end
51
+
52
+ def fetch(records)
53
+ fetch_async(LoadStrategy::Eager, records) { |child_records| child_records }
54
+ end
55
+
56
+ def fetch_async(load_strategy, records)
57
+ fetch_embedded_async(load_strategy, records) do
58
+ ids_to_parent_record = records.each_with_object({}) do |record, hash|
59
+ child_id = record.send(cached_id_name)
60
+ hash[child_id] = record if child_id
61
+ end
62
+
63
+ load_strategy.load_multi(
64
+ reflection.klass.cached_primary_index,
65
+ ids_to_parent_record.keys
66
+ ) do |child_records_by_id|
67
+ parent_record_to_child_record = {}
68
+
69
+ child_records_by_id.each do |id, child_record|
70
+ parent_record = ids_to_parent_record.fetch(id)
71
+ parent_record_to_child_record[parent_record] ||= child_record
72
+ end
73
+
74
+ parent_record_to_child_record.each do |parent, child|
75
+ parent.instance_variable_set(records_variable_name, child)
76
+ end
77
+
78
+ yield child_records_by_id.values.compact
79
+ end
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def embedded_fetched?(records)
86
+ record = records.first
87
+ super || record.instance_variable_defined?(id_variable_name)
88
+ end
89
+
90
+ def id_name
91
+ "#{name}_id"
92
+ end
93
+
94
+ def id_cached_reader_name
95
+ "cached_#{id_name}"
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -1,76 +1,19 @@
1
+ # frozen_string_literal: true
1
2
  module IdentityCache
2
3
  module ConfigurationDSL
3
4
  extend ActiveSupport::Concern
4
5
 
5
6
  included do |base|
6
- base.class_attribute :cache_indexes
7
- base.class_attribute :cached_has_manys
8
- base.class_attribute :cached_has_ones
9
- base.class_attribute :primary_cache_index_enabled
7
+ base.class_attribute(:cache_indexes)
8
+ base.class_attribute(:cached_has_manys)
9
+ base.class_attribute(:cached_has_ones)
10
10
 
11
11
  base.cached_has_manys = {}
12
12
  base.cached_has_ones = {}
13
13
  base.cache_indexes = []
14
- base.primary_cache_index_enabled = true
15
-
16
- base.after_commit :expire_parent_caches
17
14
  end
18
15
 
19
16
  module ClassMethods
20
- # Declares a new index in the cache for the class where IdentityCache was
21
- # included.
22
- #
23
- # IdentityCache will add a fetch_by_field1_and_field2_and_...field for every
24
- # index.
25
- #
26
- # == Example:
27
- #
28
- # class Product
29
- # include IdentityCache
30
- # cache_index :name, :vendor
31
- # end
32
- #
33
- # Will add Product.fetch_by_name_and_vendor
34
- #
35
- # == Parameters
36
- #
37
- # +fields+ Array of symbols or strings representing the fields in the index
38
- #
39
- # == Options
40
- # * unique: if the index would only have unique values
41
- #
42
- def cache_index(*fields)
43
- raise NotImplementedError, "Cache indexes need an enabled primary index" unless primary_cache_index_enabled
44
- options = fields.extract_options!
45
- unique = options[:unique] || false
46
- cache_attribute_by_alias('primary_key', 'id', by: fields, unique: unique)
47
-
48
- field_list = fields.join("_and_")
49
- arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
50
-
51
- if unique
52
- self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__ + 1)
53
- def fetch_by_#{field_list}(#{arg_list}, options={})
54
- id = fetch_id_by_#{field_list}(#{arg_list})
55
- id && fetch_by_id(id, options)
56
- end
57
-
58
- # exception throwing variant
59
- def fetch_by_#{field_list}!(#{arg_list}, options={})
60
- fetch_by_#{field_list}(#{arg_list}, options) or raise ActiveRecord::RecordNotFound
61
- end
62
- CODE
63
- else
64
- self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__ + 1)
65
- def fetch_by_#{field_list}(#{arg_list}, options={})
66
- ids = fetch_id_by_#{field_list}(#{arg_list})
67
- ids.empty? ? ids : fetch_multi(ids, options)
68
- end
69
- CODE
70
- end
71
- end
72
-
73
-
74
17
  # Will cache an association to the class including IdentityCache.
75
18
  # The embed option, if set, will make IdentityCache keep the association
76
19
  # values in the same cache entry as the parent.
@@ -84,10 +27,8 @@ module IdentityCache
84
27
  # include IdentityCache
85
28
  # has_many :options
86
29
  # has_many :orders
87
- # has_many :buyers
88
30
  # cache_has_many :options, embed: :ids
89
31
  # cache_has_many :orders
90
- # cache_has_many :buyers, inverse_name: 'line_item'
91
32
  # end
92
33
  #
93
34
  # == Parameters
@@ -95,27 +36,28 @@ module IdentityCache
95
36
  #
96
37
  # == Options
97
38
  #
98
- # * embed: If set to true, will cause IdentityCache to keep the
99
- # values for this association in the same cache entry as the parent,
100
- # instead of its own.
101
- # * inverse_name: The name of the parent in the association if the name is
102
- # not the lowercase pluralization of the parent object's class
103
- def cache_has_many(association, options = {})
39
+ # * embed: If `true`, IdentityCache will embed the associated records
40
+ # in the cache entries for this model, as well as all the embedded
41
+ # associations for the associated record recursively.
42
+ # If `:ids` (the default), it will only embed the ids for the associated
43
+ # records.
44
+ def cache_has_many(association, embed: :ids)
104
45
  ensure_base_model
105
- options = options.slice(:embed, :inverse_name)
106
- options[:embed] = :ids unless options.has_key?(:embed)
107
- deprecate_embed_option(options, false, :ids)
108
- ensure_cacheable_association(association, options)
109
- self.cached_has_manys[association] = options
110
-
111
- case options[:embed]
112
- when true
113
- build_recursive_association_cache(association, options)
46
+ check_association_for_caching(association)
47
+ reflection = reflect_on_association(association)
48
+ association_class = case embed
114
49
  when :ids
115
- build_id_embedded_has_many_cache(association, options)
50
+ Cached::Reference::HasMany
51
+ when true
52
+ Cached::Recursive::HasMany
116
53
  else
117
54
  raise NotImplementedError
118
55
  end
56
+
57
+ cached_has_manys[association] = association_class.new(
58
+ association,
59
+ reflection: reflection,
60
+ ).tap(&:build)
119
61
  end
120
62
 
121
63
  # Will cache an association to the class including IdentityCache.
@@ -125,7 +67,7 @@ module IdentityCache
125
67
  # == Example:
126
68
  # class Product
127
69
  # cache_has_one :store, embed: true
128
- # cache_has_one :vendor
70
+ # cache_has_one :vendor, embed: :id
129
71
  # end
130
72
  #
131
73
  # == Parameters
@@ -133,24 +75,27 @@ module IdentityCache
133
75
  #
134
76
  # == Options
135
77
  #
136
- # * embed: Only true is supported, which is also the default, so
137
- # IdentityCache will keep the values for this association in the same
138
- # cache entry as the parent, instead of its own.
139
- # * inverse_name: The name of the parent in the association ( only
140
- # necessary if the name is not the lowercase pluralization of the
141
- # parent object's class)
142
- def cache_has_one(association, options = {})
78
+ # * embed: If `true`, IdentityCache will embed the associated record
79
+ # in the cache entries for this model, as well as all the embedded
80
+ # associations for the associated record recursively.
81
+ # If `:id`, it will only embed the id for the associated record.
82
+ def cache_has_one(association, embed:)
143
83
  ensure_base_model
144
- options = options.slice(:embed, :inverse_name)
145
- options[:embed] = true unless options.has_key?(:embed)
146
- ensure_cacheable_association(association, options)
147
- self.cached_has_ones[association] = options
148
-
149
- if options[:embed] == true
150
- build_recursive_association_cache(association, options)
84
+ check_association_for_caching(association)
85
+ reflection = reflect_on_association(association)
86
+ association_class = case embed
87
+ when :id
88
+ Cached::Reference::HasOne
89
+ when true
90
+ Cached::Recursive::HasOne
151
91
  else
152
92
  raise NotImplementedError
153
93
  end
94
+
95
+ cached_has_ones[association] = association_class.new(
96
+ association,
97
+ reflection: reflection,
98
+ ).tap(&:build)
154
99
  end
155
100
 
156
101
  # Will cache a single attribute on its own blob, it will add a
@@ -170,145 +115,38 @@ module IdentityCache
170
115
  #
171
116
  # * by: Other attribute or attributes in the model to keep values indexed. Default is :id
172
117
  # * unique: if the index would only have unique values. Default is true
173
- def cache_attribute(attribute, options = {})
174
- cache_attribute_by_alias(attribute.inspect, attribute, options)
175
- end
176
-
177
- def disable_primary_cache_index
178
- ActiveSupport::Deprecation.warn("disable_primary_cache_index is deprecated, use `include IdentityCache::WithoutPrimaryIndex` instead")
179
- ensure_base_model
180
- self.primary_cache_index_enabled = false
118
+ def cache_attribute(attribute, by: :id, unique: true)
119
+ cache_attribute_by_alias(attribute, alias_name: attribute, by: by, unique: unique)
181
120
  end
182
121
 
183
122
  private
184
123
 
185
- def cache_attribute_by_alias(attribute, alias_name, options)
124
+ def cache_attribute_by_alias(attribute_or_proc, alias_name:, by:, unique:)
186
125
  ensure_base_model
187
- options[:by] ||= :id
188
- alias_name = alias_name.to_sym
189
- unique = options[:unique].nil? ? true : !!options[:unique]
190
- fields = Array(options[:by])
191
-
192
- self.cache_indexes.push [alias_name, fields, unique]
193
-
194
- field_list = fields.join("_and_")
195
- arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
196
-
197
- self.instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
198
- def fetch_#{alias_name}_by_#{field_list}(#{arg_list})
199
- attribute_dynamic_fetcher(#{attribute}, #{fields.inspect}, [#{arg_list}], #{unique})
200
- end
201
- CODE
202
- end
203
-
204
- def build_recursive_association_cache(association, options) #:nodoc:
205
- options[:association_reflection] = reflect_on_association(association)
206
- options[:cached_accessor_name] = "fetch_#{association}"
207
- options[:records_variable_name] = "cached_#{association}"
208
-
209
- self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
210
- def #{options[:cached_accessor_name]}
211
- fetch_recursively_cached_association('#{options[:records_variable_name]}', :#{association})
212
- end
213
- CODE
214
-
215
- options[:only_on_foreign_key_change] = false
216
- add_parent_expiry_hook(options)
217
- end
218
-
219
- def build_id_embedded_has_many_cache(association, options) #:nodoc:
220
- singular_association = association.to_s.singularize
221
- options[:association_reflection] = reflect_on_association(association)
222
- options[:cached_accessor_name] = "fetch_#{association}"
223
- options[:ids_name] = "#{singular_association}_ids"
224
- options[:cached_ids_name] = "fetch_#{options[:ids_name]}"
225
- options[:ids_variable_name] = "cached_#{options[:ids_name]}"
226
- options[:records_variable_name] = "cached_#{association}"
227
- options[:prepopulate_method_name] = "prepopulate_fetched_#{association}"
228
-
229
- self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
230
- attr_reader :#{options[:ids_variable_name]}
231
-
232
- def #{options[:cached_ids_name]}
233
- @#{options[:ids_variable_name]} ||= #{options[:ids_name]}
234
- end
235
-
236
- def #{options[:cached_accessor_name]}
237
- association_klass = association(:#{association}).klass
238
- if association_klass.should_use_cache? && !#{association}.loaded?
239
- @#{options[:records_variable_name]} ||= #{options[:association_reflection].klass}.fetch_multi(#{options[:cached_ids_name]})
240
- else
241
- #{association}.to_a
242
- end
243
- end
244
-
245
- def #{options[:prepopulate_method_name]}(records)
246
- @#{options[:records_variable_name]} = records
247
- end
248
- CODE
126
+ fields = Array(by)
249
127
 
250
- options[:only_on_foreign_key_change] = true
251
- add_parent_expiry_hook(options)
252
- end
253
-
254
- def attribute_dynamic_fetcher(attribute, fields, values, unique_index) #:nodoc:
255
- raise_if_scoped
256
-
257
- if should_use_cache?
258
- cache_key = rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, values, unique_index)
259
- IdentityCache.fetch(cache_key) do
260
- dynamic_attribute_cache_miss(attribute, fields, values, unique_index)
261
- end
262
- else
263
- dynamic_attribute_cache_miss(attribute, fields, values, unique_index)
264
- end
265
- end
266
-
267
- def dynamic_attribute_cache_miss(attribute, fields, values, unique_index)
268
- query = reorder(nil).where(Hash[fields.zip(values)])
269
- query = query.limit(1) if unique_index
270
- results = query.pluck(attribute)
271
- unique_index ? results.first : results
272
- end
273
-
274
- def add_parent_expiry_hook(options)
275
- child_class = options[:association_reflection].klass
276
- unless child_class < IdentityCache
277
- message = "associated class #{child_class} will need to include IdentityCache or " \
278
- "IdentityCache::WithoutPrimaryIndex for embedded associations"
279
- ActiveSupport::Deprecation.warn(message, caller(3))
280
- child_class.send(:include, IdentityCache::WithoutPrimaryIndex)
281
- end
282
- child_class.parent_expiration_entries[options[:inverse_name]] << [self, options[:only_on_foreign_key_change]]
283
- end
284
-
285
- def deprecate_embed_option(options, old_value, new_value)
286
- if options[:embed] == old_value
287
- options[:embed] = new_value
288
- ActiveSupport::Deprecation.warn("`embed: #{old_value.inspect}` was renamed to `embed: #{new_value.inspect}` for clarity", caller(2))
289
- end
128
+ klass = fields.one? ? Cached::AttributeByOne : Cached::AttributeByMulti
129
+ cached_attribute = klass.new(self, attribute_or_proc, alias_name, fields, unique)
130
+ cached_attribute.build
131
+ cache_indexes.push(cached_attribute)
290
132
  end
291
133
 
292
134
  def ensure_base_model
293
135
  if self != cached_model
294
- raise DerivedModelError, "IdentityCache class methods must be called on the same model that includes IdentityCache"
136
+ raise DerivedModelError, <<~MSG.squish
137
+ IdentityCache class methods must be called on the same
138
+ model that includes IdentityCache
139
+ MSG
295
140
  end
296
141
  end
297
142
 
298
- def ensure_cacheable_association(association, options)
299
- unless association_reflection = self.reflect_on_association(association)
143
+ def check_association_for_caching(association)
144
+ unless (association_reflection = reflect_on_association(association))
300
145
  raise AssociationError, "Association named '#{association}' was not found on #{self.class}"
301
146
  end
302
147
  if association_reflection.options[:through]
303
148
  raise UnsupportedAssociationError, "caching through associations isn't supported"
304
149
  end
305
- options[:inverse_name] ||= association_reflection.inverse_of.name if association_reflection.inverse_of
306
- options[:inverse_name] ||= self.name.underscore.to_sym
307
- child_class = association_reflection.klass
308
- raise InverseAssociationError unless child_class.reflect_on_association(options[:inverse_name])
309
- unless options[:embed] == true || child_class.include?(IdentityCache)
310
- raise UnsupportedAssociationError, "associated class #{child_class} must include IdentityCache to be cached without full embedding"
311
- end
312
150
  end
313
151
  end
314
152
  end