identity_cache 0.2.4 → 0.2.5
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 +0 -3
- data/identity_cache.gemspec +1 -1
- data/lib/identity_cache/configuration_dsl.rb +5 -11
- data/lib/identity_cache/parent_model_expiration.rb +39 -22
- data/lib/identity_cache/query_api.rb +7 -1
- data/lib/identity_cache/version.rb +1 -1
- data/test/cache_invalidation_test.rb +15 -0
- data/test/deeply_nested_associated_record_test.rb +17 -0
- data/test/fixtures/serialized_record +0 -0
- data/test/helpers/active_record_objects.rb +11 -0
- data/test/helpers/database_connection.rb +6 -6
- data/test/test_helper.rb +6 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b3399599b1784b0df560313cc902e20e7cd86fd
|
4
|
+
data.tar.gz: 4a45bfa103d6095837bf2226a1ccd505219001fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50b692df952ca33119c152a4aaaa9bf0e0360c2a56005bd0711a707ea516031a12ceb7f8e9e9599b52f0ba5ce1568c33585b7598bdc9a6a1da018b7b42eec0a3
|
7
|
+
data.tar.gz: e9519a582f37d3e029a43046157139ba37c3a897fda507febe592e1219a2d84968c3c10b68780a4c4397eeec9102cfd3ed4f69f41756818e7532b17ce4aa4d1e
|
data/.travis.yml
CHANGED
data/identity_cache.gemspec
CHANGED
@@ -31,6 +31,6 @@ Gem::Specification.new do |gem|
|
|
31
31
|
gem.add_development_dependency('cityhash', '0.6.0')
|
32
32
|
gem.add_development_dependency('mysql2')
|
33
33
|
gem.add_development_dependency('pg')
|
34
|
-
gem.add_development_dependency('stackprof') if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
|
34
|
+
gem.add_development_dependency('stackprof') if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new("2.1.0")
|
35
35
|
end
|
36
36
|
end
|
@@ -278,23 +278,17 @@ module IdentityCache
|
|
278
278
|
|
279
279
|
def add_parent_expiry_hook(options)
|
280
280
|
child_class = options[:association_class]
|
281
|
-
|
282
|
-
raise InverseAssociationError unless child_association
|
283
|
-
foreign_key = child_association.association_foreign_key
|
281
|
+
raise InverseAssociationError unless child_class.reflect_on_association(options[:inverse_name])
|
284
282
|
|
285
283
|
child_class.send(:include, ArTransactionChanges) unless child_class.include?(ArTransactionChanges)
|
286
284
|
child_class.send(:include, ParentModelExpiration) unless child_class.include?(ParentModelExpiration)
|
287
285
|
|
288
|
-
|
286
|
+
child_class.parent_expiration_entries[options[:inverse_name]] << [self, options[:only_on_foreign_key_change]]
|
289
287
|
|
290
288
|
child_class.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
add_parent_expiration_entry :#{after_action_name}
|
295
|
-
|
296
|
-
def #{after_action_name}
|
297
|
-
expire_parent_cache_on_changes(:#{options[:inverse_name]}, '#{foreign_key}', #{self.name}, #{options[:only_on_foreign_key_change]})
|
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
|
298
292
|
end
|
299
293
|
CODE
|
300
294
|
end
|
@@ -4,44 +4,61 @@ module IdentityCache
|
|
4
4
|
|
5
5
|
included do |base|
|
6
6
|
base.class_attribute :parent_expiration_entries
|
7
|
-
base.parent_expiration_entries =
|
7
|
+
base.parent_expiration_entries = Hash.new{ |hash, key| hash[key] = [] }
|
8
8
|
end
|
9
|
-
|
10
|
-
module ClassMethods
|
11
|
-
private
|
12
9
|
|
13
|
-
def add_parent_expiration_entry(after_action_name)
|
14
|
-
parent_expiration_entries << after_action_name
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
10
|
def expire_parent_caches
|
19
|
-
|
20
|
-
|
11
|
+
parents_to_expire = {}
|
12
|
+
add_parents_to_cache_expiry_set(parents_to_expire)
|
13
|
+
parents_to_expire.each_value do |parent|
|
14
|
+
parent.send(:expire_primary_index)
|
21
15
|
end
|
22
16
|
end
|
23
17
|
|
24
|
-
def
|
25
|
-
|
18
|
+
def add_parents_to_cache_expiry_set(parents_to_expire)
|
19
|
+
self.class.parent_expiration_entries.each do |association_name, cached_associations|
|
20
|
+
parents_to_expire_on_changes(parents_to_expire, association_name, cached_associations)
|
21
|
+
end
|
22
|
+
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
def add_record_to_cache_expiry_set(parents_to_expire, record)
|
25
|
+
key = record.primary_cache_index_key
|
26
|
+
unless parents_to_expire[key]
|
27
|
+
parents_to_expire[key] = record
|
28
|
+
record.add_parents_to_cache_expiry_set(parents_to_expire) if record.respond_to?(:add_parents_to_cache_expiry_set, true)
|
32
29
|
end
|
30
|
+
end
|
33
31
|
|
32
|
+
def parents_to_expire_on_changes(parents_to_expire, association_name, cached_associations)
|
33
|
+
parent_association = self.class.reflect_on_association(association_name)
|
34
|
+
foreign_key = parent_association.association_foreign_key
|
35
|
+
|
36
|
+
new_parent = send(association_name)
|
37
|
+
|
38
|
+
old_parent = nil
|
34
39
|
if transaction_changed_attributes[foreign_key].present?
|
35
40
|
begin
|
36
|
-
|
37
|
-
|
38
|
-
|
41
|
+
if parent_association.options[:polymorhpic]
|
42
|
+
klass = transaction_changed_attributes[parent_association.association_foreign_key].try(:constantize)
|
43
|
+
klass ||= new_parent.class
|
44
|
+
else
|
45
|
+
klass = parent_association.klass
|
46
|
+
end
|
47
|
+
old_parent = klass.find(transaction_changed_attributes[foreign_key])
|
39
48
|
rescue ActiveRecord::RecordNotFound => e
|
40
49
|
# suppress errors finding the old parent if its been destroyed since it will have expired itself in that case
|
41
50
|
end
|
42
51
|
end
|
43
52
|
|
44
|
-
|
53
|
+
cached_associations.each do |parent_class, only_on_foreign_key_change|
|
54
|
+
if new_parent && new_parent.is_a?(parent_class) && should_expire_identity_cache_parent?(foreign_key, only_on_foreign_key_change)
|
55
|
+
add_record_to_cache_expiry_set(parents_to_expire, new_parent)
|
56
|
+
end
|
57
|
+
|
58
|
+
if old_parent && old_parent.is_a?(parent_class)
|
59
|
+
add_record_to_cache_expiry_set(parents_to_expire, old_parent)
|
60
|
+
end
|
61
|
+
end
|
45
62
|
end
|
46
63
|
|
47
64
|
def should_expire_identity_cache_parent?(foreign_key, only_on_foreign_key_change)
|
@@ -80,7 +80,13 @@ module IdentityCache
|
|
80
80
|
def record_from_coder(coder) #:nodoc:
|
81
81
|
if coder.present? && coder.has_key?(:class)
|
82
82
|
record = coder[:class].allocate
|
83
|
-
|
83
|
+
empty_serialized_attrs =
|
84
|
+
if defined?(ActiveRecord::Type::Serialized)
|
85
|
+
coder[:class].columns.find { |t| t.cast_type.is_a?(ActiveRecord::Type::Serialized) }.nil?
|
86
|
+
else
|
87
|
+
coder[:class].serialized_attributes.empty?
|
88
|
+
end
|
89
|
+
unless empty_serialized_attrs
|
84
90
|
coder = coder.dup
|
85
91
|
coder['attributes'] = coder['attributes'].dup
|
86
92
|
end
|
@@ -120,4 +120,19 @@ class CacheInvalidationTest < IdentityCache::TestCase
|
|
120
120
|
refute IdentityCache.cache.fetch(expected_key) { nil }
|
121
121
|
end
|
122
122
|
end
|
123
|
+
|
124
|
+
def test_dedup_cache_invalidation_of_records_embedded_twice_through_different_associations
|
125
|
+
Item.cache_has_many :associated_records, embed: true
|
126
|
+
AssociatedRecord.cache_has_many :deeply_associated_records, embed: true
|
127
|
+
Item.cache_has_many :deeply_associated_records, embed: true
|
128
|
+
|
129
|
+
deeply_associated_record = DeeplyAssociatedRecord.new(name: 'deep', item_id: @record.id)
|
130
|
+
@record.associated_records[0].deeply_associated_records << deeply_associated_record
|
131
|
+
deeply_associated_record.reload
|
132
|
+
|
133
|
+
Item.any_instance.expects(:expire_primary_index).once
|
134
|
+
|
135
|
+
deeply_associated_record.name = "deep2"
|
136
|
+
deeply_associated_record.save!
|
137
|
+
end
|
123
138
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class DeeplyNestedAssociatedRecordHasOneTest < IdentityCache::TestCase
|
4
|
+
def test_deeply_nested_models_can_cache_has_one_associations
|
5
|
+
assert_nothing_raised do
|
6
|
+
Deeply::Nested::AssociatedRecord.has_one :polymorphic_record, as: 'owner'
|
7
|
+
Deeply::Nested::AssociatedRecord.cache_has_one :polymorphic_record, inverse_name: :owner
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_deeply_nested_models_can_cache_has_many_associations
|
12
|
+
assert_nothing_raised do
|
13
|
+
Deeply::Nested::AssociatedRecord.has_many :polymorphic_records, as: 'owner'
|
14
|
+
Deeply::Nested::AssociatedRecord.cache_has_many :polymorphic_records, inverse_name: :owner
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
Binary file
|
@@ -20,6 +20,7 @@ module ActiveRecordObjects
|
|
20
20
|
def setup_models(base = ActiveRecord::Base)
|
21
21
|
Object.send :const_set, 'DeeplyAssociatedRecord', Class.new(base) {
|
22
22
|
include IdentityCache
|
23
|
+
belongs_to :item
|
23
24
|
belongs_to :associated_record
|
24
25
|
default_scope { order('name DESC') }
|
25
26
|
}
|
@@ -47,10 +48,17 @@ module ActiveRecordObjects
|
|
47
48
|
belongs_to :owner, :polymorphic => true
|
48
49
|
}
|
49
50
|
|
51
|
+
Object.send :const_set, 'Deeply', Module.new
|
52
|
+
Deeply.send :const_set, 'Nested', Module.new
|
53
|
+
Deeply::Nested.send :const_set, 'AssociatedRecord', Class.new(base) {
|
54
|
+
include IdentityCache
|
55
|
+
}
|
56
|
+
|
50
57
|
Object.send :const_set, 'Item', Class.new(base) {
|
51
58
|
include IdentityCache
|
52
59
|
belongs_to :item
|
53
60
|
has_many :associated_records, inverse_of: :item
|
61
|
+
has_many :deeply_associated_records, inverse_of: :item
|
54
62
|
has_many :normalized_associated_records
|
55
63
|
has_many :not_cached_records
|
56
64
|
has_many :polymorphic_records, :as => 'owner'
|
@@ -81,5 +89,8 @@ module ActiveRecordObjects
|
|
81
89
|
Object.send :remove_const, 'Item'
|
82
90
|
Object.send :remove_const, 'ItemTwo'
|
83
91
|
Object.send :remove_const, 'KeyedRecord'
|
92
|
+
Deeply::Nested.send :remove_const, 'AssociatedRecord'
|
93
|
+
Deeply.send :remove_const, 'Nested'
|
94
|
+
Object.send :remove_const, 'Deeply'
|
84
95
|
end
|
85
96
|
end
|
@@ -31,13 +31,13 @@ module DatabaseConnection
|
|
31
31
|
end
|
32
32
|
|
33
33
|
TABLES = {
|
34
|
-
:polymorphic_records => [[:string, :owner_type], [:integer, :owner_id], [:timestamps]],
|
35
|
-
:deeply_associated_records => [[:string, :name], [:integer, :associated_record_id], [:timestamps]],
|
34
|
+
:polymorphic_records => [[:string, :owner_type], [:integer, :owner_id], [:timestamps, null: true]],
|
35
|
+
:deeply_associated_records => [[:string, :name], [:integer, :associated_record_id], [:integer, :item_id], [:timestamps, null: true]],
|
36
36
|
:associated_records => [[:string, :name], [:integer, :item_id], [:integer, :item_two_id]],
|
37
|
-
:normalized_associated_records => [[:string, :name], [:integer, :item_id], [:timestamps]],
|
38
|
-
:not_cached_records => [[:string, :name], [:integer, :item_id], [:timestamps]],
|
39
|
-
:items => [[:integer, :item_id], [:string, :title], [:timestamps]],
|
40
|
-
:items2 => [[:integer, :item_id], [:string, :title], [:timestamps]],
|
37
|
+
:normalized_associated_records => [[:string, :name], [:integer, :item_id], [:timestamps, null: true]],
|
38
|
+
:not_cached_records => [[:string, :name], [:integer, :item_id], [:timestamps, null: true]],
|
39
|
+
:items => [[:integer, :item_id], [:string, :title], [:timestamps, null: true]],
|
40
|
+
:items2 => [[:integer, :item_id], [:string, :title], [:timestamps, null: true]],
|
41
41
|
:keyed_records => [[:string, :value], :primary_key => "hashed_key"],
|
42
42
|
}
|
43
43
|
|
data/test/test_helper.rb
CHANGED
@@ -24,11 +24,16 @@ class ActiveSupport::Cache::MemcachedStore
|
|
24
24
|
alias_method_chain :read_multi, :instrumentation
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
MiniTest::Test = MiniTest::Unit::TestCase unless defined?(MiniTest::Test)
|
28
|
+
class IdentityCache::TestCase < Minitest::Test
|
28
29
|
include ActiveRecordObjects
|
29
30
|
attr_reader :backend
|
30
31
|
|
31
32
|
def setup
|
33
|
+
if ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks=)
|
34
|
+
ActiveRecord::Base.raise_in_transactional_callbacks = true
|
35
|
+
end
|
36
|
+
|
32
37
|
DatabaseConnection.drop_tables
|
33
38
|
DatabaseConnection.create_tables
|
34
39
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: identity_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Camilo Lopez
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date: 2015-05-
|
17
|
+
date: 2015-05-29 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: ar_transaction_changes
|
@@ -226,6 +226,7 @@ files:
|
|
226
226
|
- test/cache_fetch_includes_test.rb
|
227
227
|
- test/cache_hash_test.rb
|
228
228
|
- test/cache_invalidation_test.rb
|
229
|
+
- test/deeply_nested_associated_record_test.rb
|
229
230
|
- test/denormalized_has_many_test.rb
|
230
231
|
- test/denormalized_has_one_test.rb
|
231
232
|
- test/fetch_multi_test.rb
|
@@ -267,7 +268,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
267
268
|
version: '0'
|
268
269
|
requirements: []
|
269
270
|
rubyforge_project:
|
270
|
-
rubygems_version: 2.
|
271
|
+
rubygems_version: 2.2.3
|
271
272
|
signing_key:
|
272
273
|
specification_version: 4
|
273
274
|
summary: IdentityCache lets you specify how you want to cache your model objects,
|
@@ -279,6 +280,7 @@ test_files:
|
|
279
280
|
- test/cache_fetch_includes_test.rb
|
280
281
|
- test/cache_hash_test.rb
|
281
282
|
- test/cache_invalidation_test.rb
|
283
|
+
- test/deeply_nested_associated_record_test.rb
|
282
284
|
- test/denormalized_has_many_test.rb
|
283
285
|
- test/denormalized_has_one_test.rb
|
284
286
|
- test/fetch_multi_test.rb
|