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