record-cache 0.1.2 → 0.1.3

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