interlock 1.2 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,4 +1,6 @@
1
1
 
2
+ v1.3. Add basic finder memoization.
3
+
2
4
  v1.2. Add compatibility with Memcached client.
3
5
 
4
6
  v1.1. Add :perform => false option. Cache :content_for. Test nested view_cache blocks. Don't assign conventional dependency when called from the view, because we don't know what the controller might have already specified.
data/Manifest CHANGED
@@ -6,8 +6,10 @@ lib/interlock/action_view.rb
6
6
  lib/interlock/active_record.rb
7
7
  lib/interlock/config.rb
8
8
  lib/interlock/core_extensions.rb
9
+ lib/interlock/finders.rb
9
10
  lib/interlock/interlock.rb
10
11
  lib/interlock/lock.rb
12
+ lib/interlock/pass_through_store.rb
11
13
  lib/interlock.rb
12
14
  LICENSE
13
15
  Manifest
@@ -77,6 +79,7 @@ test/integration/server_test.rb
77
79
  test/setup.rb
78
80
  test/teardown.rb
79
81
  test/test_helper.rb
82
+ test/unit/finder_test.rb
80
83
  test/unit/interlock_test.rb
81
84
  test/unit/lock_test.rb
82
85
  TODO
data/README CHANGED
@@ -19,11 +19,15 @@ If you use this software, please {make a donation}[http://blog.evanweaver.com/do
19
19
 
20
20
  Note that Rails 2.0.2 is required for caching <tt>content_for</tt> or nesting <tt>view_cache</tt> blocks.
21
21
 
22
- == What it does
22
+ == Features
23
23
 
24
- Interlock makes your view fragments and associated controller blocks march along together. If a fragment is fresh, the controller behavior won't run. This eliminates duplicate effort from your request cycle. Your controller blocks run so infrequently that you can use regular ActiveRecord finders and not worry about object caching at all.
24
+ Interlock is an intelligent fragment cache for Rails.
25
25
 
26
- Interlock automatically tracks invalidation dependencies based on the model lifecyle, and supports arbitrary levels of scoping per-block. It also caches <tt>content_for</tt> calls, unlike regular Rails.
26
+ It works by making your view fragments and associated controller blocks march along together. If a fragment is fresh, the controller behavior won't run. This eliminates duplicate effort from your request cycle. Your controller blocks run so infrequently that you can use regular ActiveRecord finders and not worry about object caching at all.
27
+
28
+ Invalidations are automatically tracked based on the model lifecyle, and you can scope any block to an arbitrary level. Interlock also caches <tt>content_for</tt> calls, unlike regular Rails, and can optionally cache simple finders.
29
+
30
+ Interlock uses a tiered caching layer so that multiple lookups of a key only hit memcached once per request.
27
31
 
28
32
  == Installation
29
33
 
@@ -87,6 +91,18 @@ You can use multiple instance variables in one block, of course. Just make sure
87
91
 
88
92
  See ActionController::Base and ActionView::Helpers::CacheHelper for more details.
89
93
 
94
+ == Caching finders
95
+
96
+ Interlock 1.3 adds the ability to cache simple finder lookups. Add this line in <tt>config/memcached.yml</tt>:
97
+
98
+ with_finders: true
99
+
100
+ Now, whenever you call <b>find</b>, <b>find_by_id</b>, or <b>find_all_by_id</b> with a single id or an array of ids, the cache will be used. The cache key for each record invalidates when the record is saved or destroyed. Memcached's multiget mode is used for maximum performance.
101
+
102
+ If you pass any parameters other than ids, or use dynamic finders, the cache will not be used. This means that <tt>:include</tt> works as expected and does not require complicated invalidation.
103
+
104
+ See Interlock::Finders for more.
105
+
90
106
  == Notes
91
107
 
92
108
  You will not see any actual cache reuse in development mode unless you set <tt>config.action_controller.perform_caching = true</tt> in <tt>config/environments/development.rb</tt>.
data/TODO CHANGED
@@ -1,6 +1,11 @@
1
1
 
2
- * Add test coverage of STI child classes
2
+ Soon:
3
+
4
+ * Throw a nice error message when the requested memcache client cannot be found
3
5
  * Add anti-dogpiling logic
4
- * Possibly memoize single-id finders, either per-action or globally with automatic invalidations
6
+
7
+ Someday:
8
+
9
+ * Add test coverage of STI child classes
5
10
  * Figure out how to cache Builder blocks
6
11
  * Figure out how to cache RJS
data/interlock.gemspec CHANGED
@@ -1,26 +1,26 @@
1
1
 
2
- # Gem::Specification for Interlock-1.2
2
+ # Gem::Specification for Interlock-1.3
3
3
  # Originally generated by Echoe
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = %q{interlock}
7
- s.version = "1.2"
7
+ s.version = "1.3"
8
8
 
9
9
  s.specification_version = 2 if s.respond_to? :specification_version=
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.authors = [""]
13
- s.date = %q{2008-02-04}
13
+ s.date = %q{2008-03-02}
14
14
  s.description = %q{A Rails plugin for maintainable and high-efficiency caching.}
15
15
  s.email = %q{}
16
- s.files = ["CHANGELOG", "examples/memcached.yml", "init.rb", "lib/interlock/action_controller.rb", "lib/interlock/action_view.rb", "lib/interlock/active_record.rb", "lib/interlock/config.rb", "lib/interlock/core_extensions.rb", "lib/interlock/interlock.rb", "lib/interlock/lock.rb", "lib/interlock.rb", "LICENSE", "Manifest", "README", "tasks/interlock.rake", "test/integration/app/app/controllers/application.rb", "test/integration/app/app/controllers/eval_controller.rb", "test/integration/app/app/controllers/items_controller.rb", "test/integration/app/app/helpers/application_helper.rb", "test/integration/app/app/helpers/eval_helper.rb", "test/integration/app/app/helpers/items_helper.rb", "test/integration/app/app/models/item.rb", "test/integration/app/app/views/items/detail.rhtml", "test/integration/app/app/views/items/list.rhtml", "test/integration/app/app/views/items/recent.rhtml", "test/integration/app/app/views/items/show.rhtml", "test/integration/app/app/views/layouts/application.html.erb", "test/integration/app/app/views/shared/_related.rhtml", "test/integration/app/config/boot.rb", "test/integration/app/config/database.yml", "test/integration/app/config/environment.rb", "test/integration/app/config/environments/development.rb", "test/integration/app/config/environments/production.rb", "test/integration/app/config/environments/test.rb", "test/integration/app/config/initializers/inflections.rb", "test/integration/app/config/initializers/mime_types.rb", "test/integration/app/config/memcached.yml", "test/integration/app/config/routes.rb", "test/integration/app/db/migrate/001_create_items.rb", "test/integration/app/doc/README_FOR_APP", "test/integration/app/public/404.html", "test/integration/app/public/422.html", "test/integration/app/public/500.html", "test/integration/app/public/dispatch.cgi", "test/integration/app/public/dispatch.fcgi", "test/integration/app/public/dispatch.rb", "test/integration/app/public/favicon.ico", "test/integration/app/public/images/rails.png", "test/integration/app/public/index.html", "test/integration/app/public/javascripts/application.js", "test/integration/app/public/javascripts/controls.js", "test/integration/app/public/javascripts/dragdrop.js", "test/integration/app/public/javascripts/effects.js", "test/integration/app/public/javascripts/prototype.js", "test/integration/app/public/robots.txt", "test/integration/app/Rakefile", "test/integration/app/README", "test/integration/app/script/about", "test/integration/app/script/console", "test/integration/app/script/destroy", "test/integration/app/script/generate", "test/integration/app/script/performance/benchmarker", "test/integration/app/script/performance/profiler", "test/integration/app/script/performance/request", "test/integration/app/script/plugin", "test/integration/app/script/process/inspector", "test/integration/app/script/process/reaper", "test/integration/app/script/process/spawner", "test/integration/app/script/runner", "test/integration/app/script/server", "test/integration/app/test/fixtures/items.yml", "test/integration/app/test/functional/eval_controller_test.rb", "test/integration/app/test/functional/items_controller_test.rb", "test/integration/app/test/test_helper.rb", "test/integration/app/test/unit/item_test.rb", "test/integration/server_test.rb", "test/setup.rb", "test/teardown.rb", "test/test_helper.rb", "test/unit/interlock_test.rb", "test/unit/lock_test.rb", "TODO", "interlock.gemspec"]
16
+ s.files = ["CHANGELOG", "examples/memcached.yml", "init.rb", "lib/interlock/action_controller.rb", "lib/interlock/action_view.rb", "lib/interlock/active_record.rb", "lib/interlock/config.rb", "lib/interlock/core_extensions.rb", "lib/interlock/finders.rb", "lib/interlock/interlock.rb", "lib/interlock/lock.rb", "lib/interlock/pass_through_store.rb", "lib/interlock.rb", "LICENSE", "Manifest", "README", "tasks/interlock.rake", "test/integration/app/app/controllers/application.rb", "test/integration/app/app/controllers/eval_controller.rb", "test/integration/app/app/controllers/items_controller.rb", "test/integration/app/app/helpers/application_helper.rb", "test/integration/app/app/helpers/eval_helper.rb", "test/integration/app/app/helpers/items_helper.rb", "test/integration/app/app/models/item.rb", "test/integration/app/app/views/items/detail.rhtml", "test/integration/app/app/views/items/list.rhtml", "test/integration/app/app/views/items/recent.rhtml", "test/integration/app/app/views/items/show.rhtml", "test/integration/app/app/views/layouts/application.html.erb", "test/integration/app/app/views/shared/_related.rhtml", "test/integration/app/config/boot.rb", "test/integration/app/config/database.yml", "test/integration/app/config/environment.rb", "test/integration/app/config/environments/development.rb", "test/integration/app/config/environments/production.rb", "test/integration/app/config/environments/test.rb", "test/integration/app/config/initializers/inflections.rb", "test/integration/app/config/initializers/mime_types.rb", "test/integration/app/config/memcached.yml", "test/integration/app/config/routes.rb", "test/integration/app/db/migrate/001_create_items.rb", "test/integration/app/doc/README_FOR_APP", "test/integration/app/public/404.html", "test/integration/app/public/422.html", "test/integration/app/public/500.html", "test/integration/app/public/dispatch.cgi", "test/integration/app/public/dispatch.fcgi", "test/integration/app/public/dispatch.rb", "test/integration/app/public/favicon.ico", "test/integration/app/public/images/rails.png", "test/integration/app/public/index.html", "test/integration/app/public/javascripts/application.js", "test/integration/app/public/javascripts/controls.js", "test/integration/app/public/javascripts/dragdrop.js", "test/integration/app/public/javascripts/effects.js", "test/integration/app/public/javascripts/prototype.js", "test/integration/app/public/robots.txt", "test/integration/app/Rakefile", "test/integration/app/README", "test/integration/app/script/about", "test/integration/app/script/console", "test/integration/app/script/destroy", "test/integration/app/script/generate", "test/integration/app/script/performance/benchmarker", "test/integration/app/script/performance/profiler", "test/integration/app/script/performance/request", "test/integration/app/script/plugin", "test/integration/app/script/process/inspector", "test/integration/app/script/process/reaper", "test/integration/app/script/process/spawner", "test/integration/app/script/runner", "test/integration/app/script/server", "test/integration/app/test/fixtures/items.yml", "test/integration/app/test/functional/eval_controller_test.rb", "test/integration/app/test/functional/items_controller_test.rb", "test/integration/app/test/test_helper.rb", "test/integration/app/test/unit/item_test.rb", "test/integration/server_test.rb", "test/setup.rb", "test/teardown.rb", "test/test_helper.rb", "test/unit/finder_test.rb", "test/unit/interlock_test.rb", "test/unit/lock_test.rb", "TODO", "interlock.gemspec"]
17
17
  s.has_rdoc = true
18
18
  s.homepage = %q{http://blog.evanweaver.com/files/doc/fauna/interlock/}
19
19
  s.require_paths = ["lib"]
20
20
  s.rubyforge_project = %q{fauna}
21
21
  s.rubygems_version = %q{1.0.1}
22
22
  s.summary = %q{A Rails plugin for maintainable and high-efficiency caching.}
23
- s.test_files = ["test/integration/server_test.rb", "test/unit/interlock_test.rb", "test/unit/lock_test.rb"]
23
+ s.test_files = ["test/integration/server_test.rb", "test/unit/finder_test.rb", "test/unit/interlock_test.rb", "test/unit/lock_test.rb"]
24
24
  end
25
25
 
26
26
 
@@ -35,7 +35,7 @@ end
35
35
  # p.url = "http://blog.evanweaver.com/files/doc/fauna/interlock/"
36
36
  # p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
37
37
  # p.test_pattern = ["test/integration/*.rb", "test/unit/*.rb"]
38
- # p.rdoc_pattern = ["README", "CHANGELOG", "TODO", "LICENSE", "lib/interlock/memcached.rb", "lib/interlock/interlock.rb", "lib/interlock/action_controller.rb", "lib/interlock/active_record.rb", "lib/interlock/action_view.rb", "lib/interlock/config.rb"]
38
+ # p.rdoc_pattern = ["README", "CHANGELOG", "TODO", "LICENSE", "lib/interlock/lock.rb", "lib/interlock/interlock.rb", "lib/interlock/action_controller.rb", "lib/interlock/active_record.rb", "lib/interlock/finders.rb", "lib/interlock/action_view.rb", "lib/interlock/config.rb"]
39
39
  # p.clean_pattern += ['test/integration/app/coverage', 'test/integration/app/db/schema.rb',
40
40
  # 'test/integration/app/vendor/plugins/interlock']
41
41
  # end
@@ -44,13 +44,15 @@ end
44
44
  # task "test_all" do
45
45
  # ['memcache-client', 'memcached'].each do |client|
46
46
  # ENV['CLIENT'] = client
47
- # ENV['PRODUCTION'] = 'false'
48
- # STDERR.puts "#{'='*80}\nDevelopment mode: #{client}\n#{'='*80}"
49
- # system("rake test:multi_rails:all")
50
- #
51
- # ENV['PRODUCTION'] = 'true'
52
- # STDERR.puts "#{'='*80}\nProduction mode: #{client}\n#{'='*80}"
53
- # system("rake test:multi_rails:all")
47
+ # ['false', 'true'].each do |finder|
48
+ # ENV['FINDERS'] = finder
49
+ # ['false', 'true'].each do |env|
50
+ # ENV['PRODUCTION'] = env
51
+ # mode = env == 'false' ? "Development" : "Production"
52
+ # STDERR.puts "#{'='*80}\n#{mode} mode, #{client}, finders #{finder}\n#{'='*80}"
53
+ # system("rake test:multi_rails:all")
54
+ # end
55
+ # end
54
56
  # end
55
57
  # end
56
58
  #
@@ -217,9 +217,8 @@ And in the <tt>show.html.erb</tt> view:
217
217
 
218
218
  content.first
219
219
  rescue Interlock::FragmentConsistencyError => e
220
- # XXX Needs test coverage
221
220
  # Delete the bogus key
222
- begin; CACHE.delete key; rescue; end
221
+ Interlock.invalidate(key)
223
222
  # Reraise the error
224
223
  raise e
225
224
  end
@@ -14,16 +14,23 @@ module ActiveRecord #:nodoc:
14
14
  #
15
15
 
16
16
  def expire_interlock_keys
17
+
18
+ # Fragments
17
19
  (CACHE.get(Interlock.dependency_key(self.class.base_class)) || {}).each do |key, scope|
18
20
  if scope == :all or (scope == :id and key.field(4) == self.to_param.to_s)
19
21
  Interlock.say key, "invalidated by rule #{self.class} -> #{scope.inspect}."
20
22
  Interlock.invalidate key
21
23
  end
22
24
  end
25
+
26
+ # Models
27
+ if Interlock.config[:with_finders]
28
+ Interlock.invalidate(self.class.caching_key(self.id))
29
+ end
23
30
  end
24
31
 
25
32
  before_save :expire_interlock_keys
26
33
  after_destroy :expire_interlock_keys
27
-
34
+
28
35
  end
29
36
  end
@@ -5,10 +5,11 @@ module Interlock
5
5
  :ttl => 1.day,
6
6
  :namespace => 'app',
7
7
  :servers => ['127.0.0.1:11211'],
8
- :client => 'memcache-client'
8
+ :client => 'memcache-client',
9
+ :with_finders => false
9
10
  }
10
11
 
11
- CLIENT_KEYS = [ #:nodoc:
12
+ CLIENT_KEYS = [
12
13
  :hash,
13
14
  :no_block,
14
15
  :buffer_requests,
@@ -16,7 +17,7 @@ module Interlock
16
17
  :tcp_nodelay,
17
18
  :distribution,
18
19
  :namespace
19
- ]
20
+ ]
20
21
 
21
22
  mattr_accessor :config
22
23
  @@config = DEFAULTS
@@ -42,14 +43,15 @@ module Interlock
42
43
  Interlock.config.merge!(config[RAILS_ENV.to_sym] || {})
43
44
  end
44
45
 
45
- memcached!
46
- rails!
46
+ install_memcached
47
+ install_fragments
48
+ install_finders if Interlock.config[:with_finders]
47
49
  end
48
50
 
49
51
  #
50
52
  # Configure memcached for this app.
51
53
  #
52
- def memcached!
54
+ def install_memcached
53
55
  Interlock.config[:namespace] << "-#{RAILS_ENV}"
54
56
 
55
57
  unless defined? Object::CACHE
@@ -95,11 +97,11 @@ module Interlock
95
97
  include Interlock::Lock
96
98
 
97
99
  def read(*args)
98
- get args.first
100
+ get args.first.to_s
99
101
  end
100
102
 
101
103
  def write(name, content, options = {})
102
- set(name,
104
+ set(name.to_s,
103
105
  content,
104
106
  options.is_a?(Hash) ? options[:ttl] : Interlock.config[:ttl] )
105
107
  end
@@ -110,7 +112,7 @@ module Interlock
110
112
  #
111
113
  # Configure Rails to use the memcached store for fragments, and optionally, sessions.
112
114
  #
113
- def rails!
115
+ def install_fragments
114
116
  # Memcached fragment caching is mandatory
115
117
  ActionView::Helpers::CacheHelper.class_eval do
116
118
  def cache(name, options = nil, &block)
@@ -127,6 +129,21 @@ module Interlock
127
129
  ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.update 'cache' => CACHE
128
130
  end
129
131
  end
132
+
133
+ #
134
+ # Configure ActiveRecord#find caching.
135
+ #
136
+ def install_finders
137
+ # RAILS_DEFAULT_LOGGER.warn "** using interlock finder caches"
138
+ class << ActiveRecord::Base
139
+ private
140
+ alias :find_via_db :find
141
+ remove_method :find
142
+
143
+ public
144
+ include Interlock::Finders
145
+ end
146
+ end
130
147
 
131
148
  end
132
149
  end
@@ -0,0 +1,131 @@
1
+
2
+ module Interlock
3
+ module Finders
4
+
5
+ #
6
+ # Cached find.
7
+ #
8
+ # Any other options besides ids short-circuit the cache, include an empty trailing Hash.
9
+ #
10
+ def find(*args)
11
+ return find_via_db(*args) if args.last.is_a? Hash or args.first.is_a? Symbol
12
+ records = find_via_cache(args.flatten, true)
13
+
14
+ if args.length > 1 or args.first.is_a? Array
15
+ records
16
+ else
17
+ records.first
18
+ end
19
+
20
+ end
21
+
22
+ #
23
+ # Cached find_by_id. Short-circuiting works the same as find.
24
+ #
25
+ def find_by_id(*args)
26
+ return method_missing(:find_by_id, *args) if args.last.is_a? Hash
27
+ find_via_cache(args, false).first
28
+ end
29
+
30
+ #
31
+ # Cached find_all_by_id. Ultrasphinx uses this. Short-circuiting works the same as find.
32
+ #
33
+ def find_all_by_id(*args)
34
+ return method_missing(:find_all_by_id, *args) if args.last.is_a? Hash
35
+ find_via_cache(args, false)
36
+ end
37
+
38
+ #
39
+ # Build the model cache key for a particular id.
40
+ #
41
+ def caching_key(id)
42
+ Interlock.caching_key(
43
+ self.name,
44
+ "find",
45
+ id,
46
+ "default"
47
+ )
48
+ end
49
+
50
+ private
51
+
52
+ def find_via_cache(ids, should_raise) #:doc:
53
+ results = []
54
+
55
+ ordered_keys_to_ids = ids.map { |id| [caching_key(id), id.to_i] }
56
+ keys_to_ids = Hash[*ordered_keys_to_ids.flatten]
57
+
58
+ records = {}
59
+
60
+ if ActionController::Base.perform_caching
61
+ load_from_local_cache(records, keys_to_ids)
62
+ load_from_memcached(records, keys_to_ids)
63
+ end
64
+
65
+ load_from_db(records, keys_to_ids)
66
+
67
+ # Put them in order
68
+
69
+ ordered_keys_to_ids.each do |key, |
70
+ record = records[key]
71
+ raise ActiveRecord::RecordNotFound, "Couldn't find #{self.name} with ID=#{keys_to_ids[key]}" if should_raise and !record
72
+ results << record
73
+ end
74
+
75
+ results
76
+ end
77
+
78
+ def load_from_local_cache(current, keys_to_ids) #:doc:
79
+ # Load from the local cache
80
+ records = {}
81
+ keys_to_ids.each do |key, |
82
+ record = Interlock.local_cache.read(key, nil)
83
+ records[key] = record if record
84
+ end
85
+ current.merge!(records)
86
+ end
87
+
88
+ def load_from_memcached(current, keys_to_ids) #:doc:
89
+ # Drop to memcached if necessary
90
+ if current.size < keys_to_ids.size
91
+ records = {}
92
+ missed = keys_to_ids.reject { |key, | current[key] }
93
+
94
+ records = CACHE.get_multi(*missed.keys)
95
+
96
+ # Set missed to the caches
97
+ records.each do |key, value|
98
+ Interlock.say key, "is loading from memcached", "model"
99
+ Interlock.local_cache.write(key, value, nil)
100
+ end
101
+
102
+ current.merge!(records)
103
+ end
104
+ end
105
+
106
+ def load_from_db(current, keys_to_ids) #:doc:
107
+ # Drop to db if necessary
108
+ if current.size < keys_to_ids.size
109
+ missed = keys_to_ids.reject { |key, | current[key] }
110
+ ids_to_keys = keys_to_ids.invert
111
+
112
+ # Load from the db
113
+ records = find_all_by_id(missed.values, {})
114
+ records = Hash[*(records.map do |record|
115
+ [ids_to_keys[record.id], record]
116
+ end.flatten)]
117
+
118
+ # Set missed to the caches
119
+ records.each do |key, value|
120
+ Interlock.say key, "is loading from the db", "model"
121
+ Interlock.local_cache.write(key, value, nil)
122
+ CACHE.set key, value
123
+ end
124
+
125
+ current.merge!(records)
126
+ end
127
+ end
128
+
129
+ end
130
+ end
131
+
@@ -26,6 +26,11 @@ module Interlock
26
26
  ILLEGAL_KEY_CHARACTERS_PATTERN = /\s/
27
27
 
28
28
  mattr_accessor :local_cache
29
+
30
+ # Install the pass-through store. This is used in the console and test
31
+ # environment. In a server environment, the controller callbacks install
32
+ # the memory store before each request.
33
+ @@local_cache = Interlock::PassThroughStore.new
29
34
 
30
35
  class << self
31
36
  #
@@ -89,8 +94,8 @@ module Interlock
89
94
  end
90
95
  end
91
96
 
92
- def say(key, msg) #:nodoc:
93
- RAILS_DEFAULT_LOGGER.warn "** fragment #{key.inspect[1..-2]} #{msg}"
97
+ def say(key, msg, type = "fragment") #:nodoc:
98
+ RAILS_DEFAULT_LOGGER.warn "** #{type} #{key.inspect[1..-2]} #{msg}"
94
99
  end
95
100
 
96
101
  #
@@ -128,6 +133,9 @@ module Interlock
128
133
  # Invalidate a particular key.
129
134
  #
130
135
  def invalidate(key)
136
+ # Console and tests do not install the local cache
137
+ Interlock.local_cache.delete(key) if Interlock.local_cache
138
+
131
139
  ActionController::Base.fragment_cache_store.delete key
132
140
  end
133
141
 
@@ -0,0 +1,19 @@
1
+
2
+ module Interlock
3
+ #
4
+ # A stub class so that does not cache, for use in the test environment and the console
5
+ # when the MemoryStore is not available.
6
+ #
7
+ class PassThroughStore
8
+
9
+ # Do nothing.
10
+ def nothing(*args)
11
+ nil
12
+ end
13
+
14
+ alias :read :nothing
15
+ alias :write :nothing
16
+ alias :delete :nothing
17
+
18
+ end
19
+ end
data/lib/interlock.rb CHANGED
@@ -6,8 +6,10 @@ require 'interlock/core_extensions'
6
6
  require 'interlock/config'
7
7
  require 'interlock/interlock'
8
8
  require 'interlock/lock'
9
+ require 'interlock/pass_through_store'
9
10
  require 'interlock/action_controller'
10
11
  require 'interlock/action_view'
12
+ require 'interlock/finders'
11
13
  require 'interlock/active_record'
12
14
 
13
15
  begin
@@ -3,6 +3,7 @@ defaults:
3
3
  namespace: interlock
4
4
  sessions: true
5
5
  client: <%= ENV['CLIENT'] || 'memcached' %>
6
+ with_finders: <%= ENV['FINDERS'] || true %>
6
7
  development:
7
8
  servers:
8
9
  - 127.0.0.1:43042
@@ -18,7 +18,7 @@ class ServerTest < Test::Unit::TestCase
18
18
  end
19
19
 
20
20
 
21
- ### Tests
21
+ ### Fragment caching tests
22
22
 
23
23
  def test_render
24
24
  assert_match(/Welcome/, browse)
@@ -115,41 +115,45 @@ class ServerTest < Test::Unit::TestCase
115
115
  assert_match(/any:any:all:related invalidated/, log)
116
116
  assert_match(/any:any:all:related is running the controller block/, log)
117
117
  end
118
-
119
- def test_caching_of_content_for
120
- assert_match(/Interlock Test:\s*\d\s*Items/m, browse("items"))
121
- assert_match(/all:untagged is running the controller block/, log)
122
- assert_match(/all:untagged wrote/, log)
123
118
 
124
- truncate
125
- assert_match(/Interlock Test:\s*\d\s*Items/m, browse("items"))
126
- # Make sure we didn't copy the content_for too many times
127
- assert_no_match(/Interlock Test:\s*\d\s*Items\s*\d\s*Items/m, browse("items"))
128
- assert_no_match(/all:untagged is running the controller block/, log)
129
- assert_match(/all:untagged read from memcached/, log)
130
- end
131
-
132
- def test_nested_view_caches
133
- assert_match(/Outer: Inner<.*2 total items.*Artichoke/m, browse("items/detail/1"))
134
- assert_match(/detail:1:outer is running the controller block/, log)
135
- assert_match(/detail:1:inner is running the controller block/, log)
119
+ unless ENV['RAILS_GEM_VERSION'] == "1.2.6"
120
+ # This functionality not supported on 1.2.6
121
+
122
+ def test_caching_of_content_for
123
+ assert_match(/Interlock Test:\s*\d\s*Items/m, browse("items"))
124
+ assert_match(/all:untagged is running the controller block/, log)
125
+ assert_match(/all:untagged wrote/, log)
126
+
127
+ truncate
128
+ assert_match(/Interlock Test:\s*\d\s*Items/m, browse("items"))
129
+ # Make sure we didn't copy the content_for too many times
130
+ assert_no_match(/Interlock Test:\s*\d\s*Items\s*\d\s*Items/m, browse("items"))
131
+ assert_no_match(/all:untagged is running the controller block/, log)
132
+ assert_match(/all:untagged read from memcached/, log)
133
+ end
136
134
 
137
- truncate
138
- assert_match(/Outer: Inner<.*2 total items.*Artichoke/m, browse("items/detail/1"))
139
- assert_no_match(/detail:1:outer is running the controller block/, log)
140
- assert_no_match(/detail:1:inner is running the controller block/, log)
141
-
142
- truncate
143
- remote_eval("Item.find(2).save!")
144
- assert_match(/Outer: Inner<.*2 total items.*Artichoke/m, browse("items/detail/1"))
145
- assert_match(/detail:1:outer is running the controller block/, log)
146
- assert_no_match(/detail:1:inner is running the controller block/, log)
147
-
148
- truncate
149
- remote_eval("Item.find(1).save!")
150
- assert_match(/Outer: Inner<.*2 total items.*Artichoke/m, browse("items/detail/1"))
151
- assert_match(/detail:1:outer is running the controller block/, log)
152
- assert_match(/detail:1:inner is running the controller block/, log)
135
+ def test_nested_view_caches
136
+ assert_match(/Outer: Inner<.*2 total items.*Artichoke/m, browse("items/detail/1"))
137
+ assert_match(/detail:1:outer is running the controller block/, log)
138
+ assert_match(/detail:1:inner is running the controller block/, log)
139
+
140
+ truncate
141
+ assert_match(/Outer: Inner<.*2 total items.*Artichoke/m, browse("items/detail/1"))
142
+ assert_no_match(/detail:1:outer is running the controller block/, log)
143
+ assert_no_match(/detail:1:inner is running the controller block/, log)
144
+
145
+ truncate
146
+ remote_eval("Item.find(2).save!")
147
+ assert_match(/Outer: Inner<.*2 total items.*Artichoke/m, browse("items/detail/1"))
148
+ assert_match(/detail:1:outer is running the controller block/, log)
149
+ assert_no_match(/detail:1:inner is running the controller block/, log)
150
+
151
+ truncate
152
+ remote_eval("Item.find(1).save!")
153
+ assert_match(/Outer: Inner<.*2 total items.*Artichoke/m, browse("items/detail/1"))
154
+ assert_match(/detail:1:outer is running the controller block/, log)
155
+ assert_match(/detail:1:inner is running the controller block/, log)
156
+ end
153
157
  end
154
158
 
155
159
  ### Support methods
data/test/setup.rb CHANGED
@@ -6,9 +6,11 @@ Dir.chdir "#{File.dirname(__FILE__)}/integration/app/" do
6
6
  `ps awx`.split("\n").grep(/4304[1-3]/).map do |process|
7
7
  system("kill -9 #{process.to_i}")
8
8
  end
9
+
10
+ LOG = "/tmp/memcached.log"
9
11
 
10
- system "memcached -p 43042 &"
11
- system "memcached -p 43043 &"
12
+ system "memcached -vv -p 43042 >> #{LOG} 2>&1 &"
13
+ system "memcached -vv -p 43043 >> #{LOG} 2>&1 &"
12
14
 
13
15
  Dir.chdir "vendor/plugins" do
14
16
  system "rm interlock; ln -s ../../../../../ interlock"
@@ -0,0 +1,105 @@
1
+
2
+ require "#{File.dirname(__FILE__)}/../test_helper"
3
+ require 'fileutils'
4
+
5
+ class FinderTest < Test::Unit::TestCase
6
+
7
+ LOG = "#{HERE}/integration/app/log/development.log"
8
+
9
+ ### Finder caching tests
10
+
11
+ def test_find_without_cache
12
+ Item.find(1, {})
13
+ assert_no_match(/model.*Item:find:1:default is loading from the db/, log)
14
+ end
15
+
16
+ def test_find
17
+ assert_equal Item.find(1, {}),
18
+ Item.find(1)
19
+ assert_match(/model.*Item:find:1:default is loading from the db/, log)
20
+
21
+ assert_equal Item.find(1, {}),
22
+ Item.find(1)
23
+ assert_match(/model.*Item:find:1:default is loading from memcached/, log)
24
+ end
25
+
26
+ def test_find_with_array
27
+ assert_equal Item.find([1, 2], {}),
28
+ Item.find([1, 2])
29
+ assert_match(/model.*Item:find:1:default is loading from the db/, log)
30
+ assert_match(/model.*Item:find:2:default is loading from the db/, log)
31
+
32
+ assert_equal Item.find([1, 2], {}),
33
+ Item.find([1, 2])
34
+ assert_match(/model.*Item:find:1:default is loading from memcached/, log)
35
+ assert_match(/model.*Item:find:2:default is loading from memcached/, log)
36
+ end
37
+
38
+ def test_find_raise
39
+ assert_raises(ActiveRecord::RecordNotFound) do
40
+ Item.find(44)
41
+ end
42
+ end
43
+
44
+ def test_find_with_array_raise
45
+ assert_raises(ActiveRecord::RecordNotFound) do
46
+ # Once from the DB
47
+ Item.find([1, 2, 44])
48
+ end
49
+ assert_raises(ActiveRecord::RecordNotFound) do
50
+ # Once from Memcached
51
+ Item.find([1, 2, 44])
52
+ end
53
+ end
54
+
55
+ def test_invalidate
56
+ Item.find(1).save!
57
+ truncate
58
+ Item.find(1)
59
+ assert_match(/model.*Item:find:1:default is loading from the db/, log)
60
+ Item.find(1)
61
+ assert_match(/model.*Item:find:1:default is loading from memcached/, log)
62
+ end
63
+
64
+ def test_find_all_by_id
65
+ assert_equal Item.find_all_by_id(44, {}),
66
+ Item.find_all_by_id(44)
67
+ assert_equal Item.find_all_by_id([1,2], {}),
68
+ Item.find_all_by_id([1,2])
69
+ assert_equal Item.find_all_by_id(1, 2, {}),
70
+ Item.find_all_by_id(1, 2)
71
+ end
72
+
73
+ def test_find_by_id
74
+ assert_equal Item.find_by_id(44, {}),
75
+ Item.find_by_id(44)
76
+ assert_equal Item.find_by_id([1,2], {}),
77
+ Item.find_by_id([1,2])
78
+ assert_equal Item.find_by_id(1, 2, {}),
79
+ Item.find_by_id(1, 2)
80
+ end
81
+
82
+ ### Support methods
83
+
84
+ def setup
85
+ # Change the asset ID; has a similar effect to flushing memcached
86
+ @old_asset_id = ENV['RAILS_ASSET_ID']
87
+ ENV['RAILS_ASSET_ID'] = rand.to_s
88
+ truncate
89
+ end
90
+
91
+ def teardown
92
+ # Restore the asset id
93
+ ENV['RAILS_ASSET_ID'] = @old_asset_id
94
+ end
95
+
96
+ def truncate
97
+ system("> #{LOG}")
98
+ end
99
+
100
+ def log
101
+ File.open(LOG, 'r') do |f|
102
+ f.read
103
+ end
104
+ end
105
+ end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: interlock
3
3
  version: !ruby/object:Gem::Version
4
- version: "1.2"
4
+ version: "1.3"
5
5
  platform: ruby
6
6
  authors:
7
7
  - ""
@@ -30,7 +30,7 @@ cert_chain:
30
30
  yZ0=
31
31
  -----END CERTIFICATE-----
32
32
 
33
- date: 2008-02-04 00:00:00 -05:00
33
+ date: 2008-03-02 00:00:00 -05:00
34
34
  default_executable:
35
35
  dependencies: []
36
36
 
@@ -51,8 +51,10 @@ files:
51
51
  - lib/interlock/active_record.rb
52
52
  - lib/interlock/config.rb
53
53
  - lib/interlock/core_extensions.rb
54
+ - lib/interlock/finders.rb
54
55
  - lib/interlock/interlock.rb
55
56
  - lib/interlock/lock.rb
57
+ - lib/interlock/pass_through_store.rb
56
58
  - lib/interlock.rb
57
59
  - LICENSE
58
60
  - Manifest
@@ -122,6 +124,7 @@ files:
122
124
  - test/setup.rb
123
125
  - test/teardown.rb
124
126
  - test/test_helper.rb
127
+ - test/unit/finder_test.rb
125
128
  - test/unit/interlock_test.rb
126
129
  - test/unit/lock_test.rb
127
130
  - TODO
@@ -154,5 +157,6 @@ specification_version: 2
154
157
  summary: A Rails plugin for maintainable and high-efficiency caching.
155
158
  test_files:
156
159
  - test/integration/server_test.rb
160
+ - test/unit/finder_test.rb
157
161
  - test/unit/interlock_test.rb
158
162
  - test/unit/lock_test.rb
metadata.gz.sig CHANGED
Binary file