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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: abed676c785bc468fee7349b8e28af469499b78b
4
- data.tar.gz: e64ed12cef1b57634416a2d4183313fae2a5f491
3
+ metadata.gz: 8b3399599b1784b0df560313cc902e20e7cd86fd
4
+ data.tar.gz: 4a45bfa103d6095837bf2226a1ccd505219001fb
5
5
  SHA512:
6
- metadata.gz: cd1e569f94285089975e400da50e183731115c186b538ad822edf0bcb6bdf50c6b152a3b159d4196bb5749cd793f3745b19d744821e96479ba5106a1703d3785
7
- data.tar.gz: 45358caea4c1d9ef27a682736cf180cbe982a9ce48946b842c6f788d0cb31c47658ed478f2c056e8e736b71e78dfa9af08abdbc5989e99ef12a96450a0197a3e
6
+ metadata.gz: 50b692df952ca33119c152a4aaaa9bf0e0360c2a56005bd0711a707ea516031a12ceb7f8e9e9599b52f0ba5ce1568c33585b7598bdc9a6a1da018b7b42eec0a3
7
+ data.tar.gz: e9519a582f37d3e029a43046157139ba37c3a897fda507febe592e1219a2d84968c3c10b68780a4c4397eeec9102cfd3ed4f69f41756818e7532b17ce4aa4d1e
data/.travis.yml CHANGED
@@ -1,13 +1,10 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 1.9.3
5
- - 2.0.0
6
4
  - 2.1
7
5
  - 2.2
8
6
 
9
7
  gemfile:
10
- - Gemfile.rails32
11
8
  - Gemfile.rails40
12
9
  - Gemfile.rails41
13
10
  - Gemfile.rails42
@@ -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
- child_association = child_class.reflect_on_association(options[:inverse_name])
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
- after_action_name = "expire_parent_cache_#{self.name.underscore}"
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
- after_commit :#{after_action_name}
293
- after_touch :#{after_action_name}
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 = Set.new
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
- self.class.parent_expiration_entries.each do |parent_expiration_entry|
20
- send(parent_expiration_entry)
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 expire_parent_cache_on_changes(parent_name, foreign_key, parent_class, only_on_foreign_key_change)
25
- new_parent = send(parent_name)
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
- if new_parent && new_parent.respond_to?(:expire_primary_index, true)
28
- if should_expire_identity_cache_parent?(foreign_key, only_on_foreign_key_change)
29
- new_parent.send(:expire_primary_index)
30
- new_parent.send(:expire_parent_caches) if new_parent.respond_to?(:expire_parent_caches, true)
31
- end
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
- old_parent = parent_class.find(transaction_changed_attributes[foreign_key])
37
- old_parent.send(:expire_primary_index) if old_parent.respond_to?(:expire_primary_index, true)
38
- old_parent.send(:expire_parent_caches) if old_parent.respond_to?(:expire_parent_caches, true)
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
- true
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
- unless coder[:class].serialized_attributes.empty?
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
@@ -1,4 +1,4 @@
1
1
  module IdentityCache
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5"
3
3
  CACHE_VERSION = 5
4
4
  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
- class IdentityCache::TestCase < MiniTest::Unit::TestCase
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
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-13 00:00:00.000000000 Z
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.4.5
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