interlock 1.3 → 1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|