identity_cache 0.4.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/probots.yml +2 -0
- data/.github/workflows/ci.yml +92 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +5 -0
- data/CAVEATS.md +25 -0
- data/CHANGELOG.md +73 -19
- data/Gemfile +5 -1
- data/LICENSE +1 -1
- data/README.md +49 -27
- data/Rakefile +14 -5
- data/dev.yml +12 -16
- data/gemfiles/Gemfile.latest-release +8 -0
- data/gemfiles/Gemfile.min-supported +7 -0
- data/gemfiles/Gemfile.rails-edge +7 -0
- data/identity_cache.gemspec +29 -10
- data/lib/identity_cache.rb +78 -51
- data/lib/identity_cache/belongs_to_caching.rb +12 -40
- data/lib/identity_cache/cache_fetcher.rb +6 -5
- data/lib/identity_cache/cache_hash.rb +2 -2
- data/lib/identity_cache/cache_invalidation.rb +4 -11
- data/lib/identity_cache/cache_key_generation.rb +17 -65
- data/lib/identity_cache/cache_key_loader.rb +128 -0
- data/lib/identity_cache/cached.rb +7 -0
- data/lib/identity_cache/cached/association.rb +87 -0
- data/lib/identity_cache/cached/attribute.rb +123 -0
- data/lib/identity_cache/cached/attribute_by_multi.rb +37 -0
- data/lib/identity_cache/cached/attribute_by_one.rb +88 -0
- data/lib/identity_cache/cached/belongs_to.rb +100 -0
- data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
- data/lib/identity_cache/cached/prefetcher.rb +61 -0
- data/lib/identity_cache/cached/primary_index.rb +96 -0
- data/lib/identity_cache/cached/recursive/association.rb +109 -0
- data/lib/identity_cache/cached/recursive/has_many.rb +9 -0
- data/lib/identity_cache/cached/recursive/has_one.rb +9 -0
- data/lib/identity_cache/cached/reference/association.rb +16 -0
- data/lib/identity_cache/cached/reference/has_many.rb +105 -0
- data/lib/identity_cache/cached/reference/has_one.rb +100 -0
- data/lib/identity_cache/configuration_dsl.rb +53 -215
- data/lib/identity_cache/encoder.rb +95 -0
- data/lib/identity_cache/expiry_hook.rb +36 -0
- data/lib/identity_cache/fallback_fetcher.rb +2 -1
- data/lib/identity_cache/load_strategy/eager.rb +28 -0
- data/lib/identity_cache/load_strategy/lazy.rb +71 -0
- data/lib/identity_cache/load_strategy/load_request.rb +20 -0
- data/lib/identity_cache/load_strategy/multi_load_request.rb +27 -0
- data/lib/identity_cache/mem_cache_store_cas.rb +53 -0
- data/lib/identity_cache/memoized_cache_proxy.rb +137 -58
- data/lib/identity_cache/parent_model_expiration.rb +46 -11
- data/lib/identity_cache/query_api.rb +102 -408
- data/lib/identity_cache/railtie.rb +8 -0
- data/lib/identity_cache/record_not_found.rb +6 -0
- data/lib/identity_cache/should_use_cache.rb +1 -0
- data/lib/identity_cache/version.rb +3 -2
- data/lib/identity_cache/with_primary_index.rb +136 -0
- data/lib/identity_cache/without_primary_index.rb +24 -3
- data/performance/cache_runner.rb +25 -73
- data/performance/cpu.rb +4 -3
- data/performance/externals.rb +4 -3
- data/performance/profile.rb +6 -5
- data/railgun.yml +16 -0
- metadata +60 -73
- data/.travis.yml +0 -30
- data/Gemfile.rails42 +0 -6
- data/Gemfile.rails50 +0 -6
- data/test/attribute_cache_test.rb +0 -110
- data/test/cache_fetch_includes_test.rb +0 -46
- data/test/cache_hash_test.rb +0 -14
- data/test/cache_invalidation_test.rb +0 -139
- data/test/deeply_nested_associated_record_test.rb +0 -19
- data/test/denormalized_has_many_test.rb +0 -211
- data/test/denormalized_has_one_test.rb +0 -160
- data/test/fetch_multi_test.rb +0 -308
- data/test/fetch_test.rb +0 -258
- data/test/fixtures/serialized_record.mysql2 +0 -0
- data/test/fixtures/serialized_record.postgresql +0 -0
- data/test/helpers/active_record_objects.rb +0 -106
- data/test/helpers/database_connection.rb +0 -72
- data/test/helpers/serialization_format.rb +0 -42
- data/test/helpers/update_serialization_format.rb +0 -24
- data/test/identity_cache_test.rb +0 -29
- data/test/index_cache_test.rb +0 -161
- data/test/memoized_attributes_test.rb +0 -49
- data/test/memoized_cache_proxy_test.rb +0 -107
- data/test/normalized_belongs_to_test.rb +0 -107
- data/test/normalized_has_many_test.rb +0 -231
- data/test/normalized_has_one_test.rb +0 -9
- data/test/prefetch_associations_test.rb +0 -364
- data/test/readonly_test.rb +0 -109
- data/test/recursive_denormalized_has_many_test.rb +0 -131
- data/test/save_test.rb +0 -82
- data/test/schema_change_test.rb +0 -112
- data/test/serialization_format_change_test.rb +0 -16
- data/test/test_helper.rb +0 -140
@@ -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
|
7
|
-
base.class_attribute
|
8
|
-
base.class_attribute
|
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
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
def cache_has_many(association,
|
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
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
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:
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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,
|
174
|
-
cache_attribute_by_alias(attribute
|
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(
|
124
|
+
def cache_attribute_by_alias(attribute_or_proc, alias_name:, by:, unique:)
|
186
125
|
ensure_base_model
|
187
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
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,
|
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
|
299
|
-
unless association_reflection =
|
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
|