interlock 1.3 → 1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +2 -0
  3. data/README +6 -8
  4. data/Rakefile +34 -0
  5. data/interlock.gemspec +20 -49
  6. data/lib/interlock.rb +5 -1
  7. data/lib/interlock/action_controller.rb +32 -8
  8. data/lib/interlock/action_view.rb +4 -7
  9. data/lib/interlock/active_record.rb +35 -10
  10. data/lib/interlock/config.rb +48 -30
  11. data/lib/interlock/finders.rb +129 -105
  12. data/lib/interlock/interlock.rb +23 -11
  13. data/lib/interlock/lock.rb +7 -6
  14. data/test/integration/app/config/environment.rb +1 -1
  15. data/test/integration/app/config/environments/development.rb +0 -1
  16. data/test/integration/app/public/dispatch.cgi +0 -0
  17. data/test/integration/app/public/dispatch.fcgi +0 -0
  18. data/test/integration/app/public/dispatch.rb +0 -0
  19. data/test/integration/app/script/about +0 -0
  20. data/test/integration/app/script/console +0 -0
  21. data/test/integration/app/script/destroy +0 -0
  22. data/test/integration/app/script/generate +0 -0
  23. data/test/integration/app/script/performance/benchmarker +0 -0
  24. data/test/integration/app/script/performance/profiler +0 -0
  25. data/test/integration/app/script/performance/request +0 -0
  26. data/test/integration/app/script/plugin +0 -0
  27. data/test/integration/app/script/process/inspector +0 -0
  28. data/test/integration/app/script/process/reaper +0 -0
  29. data/test/integration/app/script/process/spawner +0 -0
  30. data/test/integration/app/script/runner +0 -0
  31. data/test/integration/app/script/server +0 -0
  32. data/test/integration/server_test.rb +21 -3
  33. data/test/unit/active_record_test.rb +8 -0
  34. data/test/unit/finder_test.rb +78 -7
  35. data/test/unit/interlock_test.rb +9 -0
  36. metadata +28 -9
  37. metadata.gz.sig +0 -0
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,4 +1,6 @@
1
1
 
2
+ v1.4. Rails 2.1 compatibility. Store dependency trees per object, when possible. Use shorter namespaces for Memcached.
3
+
2
4
  v1.3. Add basic finder memoization.
3
5
 
4
6
  v1.2. Add compatibility with Memcached client.
data/README CHANGED
@@ -15,9 +15,7 @@ If you use this software, please {make a donation}[http://blog.evanweaver.com/do
15
15
 
16
16
  * memcached (http://www.danga.com/memcached)
17
17
  * memcache-client gem
18
- * Rails 1.2.6
19
-
20
- Note that Rails 2.0.2 is required for caching <tt>content_for</tt> or nesting <tt>view_cache</tt> blocks.
18
+ * Rails 2.1
21
19
 
22
20
  == Features
23
21
 
@@ -37,7 +35,7 @@ You also need either <tt>memcache-client</tt> or {memcached}[http://blog.evanwea
37
35
  sudo gem install memcache-client
38
36
 
39
37
  Then, install the plugin:
40
- script/plugin install -x svn://rubyforge.org/var/svn/fauna/interlock/trunk
38
+ script/plugin install git://github.com/fauna/interlock.git
41
39
 
42
40
  Lastly, configure your Rails app for memcached by creating a <tt>config/memcached.yml</tt> file. The format is compatible with Cache_fu:
43
41
 
@@ -49,13 +47,13 @@ Lastly, configure your Rails app for memcached by creating a <tt>config/memcache
49
47
  servers:
50
48
  - 127.0.0.1:11211 # Default host and port
51
49
  production:
52
- servers
53
- - 10.12.128.1
54
- - 10.12.128.2
50
+ servers:
51
+ - 10.12.128.1:11211
52
+ - 10.12.128.2:11211
55
53
 
56
54
  Now you're ready to go.
57
55
 
58
- Note that if you have {memcached 0.7}[http://blog.evanweaver.com/files/doc/fauna/memcached], you can use <tt>client: memcached</tt> for better performance.
56
+ Note that if you have the {memcached}[http://blog.evanweaver.com/files/doc/fauna/memcached] client, you can use <tt>client: memcached</tt> for better performance.
59
57
 
60
58
  == Usage
61
59
 
@@ -0,0 +1,34 @@
1
+
2
+ require 'echoe'
3
+
4
+ Echoe.new("interlock") do |p|
5
+ p.project = "fauna"
6
+ p.summary = "A Rails plugin for maintainable and high-efficiency caching."
7
+ p.url = "http://blog.evanweaver.com/files/doc/fauna/interlock/"
8
+ p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
9
+ p.test_pattern = ["test/integration/*.rb", "test/unit/*.rb"]
10
+ 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"]
11
+ p.clean_pattern += ['test/integration/app/coverage', 'test/integration/app/db/schema.rb',
12
+ 'test/integration/app/vendor/plugins/interlock']
13
+ end
14
+
15
+ desc "Run all the tests in production and development mode both"
16
+ task "test_all" do
17
+ ['memcache-client', 'memcached'].each do |client|
18
+ ENV['CLIENT'] = client
19
+ ['false', 'true'].each do |finder|
20
+ ENV['FINDERS'] = finder
21
+ ['false', 'true'].each do |env|
22
+ ENV['PRODUCTION'] = env
23
+ mode = env == 'false' ? "Development" : "Production"
24
+ STDERR.puts "#{'='*80}\n#{mode} mode, #{client}, finders #{finder}\n#{'='*80}"
25
+ system("rake test:multi_rails:all")
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ task "tail" do
32
+ log = "test/integration/app/log/development.log"
33
+ system("touch #{log} && tail -f #{log} | grep interlock")
34
+ end
@@ -1,62 +1,33 @@
1
-
2
- # Gem::Specification for Interlock-1.3
3
- # Originally generated by Echoe
1
+ # -*- encoding: utf-8 -*-
4
2
 
5
3
  Gem::Specification.new do |s|
6
4
  s.name = %q{interlock}
7
- s.version = "1.3"
8
-
9
- s.specification_version = 2 if s.respond_to? :specification_version=
5
+ s.version = "1.4"
10
6
 
11
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
12
8
  s.authors = [""]
13
- s.date = %q{2008-03-02}
9
+ s.cert_chain = ["/Users/eweaver/p/configuration/gem_certificates/evan_weaver-original-public_cert.pem"]
10
+ s.date = %q{2009-11-18}
14
11
  s.description = %q{A Rails plugin for maintainable and high-efficiency caching.}
15
12
  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/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
- s.has_rdoc = true
13
+ s.extra_rdoc_files = ["CHANGELOG", "lib/interlock/action_controller.rb", "lib/interlock/action_view.rb", "lib/interlock/active_record.rb", "lib/interlock/config.rb", "lib/interlock/finders.rb", "lib/interlock/interlock.rb", "lib/interlock/lock.rb", "LICENSE", "README", "TODO"]
14
+ 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", "Rakefile", "test/unit/active_record_test.rb"]
18
15
  s.homepage = %q{http://blog.evanweaver.com/files/doc/fauna/interlock/}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Interlock", "--main", "README"]
19
17
  s.require_paths = ["lib"]
20
18
  s.rubyforge_project = %q{fauna}
21
- s.rubygems_version = %q{1.0.1}
19
+ s.rubygems_version = %q{1.3.5}
20
+ s.signing_key = %q{/Users/eweaver/p/configuration/gem_certificates/evan_weaver-original-private_key.pem}
22
21
  s.summary = %q{A Rails plugin for maintainable and high-efficiency caching.}
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
- end
22
+ s.test_files = ["test/integration/server_test.rb", "test/unit/active_record_test.rb", "test/unit/finder_test.rb", "test/unit/interlock_test.rb", "test/unit/lock_test.rb"]
25
23
 
24
+ if s.respond_to? :specification_version then
25
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
26
+ s.specification_version = 3
26
27
 
27
- # # Original Rakefile source (requires the Echoe gem):
28
- #
29
- #
30
- # require 'echoe'
31
- #
32
- # Echoe.new("interlock") do |p|
33
- # p.project = "fauna"
34
- # p.summary = "A Rails plugin for maintainable and high-efficiency caching."
35
- # p.url = "http://blog.evanweaver.com/files/doc/fauna/interlock/"
36
- # p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
37
- # p.test_pattern = ["test/integration/*.rb", "test/unit/*.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
- # p.clean_pattern += ['test/integration/app/coverage', 'test/integration/app/db/schema.rb',
40
- # 'test/integration/app/vendor/plugins/interlock']
41
- # end
42
- #
43
- # desc "Run all the tests in production and development mode both"
44
- # task "test_all" do
45
- # ['memcache-client', 'memcached'].each do |client|
46
- # ENV['CLIENT'] = client
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
56
- # end
57
- # end
58
- #
59
- # task "tail" do
60
- # log = "test/integration/app/log/development.log"
61
- # system("touch #{log} && tail -f #{log} | grep interlock")
62
- # end
28
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
29
+ else
30
+ end
31
+ else
32
+ end
33
+ end
@@ -13,7 +13,11 @@ require 'interlock/finders'
13
13
  require 'interlock/active_record'
14
14
 
15
15
  begin
16
- require 'memcached'
16
+ if defined?(JRUBY_VERSION)
17
+ require 'memcache-client'
18
+ else
19
+ require 'memcached'
20
+ end
17
21
  rescue LoadError
18
22
  end
19
23
 
@@ -16,7 +16,7 @@ If you pass an Array of symbols as the tag, it will get value-mapped onto params
16
16
  ignore = Interlock::SCOPE_KEYS if ignore.include? :all
17
17
 
18
18
  if (Interlock::SCOPE_KEYS - ignore).empty? and !tag
19
- raise UsageError, "You must specify a :tag if you are ignoring the entire default scope."
19
+ raise Interlock::UsageError, "You must specify a :tag if you are ignoring the entire default scope."
20
20
  end
21
21
 
22
22
  if tag.is_a? Array and tag.all? {|x| x.is_a? Symbol}
@@ -121,11 +121,13 @@ And in the <tt>show.html.erb</tt> view:
121
121
  conventional_class = begin; controller_name.classify.constantize; rescue NameError; end
122
122
  options, dependencies = Interlock.extract_options_and_dependencies(args, conventional_class)
123
123
 
124
- raise UsageError, ":ttl has no effect in a behavior_cache block" if options[:ttl]
124
+ raise Interlock::UsageError, ":ttl has no effect in a behavior_cache block" if options[:ttl]
125
+
126
+ Interlock.say "key", "yo: #{options.inspect} -- #{dependencies.inspect}"
125
127
 
126
128
  key = caching_key(options.value_for_indifferent_key(:ignore), options.value_for_indifferent_key(:tag))
127
129
 
128
- if options[:perform] == false
130
+ if options[:perform] == false || Interlock.config[:disabled]
129
131
  Interlock.say key, "is not cached"
130
132
  yield
131
133
  else
@@ -149,8 +151,8 @@ And in the <tt>show.html.erb</tt> view:
149
151
  # Callback to reset the local cache.
150
152
  #
151
153
  def clear_interlock_local_cache
152
- Interlock.local_cache = ::ActionController::Base::MemoryStore.new
153
- RAILS_DEFAULT_LOGGER.warn "** cleared interlock local cache"
154
+ Interlock.local_cache = ::ActiveSupport::Cache::MemoryStore.new
155
+ Interlock.log "** cleared interlock local cache"
154
156
  end
155
157
 
156
158
  # Should be registered first in the chain
@@ -172,7 +174,7 @@ And in the <tt>show.html.erb</tt> view:
172
174
 
173
175
  content = [block_content, @template.cached_content_for]
174
176
 
175
- fragment_cache_store.write(key, content, options)
177
+ cache_store.write(key, content, options)
176
178
  Interlock.local_cache.write(key, content, options)
177
179
 
178
180
  Interlock.say key, "wrote"
@@ -193,7 +195,7 @@ And in the <tt>show.html.erb</tt> view:
193
195
  begin
194
196
  if content = Interlock.local_cache.read(key, options)
195
197
  # Interlock.say key, "read from local cache"
196
- elsif content = fragment_cache_store.read(key, options)
198
+ elsif content = cache_store.read(key, options)
197
199
  raise Interlock::FragmentConsistencyError, "#{key} expected Array but got #{content.class}" unless content.is_a? Array
198
200
  Interlock.say key, "read from memcached"
199
201
  Interlock.local_cache.write(key, content, options)
@@ -225,5 +227,27 @@ And in the <tt>show.html.erb</tt> view:
225
227
  end
226
228
 
227
229
  end
228
- end
230
+
231
+ # With Rails 2.1 action caching, we need to slip in our :expire param into the ActionCacheFilter options, so that when we
232
+ # write_fragment we can pass that in and allow shane's #{key}_expiry to take effect
233
+ # (see def write in interlock/config.rb)
234
+ module Actions
235
+
236
+ module ClassMethods
237
+ def caches_action(*actions)
238
+ return unless cache_configured?
239
+ options = actions.extract_options!
240
+ around_filter(ActionCacheFilter.new(:cache_path => options.delete(:cache_path), :expire => options.delete(:expire)), {:only => actions}.merge(options))
241
+ end
242
+ end
243
+
244
+ class ActionCacheFilter #:nodoc:
245
+ def after(controller)
246
+ return if controller.rendered_action_cache || !caching_allowed(controller)
247
+ controller.write_fragment(controller.action_cache_path.path, controller.response.body, :expire => @options[:expire]) # pass in our :expire
248
+ end
249
+ end
250
+ end
251
+
252
+ end
229
253
  end
@@ -30,7 +30,7 @@ Note that the cached item is not guaranteed to live this long. An invalidation r
30
30
 
31
31
  It's fine to use a <tt>view_cache</tt> block without a <tt>behavior_cache</tt> block. For example, to mimic regular fragment cache behavior, but take advantage of memcached's <tt>:ttl</tt> support, call:
32
32
 
33
- <% view_cache :ignore => :all, :tag => 'sidebar', :ttl => 5.minutes %>
33
+ <% view_cache :ignore => :all, :tag => 'sidebar', :ttl => 5.minutes do %>
34
34
  <% end %>
35
35
 
36
36
  == Dependencies, scoping, and other options
@@ -44,7 +44,7 @@ See ActionController::Base for explanations of the rest of the options. The <tt>
44
44
 
45
45
  key = controller.caching_key(options.value_for_indifferent_key(:ignore), options.value_for_indifferent_key(:tag))
46
46
 
47
- if options[:perform] == false
47
+ if options[:perform] == false || Interlock.config[:disabled]
48
48
  # Interlock.say key, "is not cached"
49
49
  block.call
50
50
  else
@@ -53,11 +53,8 @@ See ActionController::Base for explanations of the rest of the options. The <tt>
53
53
  # Interlock.say key, "is rendering"
54
54
 
55
55
  @cached_content_for, previous_cached_content_for = {}, @cached_content_for
56
- @controller.cache_erb_fragment(
57
- block,
58
- key,
59
- :ttl => (options.value_for_indifferent_key(:ttl) or Interlock.config[:ttl])
60
- )
56
+
57
+ cache key, :ttl => (options.value_for_indifferent_key(:ttl) or Interlock.config[:ttl]), &block
61
58
 
62
59
  # This is tricky. If we were already caching content_fors in a parent block, we need to
63
60
  # append the content_fors set in the inner block to those already set in the outer block.
@@ -1,6 +1,16 @@
1
1
 
2
2
  module ActiveRecord #:nodoc:
3
3
  class Base
4
+
5
+ @@nil_sentinel = :_nil
6
+
7
+ class << self # Class methods
8
+ def update_counters_with_expiry(id, counters)
9
+ update_counters_without_expiry(id, counters)
10
+ find(id).expire_interlock_keys
11
+ end
12
+ alias_method_chain :update_counters, :expiry
13
+ end
4
14
 
5
15
  #
6
16
  # Convert this record to a tag string.
@@ -12,25 +22,40 @@ module ActiveRecord #:nodoc:
12
22
  #
13
23
  # The expiry callback.
14
24
  #
15
-
16
25
  def expire_interlock_keys
26
+ return if Interlock.config[:disabled] or (defined? CGI::Session::ActiveRecordStore and is_a? CGI::Session::ActiveRecordStore::Session)
17
27
 
18
28
  # Fragments
19
- (CACHE.get(Interlock.dependency_key(self.class.base_class)) || {}).each do |key, scope|
20
- if scope == :all or (scope == :id and key.field(4) == self.to_param.to_s)
21
- Interlock.say key, "invalidated by rule #{self.class} -> #{scope.inspect}."
22
- Interlock.invalidate key
23
- end
24
- end
29
+ self.expire_interlock_keys_for_dependency(Interlock.dependency_key(self.class.base_class, :all, nil))
30
+ self.expire_interlock_keys_for_dependency(Interlock.dependency_key(self.class.base_class, :id, "::::#{to_param}:"))
25
31
 
26
32
  # Models
27
33
  if Interlock.config[:with_finders]
28
- Interlock.invalidate(self.class.caching_key(self.id))
34
+ key = self.class.base_class.caching_key(self.id)
35
+ Interlock.say key, 'invalidated with finders', 'model'
36
+ Interlock.invalidate key
29
37
  end
30
38
  end
31
39
 
32
40
  before_save :expire_interlock_keys
41
+ after_save :expire_interlock_keys
33
42
  after_destroy :expire_interlock_keys
34
-
43
+
44
+ #
45
+ # Reload. Expires the cache and force reload from db.
46
+ #
47
+ def reload_with_expiry(*args)
48
+ expire_interlock_keys
49
+ reload_without_expiry(*args)
50
+ end
51
+ alias_method_chain :reload, :expiry
52
+
53
+ def expire_interlock_keys_for_dependency(dependency_key)
54
+ (CACHE.get(dependency_key) || {}).each do |key, scope|
55
+ Interlock.say key, "invalidated by rule #{self.class} -> #{scope.inspect}."
56
+ Interlock.invalidate key
57
+ end
58
+ end
59
+
35
60
  end
36
- end
61
+ end
@@ -1,4 +1,4 @@
1
-
1
+ #require 'ehcache'
2
2
  module Interlock
3
3
 
4
4
  DEFAULTS = {
@@ -9,13 +9,26 @@ module Interlock
9
9
  :with_finders => false
10
10
  }
11
11
 
12
- CLIENT_KEYS = [
12
+ CLIENT_KEYS =[
13
+ :prefix_key,
14
+ :distribution,
15
+ :verify_key,
16
+ :tcp_nodelay,
13
17
  :hash,
14
- :no_block,
18
+ :hash_with_prefix_key,
19
+ :show_backtraces,
20
+ :default_ttl,
21
+ :ketama_weighted,
22
+ :retry_timeout,
23
+ :default_weight,
15
24
  :buffer_requests,
25
+ :timeout,
26
+ :sort_hosts,
27
+ :cache_lookups,
28
+ :connect_timeout,
29
+ :no_block,
30
+ :failover,
16
31
  :support_cas,
17
- :tcp_nodelay,
18
- :distribution,
19
32
  :namespace
20
33
  ]
21
34
 
@@ -42,9 +55,11 @@ module Interlock
42
55
  Interlock.config.merge!(config[:defaults] || {})
43
56
  Interlock.config.merge!(config[RAILS_ENV.to_sym] || {})
44
57
  end
45
-
58
+
46
59
  install_memcached
47
60
  install_fragments
61
+
62
+ # Always install the finders.
48
63
  install_finders if Interlock.config[:with_finders]
49
64
  end
50
65
 
@@ -55,16 +70,25 @@ module Interlock
55
70
  Interlock.config[:namespace] << "-#{RAILS_ENV}"
56
71
 
57
72
  unless defined? Object::CACHE
58
-
73
+
59
74
  # Give people a choice of client, even though I don't like conditional dependencies.
60
75
  klass = case Interlock.config[:client]
61
76
  when 'memcached'
62
- Memcached::Rails
77
+ begin
78
+ Memcached::Rails
79
+ rescue ArgumentError
80
+ raise ConfigurationError, "'memcached' client requested but not installed. Try 'sudo gem install memcached'."
81
+ end
82
+
63
83
  when 'memcache-client'
64
- raise ConfigurationError, "You have the Ruby-MemCache gem installed. Please uninstall Ruby-MemCache, or otherwise guarantee that memcache-client will load instead." if MemCache.constants.include?('SVNURL')
65
- MemCache
66
- else
67
- raise ConfigurationError, "Invalid client name '#{Interlock.config[:client]}'"
84
+ begin
85
+ if MemCache.constants.include?('SVNURL')
86
+ raise ConfigurationError, "You have the Ruby-MemCache gem installed. Please uninstall Ruby-MemCache, or otherwise guarantee that memcache-client will load instead."
87
+ end
88
+ MemCache
89
+ rescue ArgumentError
90
+ raise ConfigurationError, "'memcache-client' client requested but not installed. Try 'sudo gem install memcache-client'."
91
+ end
68
92
  end
69
93
 
70
94
  Object.const_set('CACHE',
@@ -73,7 +97,7 @@ module Interlock
73
97
  Interlock.config.slice(*CLIENT_KEYS)
74
98
  )
75
99
  )
76
-
100
+
77
101
  # Mark that we're the ones who did it.
78
102
  class << CACHE
79
103
  def installed_by_interlock; true; end
@@ -104,9 +128,8 @@ module Interlock
104
128
  set(name.to_s,
105
129
  content,
106
130
  options.is_a?(Hash) ? options[:ttl] : Interlock.config[:ttl] )
107
- end
108
- end
109
-
131
+ end
132
+ end
110
133
  end
111
134
 
112
135
  #
@@ -115,16 +138,18 @@ module Interlock
115
138
  def install_fragments
116
139
  # Memcached fragment caching is mandatory
117
140
  ActionView::Helpers::CacheHelper.class_eval do
118
- def cache(name, options = nil, &block)
141
+ def cache(name = {}, options = nil, &block)
119
142
  # Things explode if options does not default to nil
120
143
  RAILS_DEFAULT_LOGGER.debug "** fragment #{name} stored via obsolete cache() call"
121
- @controller.cache_erb_fragment(block, name, options)
144
+ @controller.fragment_for(output_buffer, name, options, &block)
122
145
  end
123
146
  end
124
- ActionController::Base.fragment_cache_store = CACHE
147
+ ActionController::Base.cache_store = CACHE
125
148
 
126
149
  # Sessions are optional
127
150
  if Interlock.config[:sessions]
151
+ # XXX Right now this requires memcache-client to be installed, due to a Rails problem.
152
+ # http://dev.rubyonrails.org/ticket/11290
128
153
  ActionController::Base.session_store = :mem_cache_store
129
154
  ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.update 'cache' => CACHE
130
155
  end
@@ -134,17 +159,10 @@ module Interlock
134
159
  # Configure ActiveRecord#find caching.
135
160
  #
136
161
  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
162
+ RAILS_DEFAULT_LOGGER.warn "** using interlock finder caches"
163
+ ActiveRecord::Base.send(:include, Interlock::Finders)
146
164
  end
147
165
 
148
- end
166
+ end
149
167
  end
150
- end
168
+ end
@@ -1,131 +1,155 @@
1
1
 
2
2
  module Interlock
3
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
4
+ def self.included(klass)
5
+ class << klass
6
+ alias_method :find_via_db, :find
7
+ remove_method :find
8
+ end
19
9
 
10
+ klass.extend ClassMethods
20
11
  end
21
12
 
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
13
+ module ClassMethods
14
+
15
+ #
16
+ # Cached find.
17
+ #
18
+ # Any other options besides ids short-circuit the cache.
19
+ #
20
+ def find(*args)
21
+ return find_via_db(*args) if args.last.is_a? Hash or args.first.is_a? Symbol
22
+ ids = args.flatten.compact.uniq
23
+ return find_via_db(ids) if ids.blank?
24
+
25
+ records = find_via_cache(ids, true)
26
+
27
+ if ids.length > 1 or args.first.is_a?(Array)
28
+ records
29
+ else
30
+ records.first
31
+ end
32
+
33
+ end
29
34
 
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
35
+ #
36
+ # Cached find_by_id. Short-circuiting works the same as find.
37
+ #
38
+ def find_by_id(*args)
39
+ return method_missing(:find_by_id, *args) if args.last.is_a? Hash
40
+ find_via_cache(args, false).first
41
+ end
37
42
 
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
43
+ #
44
+ # Cached find_all_by_id. Ultrasphinx uses this. Short-circuiting works the same as find.
45
+ #
46
+ def find_all_by_id(*args)
47
+ return method_missing(:find_all_by_id, *args) if args.last.is_a? Hash
48
+ find_via_cache(args, false)
49
+ end
51
50
 
52
- def find_via_cache(ids, should_raise) #:doc:
53
- results = []
51
+ #
52
+ # Build the model cache key for a particular id.
53
+ #
54
+ def caching_key(id)
55
+ Interlock.caching_key(
56
+ self.base_class.name,
57
+ "find",
58
+ id,
59
+ "default"
60
+ )
61
+ end
62
+
63
+ def finder_ttl
64
+ 0
65
+ end
66
+
67
+ private
68
+
69
+ def find_via_cache(ids, should_raise) #:doc:
70
+ results = []
54
71
 
55
- ordered_keys_to_ids = ids.map { |id| [caching_key(id), id.to_i] }
56
- keys_to_ids = Hash[*ordered_keys_to_ids.flatten]
72
+ ordered_keys_to_ids = ids.flatten.map { |id| [caching_key(id), id.to_i] }
73
+ keys_to_ids = Hash[*ordered_keys_to_ids.flatten]
57
74
 
58
- records = {}
75
+ records = {}
59
76
 
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
77
+ if ActionController::Base.perform_caching
78
+ load_from_local_cache(records, keys_to_ids)
79
+ load_from_memcached(records, keys_to_ids)
80
+ end
64
81
 
65
- load_from_db(records, keys_to_ids)
82
+ load_from_db(records, keys_to_ids)
66
83
 
67
- # Put them in order
84
+ # Put them in order
68
85
 
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
86
+ ordered_keys_to_ids.each do |key, |
87
+ record = records[key]
88
+ raise ActiveRecord::RecordNotFound, "Couldn't find #{self.name} with ID=#{keys_to_ids[key]}" if should_raise and !record
89
+ results << record
90
+ end
91
+
92
+ # Don't return Nil objects, only the found records
93
+ results.compact
73
94
  end
74
-
75
- results
76
- end
77
95
 
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
96
+ def load_from_local_cache(current, keys_to_ids) #:doc:
97
+ # Load from the local cache
91
98
  records = {}
92
- missed = keys_to_ids.reject { |key, | current[key] }
99
+ keys_to_ids.each do |key, |
100
+ record = Interlock.local_cache.read(key, nil)
101
+ records[key] = record if record
102
+ end
103
+ current.merge!(records)
104
+ end
105
+
106
+ def load_from_memcached(current, keys_to_ids) #:doc:
107
+ # Drop to memcached if necessary
108
+ if current.size < keys_to_ids.size
109
+ records = {}
110
+ missed = keys_to_ids.reject { |key, | current[key] }
93
111
 
94
- records = CACHE.get_multi(*missed.keys)
112
+ records = CACHE.get_multi(*missed.keys)
95
113
 
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
114
+ # Set missed to the caches
115
+ records.each do |key, value|
116
+ Interlock.say key, "is loading from memcached", "model"
117
+ Interlock.local_cache.write(key, value, nil)
118
+ end
101
119
 
102
- current.merge!(records)
103
- end
104
- end
120
+ current.merge!(records)
121
+ end
122
+ end
105
123
 
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
124
+ def load_from_db(current, keys_to_ids) #:doc:
125
+ # Drop to db if necessary
126
+ if current.size < keys_to_ids.size
127
+ missed = keys_to_ids.reject { |key, | current[key] }
128
+ ids_to_keys = keys_to_ids.invert
111
129
 
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)]
130
+ # Load from the db
131
+ ids_to_find = missed.values
132
+ if ids_to_find.length > 1
133
+ records = send("find_all_by_#{primary_key}".to_sym, ids_to_find, {})
134
+ else
135
+ records = [send("find_by_#{primary_key}".to_sym, ids_to_find.first, {})].compact # explicitly just look for one if that's all that's needed
136
+ end
137
+
138
+ records = Hash[*(records.map do |record|
139
+ [ids_to_keys[record.id], record]
140
+ end.flatten)]
117
141
 
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
142
+ # Set missed to the caches
143
+ records.each do |key, value|
144
+ Interlock.say key, "is loading from the db", "model"
145
+ Interlock.local_cache.write(key, value, nil)
146
+ CACHE.set key, value, value.class.finder_ttl unless Interlock.config[:disabled]
147
+ end
124
148
 
125
- current.merge!(records)
126
- end
149
+ current.merge!(records)
150
+ end
151
+ end
127
152
  end
128
-
129
- end
130
- end
131
-
153
+ end # Finders
154
+ end # Interlock
155
+
@@ -76,9 +76,11 @@ module Interlock
76
76
  # Add each key with scope to the appropriate dependencies array.
77
77
  #
78
78
  def register_dependencies(dependencies, key)
79
+ return if Interlock.config[:disabled]
80
+
79
81
  Array(dependencies).each do |klass, scope|
80
- dep_key = dependency_key(klass)
81
-
82
+ dep_key = dependency_key(klass, scope, key)
83
+
82
84
  # Get the value for this class/key out of the global store.
83
85
  this = (CACHE.get(dep_key) || {})[key]
84
86
 
@@ -89,21 +91,32 @@ module Interlock
89
91
  Interlock.say key, "registered a dependency on #{klass} -> #{scope.inspect}."
90
92
  (hash || {}).merge({key => scope})
91
93
  end
92
- end
93
-
94
+ end
94
95
  end
95
96
  end
96
97
 
97
98
  def say(key, msg, type = "fragment") #:nodoc:
98
- RAILS_DEFAULT_LOGGER.warn "** #{type} #{key.inspect[1..-2]} #{msg}"
99
+ log "** #{type} #{key.inspect[1..-2]} #{msg}"
99
100
  end
100
-
101
+
102
+ def log(msg)
103
+ case Interlock.config[:log_level]
104
+ when 'debug', 'info', 'warn', 'error'
105
+ log_method = Interlock.config[:log_level]
106
+ else
107
+ log_method = :debug
108
+ end
109
+
110
+ RAILS_DEFAULT_LOGGER.send( log_method, msg )
111
+ end
112
+
101
113
  #
102
114
  # Get the Memcached key for a class's dependency list. We store per-class
103
115
  # to reduce lock contention.
104
116
  #
105
- def dependency_key(klass)
106
- "interlock:#{ENV['RAILS_ASSET_ID']}:dependency:#{klass.name}"
117
+ def dependency_key(klass, scope, key)
118
+ id = (scope == :id ? ":#{key.field(4)}" : nil)
119
+ "interlock:#{ENV['RAILS_ASSET_ID']}:dependency:#{klass.name}#{id}"
107
120
  end
108
121
 
109
122
  #
@@ -135,9 +148,8 @@ module Interlock
135
148
  def invalidate(key)
136
149
  # Console and tests do not install the local cache
137
150
  Interlock.local_cache.delete(key) if Interlock.local_cache
151
+ ActionController::Base.cache_store.delete key
152
+ end
138
153
 
139
- ActionController::Base.fragment_cache_store.delete key
140
- end
141
-
142
154
  end
143
155
  end
@@ -17,14 +17,13 @@ module Interlock
17
17
  # for this.
18
18
  begin
19
19
  response = CACHE.add("lock:#{key}", "Locked by #{Process.pid}", lock_expiry)
20
- # Nil is a successful response for Memcached, so we'll simulate the MemCache
21
- # API.
22
- response ||= "STORED\r\n"
23
- rescue Object => e
24
- # Catch exceptions from Memcached without setting response.
20
+ # Nil is a successful response for Memcached 0.11, and "STORED\r\n" for MemCache.
21
+ response = [true, nil, "STORED\r\n"].include?(response)
22
+ rescue Memcached::NotStored
23
+ # Do nothing
25
24
  end
26
25
 
27
- if response == "STORED\r\n"
26
+ if response
28
27
  begin
29
28
  value = yield(CACHE.get(key))
30
29
  CACHE.set(key, value)
@@ -33,9 +32,11 @@ module Interlock
33
32
  CACHE.delete("lock:#{key}")
34
33
  end
35
34
  else
35
+ # Wait
36
36
  sleep((2**count) / 2.0)
37
37
  end
38
38
  end
39
+
39
40
  raise ::Interlock::LockAcquisitionError, "Couldn't acquire lock for #{key}"
40
41
  end
41
42
 
@@ -5,7 +5,7 @@ require 'action_controller'
5
5
  Rails::Initializer.run do |config|
6
6
 
7
7
  if ActionController::Base.respond_to? 'session='
8
- config.action_controller.session = {:session_key => '_app_session', :secret => '22cde4d5c1a61ba69a81795322cde4d5c1a61ba69a817953'}
8
+ config.action_controller.session = {:session_key => rand.to_s, :secret => '22cde4d5c1a61ba69a81795322cde4d5c1a61ba69a817953'}
9
9
  end
10
10
 
11
11
  # config.to_prepare do
@@ -5,6 +5,5 @@ config.cache_classes = production
5
5
  config.whiny_nils = true
6
6
  config.action_controller.consider_all_requests_local = !production
7
7
  config.action_controller.perform_caching = true
8
- config.action_view.cache_template_extensions = production
9
8
  config.action_view.debug_rjs = !production
10
9
  config.action_mailer.raise_delivery_errors = false
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -39,6 +39,23 @@ class ServerTest < Test::Unit::TestCase
39
39
  assert_match(/all:untagged read from memcached/, log)
40
40
  end
41
41
 
42
+ def test_controller_respects_log_level
43
+ remote = <<-CODE
44
+ RAILS_DEFAULT_LOGGER.level = Logger::INFO;
45
+ Interlock.config[:log_level] = 'info'
46
+ CODE
47
+ remote_eval(remote)
48
+
49
+ truncate
50
+ browse("items")
51
+ assert_match(/cleared interlock local cache/, log)
52
+
53
+ remote_eval("Interlock.config[:log_level] = 'debug'")
54
+ truncate
55
+ browse("items")
56
+ assert_no_match(/cleared interlock local cache/, log)
57
+ end
58
+
42
59
  def test_broad_invalidation
43
60
  browse("items")
44
61
  assert_match(/all:untagged is running the controller block/, log)
@@ -71,7 +88,8 @@ class ServerTest < Test::Unit::TestCase
71
88
 
72
89
  def test_caching_with_tag
73
90
  # This test is a little over-complicated
74
- sleep(4)
91
+ remote_eval("Item.update_all('updated_at = NULL')")
92
+
75
93
  assert_no_match(/Artichoke/, browse("items/recent?seconds=3"))
76
94
  assert_match(/recent:all:3 is running the controller block/, log)
77
95
 
@@ -81,13 +99,13 @@ class ServerTest < Test::Unit::TestCase
81
99
  assert_no_match(/recent:all:3 is running the controller block/, log)
82
100
 
83
101
  truncate
84
- remote_eval("Item.find(1).save!")
102
+ remote_eval("Item.find(1).update_attributes!(:description => 'Changed!')")
85
103
  assert_match(/Artichoke/, browse("items/recent?seconds=4"))
86
104
  assert_match(/recent:all:4 is running the controller block/, log)
87
105
 
88
106
  truncate
89
107
  assert_no_match(/Artichoke/, browse("items/recent?seconds=3"))
90
- assert_no_match(/recent:all:3 is running the controller block/, log)
108
+ assert_no_match(/recent:all:3 is running the controller block/, log)
91
109
  end
92
110
 
93
111
  def test_caching_with_perform_false
@@ -0,0 +1,8 @@
1
+ require "#{File.dirname(__FILE__)}/../test_helper"
2
+
3
+ class FinderTest < Test::Unit::TestCase
4
+ def test_reload_should_work
5
+ item = Item.find(1)
6
+ assert_equal Item.find(1, {}), item.reload
7
+ end
8
+ end
@@ -9,7 +9,8 @@ class FinderTest < Test::Unit::TestCase
9
9
  ### Finder caching tests
10
10
 
11
11
  def test_find_without_cache
12
- Item.find(1, {})
12
+ # Non-empty options hash bypasses the cache entirely, including the logging
13
+ Item.find(1, {:conditions => "1 = 1"})
13
14
  assert_no_match(/model.*Item:find:1:default is loading from the db/, log)
14
15
  end
15
16
 
@@ -33,14 +34,19 @@ class FinderTest < Test::Unit::TestCase
33
34
  Item.find([1, 2])
34
35
  assert_match(/model.*Item:find:1:default is loading from memcached/, log)
35
36
  assert_match(/model.*Item:find:2:default is loading from memcached/, log)
36
- end
37
+ end
38
+
39
+ def test_single_element_array_returns_array
40
+ assert_equal Item.find([1], {}),
41
+ Item.find([1])
42
+ end
37
43
 
38
44
  def test_find_raise
39
45
  assert_raises(ActiveRecord::RecordNotFound) do
40
46
  Item.find(44)
41
- end
47
+ end
42
48
  end
43
-
49
+
44
50
  def test_find_with_array_raise
45
51
  assert_raises(ActiveRecord::RecordNotFound) do
46
52
  # Once from the DB
@@ -49,7 +55,12 @@ class FinderTest < Test::Unit::TestCase
49
55
  assert_raises(ActiveRecord::RecordNotFound) do
50
56
  # Once from Memcached
51
57
  Item.find([1, 2, 44])
52
- end
58
+ end
59
+ end
60
+
61
+ def test_find_with_array_ignores_nil
62
+ assert_equal Item.find(1, nil, {}), Item.find(1, nil)
63
+ assert_equal Item.find([1, nil], {}), Item.find([1, nil])
53
64
  end
54
65
 
55
66
  def test_invalidate
@@ -59,7 +70,39 @@ class FinderTest < Test::Unit::TestCase
59
70
  assert_match(/model.*Item:find:1:default is loading from the db/, log)
60
71
  Item.find(1)
61
72
  assert_match(/model.*Item:find:1:default is loading from memcached/, log)
62
- end
73
+ end
74
+
75
+ def test_reload_should_invalidate
76
+ item = Item.find(1)
77
+ item.reload
78
+ assert_match(/model.*Item:find:1:default invalidated with finders/, log)
79
+ truncate
80
+ Item.find(1)
81
+ assert_match(/model.*Item:find:1:default is loading from memcached/, log)
82
+ end
83
+
84
+ def test_update_attributes_should_invalidate
85
+ item = Item.find(1)
86
+ name = item.name
87
+
88
+ item.update_attributes!(:name => 'Updated')
89
+ updated_item = Item.find(1)
90
+ assert_equal 'Updated', item.name
91
+
92
+ # Restore name for further tests
93
+ item.update_attributes!(:name => name)
94
+ end
95
+
96
+ def test_update_all_should_invalidate
97
+ # TODO
98
+ end
99
+
100
+ def test_update_counters_should_invalidate
101
+ item = Item.find(1)
102
+ Item.update_counters(1, :counting_something => 1)
103
+ updated_item = Item.find(1)
104
+ assert_equal updated_item.counting_something, item.counting_something + 1
105
+ end
63
106
 
64
107
  def test_find_all_by_id
65
108
  assert_equal Item.find_all_by_id(44, {}),
@@ -69,6 +112,10 @@ class FinderTest < Test::Unit::TestCase
69
112
  assert_equal Item.find_all_by_id(1, 2, {}),
70
113
  Item.find_all_by_id(1, 2)
71
114
  end
115
+
116
+ def test_invalidate_sti
117
+ # XXX Need a regression test
118
+ end
72
119
 
73
120
  def test_find_by_id
74
121
  assert_equal Item.find_by_id(44, {}),
@@ -78,7 +125,31 @@ class FinderTest < Test::Unit::TestCase
78
125
  assert_equal Item.find_by_id(1, 2, {}),
79
126
  Item.find_by_id(1, 2)
80
127
  end
81
-
128
+
129
+ def test_custom_log_level
130
+ old_level = RAILS_DEFAULT_LOGGER.level
131
+ RAILS_DEFAULT_LOGGER.level = Logger::INFO
132
+
133
+ Interlock.config[:log_level] = 'info'
134
+ truncate
135
+ Item.find(1)
136
+ assert_match(/model.*Item:find:1:default is loading/, log)
137
+
138
+ Interlock.config[:log_level] = 'debug'
139
+ truncate
140
+ Item.find(1)
141
+ assert_no_match(/model.*Item:find:1:default is loading/, log)
142
+ ensure
143
+ RAILS_DEFAULT_LOGGER.level = old_level
144
+ end
145
+
146
+ def test_find_with_nonstandard_primary_key
147
+ db = Book.find_via_db(1137)
148
+ cache = Book.find(1137)
149
+ assert_equal db, cache
150
+ assert_equal Book.find_via_db(1137, 2001, :order => "guid ASC"), Book.find(1137, 2001)
151
+ end
152
+
82
153
  ### Support methods
83
154
 
84
155
  def setup
@@ -18,4 +18,13 @@ class InterlockTest < Test::Unit::TestCase
18
18
  Interlock.caching_key('controller', 'action', 'id', 'tag with illegal characters')
19
19
  'generated keys should not contain illegal characters'
20
20
  end
21
+
22
+ def disabled_test_register_dependencies_with_many_keys_one_dependency
23
+ assert_nothing_raised do
24
+ (1..5000).each do |i|
25
+ Interlock.register_dependencies({Item=>:id}, Interlock.caching_key("a"*200, "show", i, nil))
26
+ end
27
+ end
28
+ end
29
+
21
30
  end
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.3"
4
+ version: "1.4"
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-03-02 00:00:00 -05:00
33
+ date: 2009-11-18 00:00:00 -08:00
34
34
  default_executable:
35
35
  dependencies: []
36
36
 
@@ -40,8 +40,18 @@ executables: []
40
40
 
41
41
  extensions: []
42
42
 
43
- extra_rdoc_files: []
44
-
43
+ extra_rdoc_files:
44
+ - CHANGELOG
45
+ - lib/interlock/action_controller.rb
46
+ - lib/interlock/action_view.rb
47
+ - lib/interlock/active_record.rb
48
+ - lib/interlock/config.rb
49
+ - lib/interlock/finders.rb
50
+ - lib/interlock/interlock.rb
51
+ - lib/interlock/lock.rb
52
+ - LICENSE
53
+ - README
54
+ - TODO
45
55
  files:
46
56
  - CHANGELOG
47
57
  - examples/memcached.yml
@@ -129,11 +139,19 @@ files:
129
139
  - test/unit/lock_test.rb
130
140
  - TODO
131
141
  - interlock.gemspec
142
+ - Rakefile
132
143
  has_rdoc: true
133
144
  homepage: http://blog.evanweaver.com/files/doc/fauna/interlock/
134
- post_install_message:
135
- rdoc_options: []
145
+ licenses: []
136
146
 
147
+ post_install_message:
148
+ rdoc_options:
149
+ - --line-numbers
150
+ - --inline-source
151
+ - --title
152
+ - Interlock
153
+ - --main
154
+ - README
137
155
  require_paths:
138
156
  - lib
139
157
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -146,17 +164,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
146
164
  requirements:
147
165
  - - ">="
148
166
  - !ruby/object:Gem::Version
149
- version: "0"
167
+ version: "1.2"
150
168
  version:
151
169
  requirements: []
152
170
 
153
171
  rubyforge_project: fauna
154
- rubygems_version: 1.0.1
172
+ rubygems_version: 1.3.5
155
173
  signing_key:
156
- specification_version: 2
174
+ specification_version: 3
157
175
  summary: A Rails plugin for maintainable and high-efficiency caching.
158
176
  test_files:
159
177
  - test/integration/server_test.rb
178
+ - test/unit/active_record_test.rb
160
179
  - test/unit/finder_test.rb
161
180
  - test/unit/interlock_test.rb
162
181
  - test/unit/lock_test.rb
metadata.gz.sig CHANGED
Binary file