interlock 1.3 → 1.4
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/CHANGELOG +2 -0
- data/README +6 -8
- data/Rakefile +34 -0
- data/interlock.gemspec +20 -49
- data/lib/interlock.rb +5 -1
- data/lib/interlock/action_controller.rb +32 -8
- data/lib/interlock/action_view.rb +4 -7
- data/lib/interlock/active_record.rb +35 -10
- data/lib/interlock/config.rb +48 -30
- data/lib/interlock/finders.rb +129 -105
- data/lib/interlock/interlock.rb +23 -11
- data/lib/interlock/lock.rb +7 -6
- data/test/integration/app/config/environment.rb +1 -1
- data/test/integration/app/config/environments/development.rb +0 -1
- data/test/integration/app/public/dispatch.cgi +0 -0
- data/test/integration/app/public/dispatch.fcgi +0 -0
- data/test/integration/app/public/dispatch.rb +0 -0
- data/test/integration/app/script/about +0 -0
- data/test/integration/app/script/console +0 -0
- data/test/integration/app/script/destroy +0 -0
- data/test/integration/app/script/generate +0 -0
- data/test/integration/app/script/performance/benchmarker +0 -0
- data/test/integration/app/script/performance/profiler +0 -0
- data/test/integration/app/script/performance/request +0 -0
- data/test/integration/app/script/plugin +0 -0
- data/test/integration/app/script/process/inspector +0 -0
- data/test/integration/app/script/process/reaper +0 -0
- data/test/integration/app/script/process/spawner +0 -0
- data/test/integration/app/script/runner +0 -0
- data/test/integration/app/script/server +0 -0
- data/test/integration/server_test.rb +21 -3
- data/test/unit/active_record_test.rb +8 -0
- data/test/unit/finder_test.rb +78 -7
- data/test/unit/interlock_test.rb +9 -0
- metadata +28 -9
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG
CHANGED
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
|
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
|
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
|
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
|
|
data/Rakefile
ADDED
@@ -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
|
data/interlock.gemspec
CHANGED
@@ -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.
|
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(">=
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
12
8
|
s.authors = [""]
|
13
|
-
s.
|
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.
|
17
|
-
s.
|
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.
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
data/lib/interlock.rb
CHANGED
@@ -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 = ::
|
153
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
20
|
-
|
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
|
-
|
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
|
data/lib/interlock/config.rb
CHANGED
@@ -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
|
-
:
|
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
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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.
|
144
|
+
@controller.fragment_for(output_buffer, name, options, &block)
|
122
145
|
end
|
123
146
|
end
|
124
|
-
ActionController::Base.
|
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
|
-
|
138
|
-
|
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
|
data/lib/interlock/finders.rb
CHANGED
@@ -1,131 +1,155 @@
|
|
1
1
|
|
2
2
|
module Interlock
|
3
3
|
module Finders
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
53
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
75
|
+
records = {}
|
59
76
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
82
|
+
load_from_db(records, keys_to_ids)
|
66
83
|
|
67
|
-
|
84
|
+
# Put them in order
|
68
85
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
112
|
+
records = CACHE.get_multi(*missed.keys)
|
95
113
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
120
|
+
current.merge!(records)
|
121
|
+
end
|
122
|
+
end
|
105
123
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
126
|
-
|
149
|
+
current.merge!(records)
|
150
|
+
end
|
151
|
+
end
|
127
152
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
153
|
+
end # Finders
|
154
|
+
end # Interlock
|
155
|
+
|
data/lib/interlock/interlock.rb
CHANGED
@@ -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
|
-
|
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
|
-
"
|
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
|
data/lib/interlock/lock.rb
CHANGED
@@ -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,
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
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 =>
|
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
|
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
|
-
|
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).
|
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
|
data/test/unit/finder_test.rb
CHANGED
@@ -9,7 +9,8 @@ class FinderTest < Test::Unit::TestCase
|
|
9
9
|
### Finder caching tests
|
10
10
|
|
11
11
|
def test_find_without_cache
|
12
|
-
|
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
|
data/test/unit/interlock_test.rb
CHANGED
@@ -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.
|
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:
|
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
|
-
|
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: "
|
167
|
+
version: "1.2"
|
150
168
|
version:
|
151
169
|
requirements: []
|
152
170
|
|
153
171
|
rubyforge_project: fauna
|
154
|
-
rubygems_version: 1.
|
172
|
+
rubygems_version: 1.3.5
|
155
173
|
signing_key:
|
156
|
-
specification_version:
|
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
|