record-cache 0.1.2 → 0.1.3

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.
Files changed (52) hide show
  1. checksums.yaml +15 -0
  2. data/lib/record_cache.rb +2 -1
  3. data/lib/record_cache/base.rb +63 -22
  4. data/lib/record_cache/datastore/active_record.rb +5 -3
  5. data/lib/record_cache/datastore/active_record_30.rb +95 -38
  6. data/lib/record_cache/datastore/active_record_31.rb +157 -54
  7. data/lib/record_cache/datastore/active_record_32.rb +444 -0
  8. data/lib/record_cache/dispatcher.rb +47 -47
  9. data/lib/record_cache/multi_read.rb +14 -1
  10. data/lib/record_cache/query.rb +36 -25
  11. data/lib/record_cache/statistics.rb +5 -5
  12. data/lib/record_cache/strategy/base.rb +49 -19
  13. data/lib/record_cache/strategy/full_table_cache.rb +81 -0
  14. data/lib/record_cache/strategy/index_cache.rb +38 -36
  15. data/lib/record_cache/strategy/unique_index_cache.rb +130 -0
  16. data/lib/record_cache/strategy/util.rb +12 -12
  17. data/lib/record_cache/test/resettable_version_store.rb +2 -9
  18. data/lib/record_cache/version.rb +1 -1
  19. data/lib/record_cache/version_store.rb +23 -16
  20. data/spec/db/schema.rb +12 -0
  21. data/spec/db/seeds.rb +10 -0
  22. data/spec/lib/active_record/visitor_spec.rb +22 -0
  23. data/spec/lib/base_spec.rb +21 -0
  24. data/spec/lib/dispatcher_spec.rb +24 -46
  25. data/spec/lib/multi_read_spec.rb +6 -6
  26. data/spec/lib/query_spec.rb +43 -43
  27. data/spec/lib/statistics_spec.rb +28 -28
  28. data/spec/lib/strategy/base_spec.rb +98 -87
  29. data/spec/lib/strategy/full_table_cache_spec.rb +68 -0
  30. data/spec/lib/strategy/index_cache_spec.rb +112 -69
  31. data/spec/lib/strategy/query_cache_spec.rb +83 -0
  32. data/spec/lib/strategy/unique_index_on_id_cache_spec.rb +317 -0
  33. data/spec/lib/strategy/unique_index_on_string_cache_spec.rb +168 -0
  34. data/spec/lib/strategy/util_spec.rb +67 -49
  35. data/spec/lib/version_store_spec.rb +22 -41
  36. data/spec/models/address.rb +9 -0
  37. data/spec/models/apple.rb +1 -1
  38. data/spec/models/banana.rb +21 -2
  39. data/spec/models/language.rb +5 -0
  40. data/spec/models/person.rb +1 -1
  41. data/spec/models/store.rb +2 -1
  42. data/spec/spec_helper.rb +7 -4
  43. data/spec/support/after_commit.rb +2 -0
  44. data/spec/support/matchers/hit_cache_matcher.rb +10 -6
  45. data/spec/support/matchers/log.rb +45 -0
  46. data/spec/support/matchers/miss_cache_matcher.rb +10 -6
  47. data/spec/support/matchers/use_cache_matcher.rb +10 -6
  48. metadata +156 -161
  49. data/lib/record_cache/strategy/id_cache.rb +0 -93
  50. data/lib/record_cache/strategy/request_cache.rb +0 -49
  51. data/spec/lib/strategy/id_cache_spec.rb +0 -168
  52. data/spec/lib/strategy/request_cache_spec.rb +0 -85
@@ -0,0 +1,130 @@
1
+ module RecordCache
2
+ module Strategy
3
+ class UniqueIndexCache < Base
4
+
5
+ # All attributes with a unique index for the given model
6
+ def self.attributes(base)
7
+ (@attributes ||= {})[base.name] ||= []
8
+ end
9
+
10
+ # parse the options and return (an array of) instances of this strategy
11
+ def self.parse(base, record_store, options)
12
+ return nil unless base.table_exists?
13
+
14
+ attributes = [options[:unique_index]].flatten.compact
15
+ # add unique index for :id by default
16
+ attributes << :id if base.columns_hash['id'] unless base.record_cache[:id]
17
+ attributes.uniq! # in development mode, do not keep adding 'id' to the list of unique index attributes
18
+ return nil if attributes.empty?
19
+ attributes.map do |attribute|
20
+ type = base.columns_hash[attribute.to_s].try(:type)
21
+ raise "No column found for unique index '#{index}' on #{base.name}." unless type
22
+ raise "Incorrect type (expected string or integer, found #{type}) for unique index '#{attribute}' on #{base.name}." unless type == :string || type == :integer
23
+ UniqueIndexCache.new(base, attribute, record_store, options, type)
24
+ end
25
+ end
26
+
27
+ def initialize(base, attribute, record_store, options, type)
28
+ super(base, attribute, record_store, options)
29
+ # remember the attributes with a unique index
30
+ UniqueIndexCache.attributes(base) << attribute
31
+ # for unique indexes that are not on the :id column, use key: rc/<key or model name>/<attribute>:
32
+ @cache_key_prefix << "#{attribute}:" unless attribute == :id
33
+ @type = type
34
+ end
35
+
36
+ # Can the cache retrieve the records based on this query?
37
+ def cacheable?(query)
38
+ values = query.where_values(@attribute, @type)
39
+ values && (query.limit.nil? || (query.limit == 1 && values.size == 1))
40
+ end
41
+
42
+ # Update the version store and the record store
43
+ def record_change(record, action)
44
+ key = cache_key(record.send(@attribute))
45
+ if action == :destroy
46
+ version_store.delete(key)
47
+ else
48
+ # update the version store and add the record to the cache
49
+ new_version = version_store.renew(key, version_opts)
50
+ record_store.write(versioned_key(key, new_version), Util.serialize(record))
51
+ end
52
+ end
53
+
54
+ protected
55
+
56
+ # retrieve the record(s) with the given id(s) as an array
57
+ def fetch_records(query)
58
+ ids = query.where_values(@attribute, @type)
59
+ query.wheres.delete(@attribute) # make sure CacheCase.filter! does not see this where anymore
60
+ id_to_key_map = ids.inject({}){|h,id| h[id] = cache_key(id); h }
61
+ # retrieve the current version of the records
62
+ current_versions = version_store.current_multi(id_to_key_map)
63
+ # get the keys for the records for which a current version was found
64
+ id_to_version_key_map = Hash[id_to_key_map.map{ |id, key| current_versions[id] ? [id, versioned_key(key, current_versions[id])] : nil }.compact]
65
+ # retrieve the records from the cache
66
+ records = id_to_version_key_map.size > 0 ? from_cache(id_to_version_key_map) : []
67
+ # query the records with missing ids
68
+ id_to_key_map.except!(*records.map(&@attribute))
69
+ # logging (only in debug mode!) and statistics
70
+ log_id_cache_hit(ids, id_to_key_map.keys) if RecordCache::Base.logger.debug?
71
+ statistics.add(ids.size, records.size) if statistics.active?
72
+ # retrieve records from DB in case there are some missing ids
73
+ records += from_db(id_to_key_map, id_to_version_key_map) if id_to_key_map.size > 0
74
+ # return the array
75
+ records
76
+ end
77
+
78
+ private
79
+
80
+ # ---------------------------- Querying ------------------------------------
81
+
82
+ # retrieve the records from the cache with the given keys
83
+ def from_cache(id_to_versioned_key_map)
84
+ records = record_store.read_multi(*(id_to_versioned_key_map.values)).values.compact
85
+ records.map do |record|
86
+ record = Util.deserialize(record)
87
+ record.becomes(self.instance_variable_get('@base')) unless record.class == self.instance_variable_get('@base')
88
+ record
89
+ end
90
+ end
91
+
92
+ # retrieve the records with the given ids from the database
93
+ def from_db(id_to_key_map, id_to_version_key_map)
94
+ # skip record cache itself
95
+ RecordCache::Base.without_record_cache do
96
+ # set version store in multi-mode
97
+ RecordCache::Base.version_store.multi do
98
+ # set record store in multi-mode
99
+ record_store.multi do
100
+ # retrieve the records from the database
101
+ records = @base.where(@attribute => id_to_key_map.keys).to_a
102
+ records.each do |record|
103
+ versioned_key = id_to_version_key_map[record.send(@attribute)]
104
+ unless versioned_key
105
+ # renew the key in the version store in case it was missing
106
+ key = id_to_key_map[record.send(@attribute)]
107
+ versioned_key = versioned_key(key, version_store.renew(key, version_opts))
108
+ end
109
+ # store the record based on the versioned key
110
+ record_store.write(versioned_key, Util.serialize(record))
111
+ end
112
+ records
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ # ------------------------- Utility methods ----------------------------
119
+
120
+ # log cache hit/miss to debug log
121
+ def log_id_cache_hit(ids, missing_ids)
122
+ hit = missing_ids.empty? ? "hit" : ids.size == missing_ids.size ? "miss" : "partial hit"
123
+ missing = missing_ids.empty? || ids.size == missing_ids.size ? "" : ": missing #{missing_ids.inspect}"
124
+ msg = "UniqueIndexCache on '#{@base.name}.#{@attribute}' #{hit} for ids #{ids.size == 1 ? ids.first.inspect : ids.inspect}#{missing}"
125
+ RecordCache::Base.logger.debug{ msg }
126
+ end
127
+
128
+ end
129
+ end
130
+ end
@@ -11,17 +11,17 @@ module RecordCache
11
11
  # creates a shallow clone with a version and without associations
12
12
  def serialize(record)
13
13
  {CLASS_KEY => record.class.name,
14
- ATTRIBUTES_KEY => record.instance_variable_get(:@attributes)}.freeze
14
+ ATTRIBUTES_KEY => record.instance_variable_get(:@attributes).dup}
15
15
  end
16
16
 
17
17
  # deserialize a cached record
18
18
  def deserialize(serialized)
19
- record = serialized[CLASS_KEY].constantize.new
19
+ record = serialized[CLASS_KEY].constantize.allocate
20
20
  attributes = serialized[ATTRIBUTES_KEY]
21
- record.instance_variable_set(:@attributes, Hash[attributes])
22
- record.instance_variable_set(:@new_record, false)
23
- record.instance_variable_set(:@changed_attributes, {})
24
- record.instance_variable_set(:@previously_changed, {})
21
+ record.class.serialized_attributes.keys.each do |attribute|
22
+ attributes[attribute] = attributes[attribute].unserialize if attributes[attribute].respond_to?(:unserialize)
23
+ end
24
+ record.init_with('attributes' => attributes)
25
25
  record
26
26
  end
27
27
 
@@ -81,23 +81,23 @@ module RecordCache
81
81
  # Proc.new{ |x,y| { ([(COLLATER.collate(x.name) || NIL_COMES_FIRST), (y.updated_at || NIL_COMES_FIRST)] <=> [COLLATER.collate(y.name), x.updated_at]) || 1 }
82
82
  eval("Proc.new{ |x,y| (#{sort[0]} <=> #{sort[1]}) || 1 }")
83
83
  end
84
-
84
+
85
85
  # If +x.nil?+ this class will return -1 for +x <=> y+
86
86
  NIL_COMES_FIRST = ((class NilComesFirst; def <=>(y); -1; end; end); NilComesFirst.new)
87
-
87
+
88
88
  # StringCollator uses the Rails transliterate method for collation
89
89
  module Collator
90
90
  @collated = []
91
-
91
+
92
92
  def self.clear
93
93
  @collated.each { |string| string.send(:remove_instance_variable, :@rc_collated) }
94
94
  @collated.clear
95
95
  end
96
-
96
+
97
97
  def self.collate(string)
98
98
  collated = string.instance_variable_get(:@rc_collated)
99
99
  return collated if collated
100
- normalized = ActiveSupport::Multibyte::Unicode.normalize(ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c).mb_chars
100
+ normalized = ActiveSupport::Multibyte::Unicode.normalize(ActiveSupport::Multibyte::Unicode.tidy_bytes(string || ''), :c).mb_chars
101
101
  collated = I18n.transliterate(normalized).downcase.mb_chars
102
102
  # transliterate will replace ignored/unknown chars with ? the following line replaces ? with the original character
103
103
  collated.chars.each_with_index{ |c, i| collated[i] = normalized[i] if c == '?' } if collated.index('?')
@@ -108,7 +108,7 @@ module RecordCache
108
108
  end
109
109
  end
110
110
  end
111
-
111
+
112
112
  end
113
113
  end
114
114
  end
@@ -11,7 +11,6 @@ module RecordCache
11
11
  base.extend ClassMethods
12
12
  base.send(:include, InstanceMethods)
13
13
  base.instance_eval do
14
- alias_method_chain :increment, :reset
15
14
  alias_method_chain :renew, :reset
16
15
  end
17
16
  end
@@ -21,18 +20,12 @@ module RecordCache
21
20
 
22
21
  module InstanceMethods
23
22
 
24
- def increment_with_reset(key)
23
+ def renew_with_reset(key, opts = {})
25
24
  updated_version_keys << key
26
- increment_without_reset(key)
27
- end
28
-
29
- def renew_with_reset(key)
30
- updated_version_keys << key
31
- renew_without_reset(key)
25
+ renew_without_reset(key, opts)
32
26
  end
33
27
 
34
28
  def reset!
35
- RecordCache::Strategy::RequestCache.clear
36
29
  updated_version_keys.each { |key| delete(key) }
37
30
  updated_version_keys.clear
38
31
  end
@@ -1,5 +1,5 @@
1
1
  module RecordCache # :nodoc:
2
2
  module Version # :nodoc:
3
- STRING = '0.1.2'
3
+ STRING = '0.1.3'
4
4
  end
5
5
  end
@@ -3,7 +3,9 @@ module RecordCache
3
3
  attr_accessor :store
4
4
 
5
5
  def initialize(store)
6
- raise "Must be an ActiveSupport::Cache::Store" unless store.is_a?(ActiveSupport::Cache::Store)
6
+ [:write, :read, :read_multi, :delete].each do |method|
7
+ raise "Store #{store.class.name} must respond to #{method}" unless store.respond_to?(method)
8
+ end
7
9
  @store = store
8
10
  end
9
11
 
@@ -22,31 +24,36 @@ module RecordCache
22
24
  Hash[id_key_map.map{ |id, key| [id, current_versions[key]] }]
23
25
  end
24
26
 
25
- # In case the version store did not have a key anymore, call this methods
26
- # to reset the key with a (unique) new version
27
- def renew(key)
27
+ # Call this method to reset the key to a new (unique) version
28
+ def renew(key, options = {})
28
29
  new_version = (Time.current.to_f * 10000).to_i
29
- @store.write(key, new_version)
30
- RecordCache::Base.logger.debug("Version Store: renew #{key}: nil => #{new_version}") if RecordCache::Base.logger.debug?
30
+ seconds = options[:ttl] ? options[:ttl] + (rand(options[:ttl] / 2) * [1, -1].sample) : nil
31
+ @store.write(key, new_version, {:expires_in => seconds})
32
+ RecordCache::Base.logger.debug{ "Version Store: renew #{key}: nil => #{new_version}" }
31
33
  new_version
32
34
  end
33
35
 
34
- # Increment the current version for the given key, in case of record updates
35
- def increment(key)
36
- version = @store.increment(key, 1)
37
- # renew key in case the version store already purged the key
38
- if version.nil? || version == 1
39
- version = renew(key)
36
+ # perform several actions on the version store in one go
37
+ # Dalli: Turn on quiet aka noreply support. All relevant operations within this block will be effectively pipelined using 'quiet' operations where possible.
38
+ # Currently supports the set, add, replace and delete operations for Dalli cache.
39
+ def multi(&block)
40
+ if @store.respond_to?(:multi)
41
+ @store.multi(&block)
40
42
  else
41
- RecordCache::Base.logger.debug("Version Store: incremented #{key}: #{version - 1} => #{version}") if RecordCache::Base.logger.debug?
43
+ yield
42
44
  end
43
- version
44
45
  end
45
-
46
+
47
+ # @deprecated: use renew instead
48
+ def increment(key)
49
+ RecordCache::Base.logger.debug{ "increment is deprecated, use renew instead. Called from: #{caller[0]}" }
50
+ renew(key)
51
+ end
52
+
46
53
  # Delete key from the version store (records cached in the Record Store belonging to this key will become unreachable)
47
54
  def delete(key)
48
55
  deleted = @store.delete(key)
49
- RecordCache::Base.logger.debug("Version Store: deleted #{key}") if RecordCache::Base.logger.debug?
56
+ RecordCache::Base.logger.debug{ "Version Store: deleted #{key}" }
50
57
  deleted
51
58
  end
52
59
 
@@ -39,4 +39,16 @@ ActiveRecord::Schema.define :version => 0 do
39
39
  t.integer :person_id
40
40
  end
41
41
 
42
+ create_table :addresses, :force => true do |t|
43
+ t.integer :id
44
+ t.string :name
45
+ t.integer :store_id
46
+ t.string :location
47
+ end
48
+
49
+ create_table :languages, :force => true do |t|
50
+ t.string :name
51
+ t.string :locale
52
+ end
53
+
42
54
  end
@@ -1,3 +1,4 @@
1
+ # coding: utf-8
1
2
  ActiveRecord::Schema.define :version => 1 do
2
3
 
3
4
  # Make sure that at the beginning of the tests, NOTHING is known to Record Cache
@@ -11,6 +12,10 @@ ActiveRecord::Schema.define :version => 1 do
11
12
  @blue_fruits = Store.create!(:name => "Blue Fruits", :owner => @blue)
12
13
  @cris_bananas = Store.create!(:name => "Chris Bananas", :owner => @cris)
13
14
 
15
+ @adam_apples_address = Address.create!(:name => "101 1st street", :store => @adam_apples)
16
+ @blue_fruits_address = Address.create!(:name => "102 1st street", :store => @blue_fruits)
17
+ @cris_bananas_address = Address.create!(:name => "103 1st street", :store => @cris_bananas, :location => {latitue: 27.175015, longitude: 78.042155, dms_lat: %(27° 10' 30.0540" N), dms_long: %(78° 2' 31.7580" E)})
18
+
14
19
  @fry = Person.create!(:name => "Fry", :birthday => Date.civil(1985,01,20), :height => 1.69)
15
20
  @chase = Person.create!(:name => "Chase", :birthday => Date.civil(1970,07,03), :height => 1.91)
16
21
  @penny = Person.create!(:name => "Penny", :birthday => Date.civil(1958,04,16), :height => 1.61)
@@ -36,5 +41,10 @@ ActiveRecord::Schema.define :version => 1 do
36
41
  Pear.create!(:name => "Blue Pear 3", :store => @blue_fruits, :person => @chase)
37
42
  Pear.create!(:name => "Blue Pear 4", :store => @blue_fruits, :person => @chase)
38
43
 
44
+ Language.create!(:name => "English (US)", :locale => "en-US")
45
+ Language.create!(:name => "English (GB)", :locale => "en-GB")
46
+ Language.create!(:name => "Nederlands (NL)", :locale => "du-NL")
47
+ Language.create!(:name => "Magyar", :locale => "hu")
48
+
39
49
  RecordCache::Base.enable
40
50
  end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'ActiveRecord Visitor' do
5
+
6
+ def find_visit_methods(visitor_class)
7
+ (visitor_class.instance_methods + visitor_class.private_instance_methods).select{ |method| method.to_s =~ /^visit_Arel_/ }.sort.uniq
8
+ end
9
+
10
+ it 'should implement all visitor methods' do
11
+ all_visit_methods = find_visit_methods(Arel::Visitors::ToSql)
12
+ rc_visit_methods = find_visit_methods(RecordCache::Arel::QueryVisitor)
13
+ expect(all_visit_methods - rc_visit_methods).to be_empty
14
+ end
15
+
16
+ it 'should not implement old visitor methods' do
17
+ all_visit_methods = find_visit_methods(Arel::Visitors::ToSql)
18
+ rc_visit_methods = find_visit_methods(RecordCache::Arel::QueryVisitor)
19
+ expect(rc_visit_methods - all_visit_methods).to be_empty
20
+ end
21
+
22
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe RecordCache::Base do
5
+
6
+ it "should run a block in enabled mode" do
7
+ RecordCache::Base.disable!
8
+ RecordCache::Base.enabled do
9
+ expect(RecordCache::Base.status).to eq(RecordCache::ENABLED)
10
+ end
11
+ expect(RecordCache::Base.status).to eq(RecordCache::DISABLED)
12
+ end
13
+
14
+ it "should be possible to provide a different logger" do
15
+ custom_logger = Logger.new(STDOUT)
16
+ RecordCache::Base.logger = custom_logger
17
+ expect(RecordCache::Base.logger).to eq(custom_logger)
18
+ RecordCache::Base.logger = nil
19
+ expect(RecordCache::Base.logger).to eq(::ActiveRecord::Base.logger)
20
+ end
21
+ end
@@ -4,55 +4,45 @@ describe RecordCache::Dispatcher do
4
4
  before(:each) do
5
5
  @apple_dispatcher = Apple.record_cache
6
6
  end
7
-
8
- it "should raise an error when the same index is added twice" do
9
- lambda { @apple_dispatcher.register(:store_id, RecordCache::Strategy::IdCache, nil, {}) }.should raise_error("Multiple record cache definitions found for 'store_id' on Apple")
10
- end
11
-
12
- it "should return the Cache for the requested strategy" do
13
- @apple_dispatcher[:id].class.should == RecordCache::Strategy::IdCache
14
- @apple_dispatcher[:store_id].class.should == RecordCache::Strategy::IndexCache
15
- end
16
7
 
17
- it "should return nil for unknown requested strategies" do
18
- @apple_dispatcher[:unknown].should == nil
8
+ it "should return the (ordered) strategy classes" do
9
+ expect(RecordCache::Dispatcher.strategy_classes).to eq([RecordCache::Strategy::UniqueIndexCache, RecordCache::Strategy::FullTableCache, RecordCache::Strategy::IndexCache])
19
10
  end
20
11
 
21
- it "should return cacheable? true if there is a cacheable strategy that accepts the query" do
22
- query = RecordCache::Query.new
23
- mock(@apple_dispatcher).first_cacheable_strategy(query) { Object.new }
24
- @apple_dispatcher.cacheable?(query).should == true
12
+ it "should be able to register a new strategy" do
13
+ RecordCache::Dispatcher.strategy_classes << Integer
14
+ expect(RecordCache::Dispatcher.strategy_classes).to include(Integer)
15
+ RecordCache::Dispatcher.strategy_classes.delete(Integer)
25
16
  end
26
17
 
27
- context "fetch" do
28
- it "should delegate fetch to the Request Cache if present" do
29
- query = RecordCache::Query.new
30
- mock(@apple_dispatcher[:request_cache]).fetch(query)
31
- @apple_dispatcher.fetch(query)
32
- end
33
-
34
- it "should delegate fetch to the first cacheable strategy if Request Cache is not present" do
35
- query = RecordCache::Query.new
36
- banana_dispatcher = Banana.record_cache
37
- banana_dispatcher[:request_cache].should == nil
38
- mock(banana_dispatcher).first_cacheable_strategy(query) { mock(Object.new).fetch(query) }
39
- banana_dispatcher.fetch(query)
18
+ context "parse" do
19
+ it "should raise an error when the same index is added twice" do
20
+ expect{ Apple.cache_records(:index => :store_id) }.to raise_error("Multiple record cache definitions found for 'store_id' on Apple")
40
21
  end
41
22
  end
42
23
 
24
+ it "should return the Cache for the requested strategy" do
25
+ expect(@apple_dispatcher[:id].class).to eq(RecordCache::Strategy::UniqueIndexCache)
26
+ expect(@apple_dispatcher[:store_id].class).to eq(RecordCache::Strategy::IndexCache)
27
+ end
28
+
29
+ it "should return nil for unknown requested strategies" do
30
+ expect(@apple_dispatcher[:unknown]).to be_nil
31
+ end
32
+
43
33
  context "record_change" do
44
34
  it "should dispatch record_change to all strategies" do
45
35
  apple = Apple.first
46
36
  [:id, :store_id, :person_id].each do |strategy|
47
- mock(@apple_dispatcher[strategy]).record_change(apple, :create)
37
+ expect(@apple_dispatcher[strategy]).to receive(:record_change).with(apple, :create)
48
38
  end
49
39
  @apple_dispatcher.record_change(apple, :create)
50
40
  end
51
41
 
52
42
  it "should not dispatch record_change for updates without changes" do
53
43
  apple = Apple.first
54
- [:request_cache, :id, :store_id, :person_id].each do |strategy|
55
- mock(@apple_dispatcher[strategy]).record_change(anything, anything).times(0)
44
+ [:id, :store_id, :person_id].each do |strategy|
45
+ expect(@apple_dispatcher[strategy]).to_not receive(:record_change)
56
46
  end
57
47
  @apple_dispatcher.record_change(apple, :update)
58
48
  end
@@ -60,27 +50,15 @@ describe RecordCache::Dispatcher do
60
50
 
61
51
  context "invalidate" do
62
52
  it "should default to the :id strategy" do
63
- mock(@apple_dispatcher[:id]).invalidate(15)
53
+ expect(@apple_dispatcher[:id]).to receive(:invalidate).with(15)
64
54
  @apple_dispatcher.invalidate(15)
65
55
  end
66
56
 
67
57
  it "should delegate to given strategy" do
68
- mock(@apple_dispatcher[:id]).invalidate(15)
69
- mock(@apple_dispatcher[:store_id]).invalidate(31)
58
+ expect(@apple_dispatcher[:id]).to receive(:invalidate).with(15)
59
+ expect(@apple_dispatcher[:store_id]).to receive(:invalidate).with(31)
70
60
  @apple_dispatcher.invalidate(:id, 15)
71
61
  @apple_dispatcher.invalidate(:store_id, 31)
72
62
  end
73
-
74
- it "should invalidate the request cache" do
75
- store_dispatcher = Store.record_cache
76
- mock(store_dispatcher[:request_cache]).invalidate(15)
77
- store_dispatcher.invalidate(:id, 15)
78
- end
79
-
80
- it "should even invalidate the request cache if the given strategy is not known" do
81
- store_dispatcher = Store.record_cache
82
- mock(store_dispatcher[:request_cache]).invalidate(31)
83
- store_dispatcher.invalidate(:unknown_id, 31)
84
- end
85
63
  end
86
64
  end