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