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 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