stale 0.0.1
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/.gitignore +4 -0
- data/.travis.yml +4 -0
- data/Gemfile +5 -0
- data/LICENSE +22 -0
- data/README.md +7 -0
- data/Rakefile +11 -0
- data/lib/stale.rb +13 -0
- data/lib/stale/cache.rb +49 -0
- data/lib/stale/configuration.rb +46 -0
- data/lib/stale/controller.rb +20 -0
- data/lib/stale/dependencies.rb +39 -0
- data/lib/stale/fragments.rb +19 -0
- data/lib/stale/interface.rb +31 -0
- data/lib/stale/model.rb +23 -0
- data/lib/stale/parameters.rb +31 -0
- data/lib/stale/version.rb +3 -0
- data/lib/stale/view.rb +39 -0
- data/stale.gemspec +18 -0
- data/test/cache_test.rb +55 -0
- data/test/configuration_test.rb +33 -0
- data/test/controller_test.rb +53 -0
- data/test/dependencies_test.rb +46 -0
- data/test/fragments_test.rb +32 -0
- data/test/interface_test.rb +32 -0
- data/test/model_test.rb +10 -0
- data/test/parameters_test.rb +54 -0
- data/test/test_helper.rb +46 -0
- data/test/view_test.rb +55 -0
- metadata +115 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Norbert Crombach
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/lib/stale.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
|
3
|
+
require 'stale/cache'
|
4
|
+
require 'stale/dependencies'
|
5
|
+
require 'stale/fragments'
|
6
|
+
require 'stale/interface'
|
7
|
+
require 'stale/parameters'
|
8
|
+
|
9
|
+
require 'stale/model'
|
10
|
+
require 'stale/view'
|
11
|
+
require 'stale/controller'
|
12
|
+
|
13
|
+
require 'stale/configuration'
|
data/lib/stale/cache.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Stale
|
2
|
+
class Cache
|
3
|
+
attr_reader :data
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
@data = data
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(key)
|
10
|
+
data.get(key_with_prefix(key))
|
11
|
+
end
|
12
|
+
|
13
|
+
def set(key, value, ttl = nil)
|
14
|
+
data.set(key_with_prefix(key), value, ttl)
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(key, value, ttl = nil)
|
18
|
+
data.add(key_with_prefix(key), value, ttl)
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete(*keys)
|
22
|
+
keys.each do |key|
|
23
|
+
data.delete(key_with_prefix(key))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def modify(key, ttl = nil, &block)
|
28
|
+
stored = false
|
29
|
+
retries = 0
|
30
|
+
|
31
|
+
while !stored
|
32
|
+
stored = data.cas(key_with_prefix(key), ttl, &block)
|
33
|
+
|
34
|
+
if stored.nil?
|
35
|
+
stored = add(key, block.call(nil), ttl)
|
36
|
+
elsif stored == false
|
37
|
+
retries += 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
stored
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def key_with_prefix(key)
|
46
|
+
Stale.configuration[:key_prefix] + key
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Stale
|
2
|
+
class Configuration
|
3
|
+
include Parameters
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = {
|
6
|
+
:expiration_time => 1.day,
|
7
|
+
:key_prefix => 'stale:',
|
8
|
+
:key_separator => ':',
|
9
|
+
:model_key_separator => ':',
|
10
|
+
:dependency_key_suffix => ':dependencies'
|
11
|
+
}
|
12
|
+
|
13
|
+
attr_accessor :cache, :interface
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@options = DEFAULT_OPTIONS.clone
|
18
|
+
@interface = Interface
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
options[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(key, value)
|
26
|
+
options[key] = value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
mattr_accessor :configuration
|
31
|
+
self.configuration = Configuration.new
|
32
|
+
|
33
|
+
def self.configure(&block)
|
34
|
+
configuration = Configuration.new
|
35
|
+
yield configuration if block_given?
|
36
|
+
self.configuration = configuration
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.interface
|
40
|
+
configuration.interface
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.cache
|
44
|
+
configuration.cache
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Stale
|
2
|
+
module Controller
|
3
|
+
include Fragments
|
4
|
+
|
5
|
+
def stale(parameters, &block)
|
6
|
+
if !perform_caching
|
7
|
+
yield
|
8
|
+
return
|
9
|
+
end
|
10
|
+
|
11
|
+
key = Stale.interface.key_for_parameters(parameters, self)
|
12
|
+
Stale.interface.register_dependencies(key)
|
13
|
+
|
14
|
+
if !stale_fragment_exist?(key)
|
15
|
+
yield
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Stale
|
2
|
+
module Dependencies
|
3
|
+
def register_dependencies(key_array)
|
4
|
+
model_keys = models_from_key(key_array)
|
5
|
+
key_string = key_as_string(key_array)
|
6
|
+
|
7
|
+
model_keys.each do |model_key|
|
8
|
+
register_dependency(model_key, key_string)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def expire_dependencies(model_key)
|
13
|
+
expire_dependencies_from_list(dependency_key_for_instance(model_key))
|
14
|
+
expire_dependencies_from_list(dependency_key_for_collection(model_key))
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_dependency(model_key, key_string)
|
18
|
+
add_dependency_to_list(dependency_key_for_instance(model_key), key_string)
|
19
|
+
end
|
20
|
+
|
21
|
+
def dependency_key_for_instance(model_key)
|
22
|
+
model_key + Stale.configuration[:dependency_key_suffix]
|
23
|
+
end
|
24
|
+
|
25
|
+
def dependency_key_for_collection(model_key)
|
26
|
+
collection_from_key(model_key) + Stale.configuration[:dependency_key_suffix]
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_dependency_to_list(dependency_key, key_string)
|
30
|
+
Stale.cache.modify(dependency_key) do |dependency_list|
|
31
|
+
(Array(dependency_list) << key_string).uniq
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def expire_dependencies_from_list(dependency_key)
|
36
|
+
Stale.cache.delete(*Array(Stale.cache.get(dependency_key)))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Stale
|
2
|
+
module Fragments
|
3
|
+
def read_stale_fragment(key_array)
|
4
|
+
key_string = Stale.interface.key_as_string(key_array)
|
5
|
+
Stale.cache.get(key_string)
|
6
|
+
end
|
7
|
+
|
8
|
+
def write_stale_fragment(key_array, value, ttl = nil)
|
9
|
+
ttl ||= Stale.configuration[:expiration_time]
|
10
|
+
|
11
|
+
key_string = Stale.interface.key_as_string(key_array)
|
12
|
+
Stale.cache.set(key_string, value, ttl)
|
13
|
+
end
|
14
|
+
|
15
|
+
def stale_fragment_exist?(key_array)
|
16
|
+
!!read_stale_fragment(key_array)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Stale
|
2
|
+
module Interface
|
3
|
+
include Dependencies
|
4
|
+
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def key_for_parameters(parameters, controller)
|
8
|
+
Stale.configuration.key_for_parameters(parameters, controller)
|
9
|
+
end
|
10
|
+
|
11
|
+
def key_for_model(collection, id = nil, *args)
|
12
|
+
[key_for_collection(collection), id, *args].join(Stale.configuration[:model_key_separator])
|
13
|
+
end
|
14
|
+
|
15
|
+
def key_for_collection(collection)
|
16
|
+
collection.is_a?(Class) ? collection.name.underscore : collection
|
17
|
+
end
|
18
|
+
|
19
|
+
def key_as_string(key_array)
|
20
|
+
Array(key_array).join(Stale.configuration[:key_separator])
|
21
|
+
end
|
22
|
+
|
23
|
+
def models_from_key(key_array)
|
24
|
+
key_array.select { |key_part| key_part.include?(Stale.configuration[:model_key_separator]) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def collection_from_key(model_key)
|
28
|
+
key_for_model(model_key.split(Stale.configuration[:model_key_separator], 2).first)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/stale/model.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Stale
|
2
|
+
module Model
|
3
|
+
IDENTIFIER = :stale_key
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
after_commit :expire_stale_dependencies if respond_to?(:after_commit)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def stale_id
|
12
|
+
to_param
|
13
|
+
end
|
14
|
+
|
15
|
+
def stale_key
|
16
|
+
Stale.interface.key_for_model(self.class, stale_id)
|
17
|
+
end
|
18
|
+
|
19
|
+
def expire_stale_dependencies
|
20
|
+
Stale.interface.expire_dependencies(stale_key)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Stale
|
2
|
+
module Parameters
|
3
|
+
def key_for_parameters(parameters, controller = nil)
|
4
|
+
parameters.map { |parameter| key_for_parameter(parameter, controller) }
|
5
|
+
end
|
6
|
+
|
7
|
+
def key_for_parameter(parameter, controller = nil)
|
8
|
+
if parameter.is_a?(Symbol)
|
9
|
+
self.named_parameters[parameter].call(controller)
|
10
|
+
elsif parameter.respond_to?(Model::IDENTIFIER)
|
11
|
+
parameter.send(Model::IDENTIFIER)
|
12
|
+
elsif parameter.is_a?(Class)
|
13
|
+
Stale.interface.key_for_model(parameter)
|
14
|
+
elsif parameter.is_a?(Array)
|
15
|
+
Stale.interface.key_for_model(*parameter)
|
16
|
+
else
|
17
|
+
parameter.to_param
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def define_named_parameter(method, &block)
|
22
|
+
block = method.to_proc unless block_given?
|
23
|
+
self.named_parameters[method] = block
|
24
|
+
end
|
25
|
+
alias_method :parameter, :define_named_parameter
|
26
|
+
|
27
|
+
def named_parameters
|
28
|
+
@named_parameters ||= {}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/stale/view.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Stale
|
2
|
+
module View
|
3
|
+
include Fragments
|
4
|
+
|
5
|
+
def stale(parameters, &block)
|
6
|
+
if !controller.perform_caching
|
7
|
+
safe_concat(build_stale_fragment(&block))
|
8
|
+
return
|
9
|
+
end
|
10
|
+
|
11
|
+
key = Stale.interface.key_for_parameters(parameters, controller)
|
12
|
+
Stale.interface.register_dependencies(key)
|
13
|
+
|
14
|
+
unless fragment = read_stale_fragment(key)
|
15
|
+
fragment = build_stale_fragment(&block)
|
16
|
+
write_stale_fragment(key, fragment)
|
17
|
+
end
|
18
|
+
|
19
|
+
safe_concat(fragment)
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def build_stale_fragment(&block)
|
25
|
+
# copied from actionpack/lib/action_view/helpers/cache_helper.rb
|
26
|
+
pos = output_buffer.length
|
27
|
+
yield
|
28
|
+
if output_buffer.html_safe?
|
29
|
+
safe_output_buffer = output_buffer.to_str
|
30
|
+
fragment = safe_output_buffer.slice!(pos..-1)
|
31
|
+
self.output_buffer = output_buffer.class.new(safe_output_buffer)
|
32
|
+
else
|
33
|
+
fragment = output_buffer.slice!(pos..-1)
|
34
|
+
end
|
35
|
+
|
36
|
+
fragment
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/stale.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "stale"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.authors = ["Norbert Crombach"]
|
6
|
+
s.email = ["norbert.crombach@primetheory.org"]
|
7
|
+
s.homepage = "http://github.com/norbert/stale"
|
8
|
+
s.summary = %q{Experimental fragment caching layer.}
|
9
|
+
|
10
|
+
s.rubyforge_project = "stale"
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.test_files = s.files.grep(/^test\//)
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
|
16
|
+
s.add_dependency 'rails', '~> 3.2'
|
17
|
+
s.add_development_dependency 'mocha', '0.12.1'
|
18
|
+
end
|
data/test/cache_test.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StaleCacheTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
@data = mock('data')
|
6
|
+
@cache = Stale::Cache.new(@data)
|
7
|
+
|
8
|
+
Stale.configure do |config|
|
9
|
+
config.cache = @cache
|
10
|
+
config[:key_prefix] = 'test:'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
teardown do
|
15
|
+
Stale.configure
|
16
|
+
end
|
17
|
+
|
18
|
+
test "performs get with prefix" do
|
19
|
+
@data.expects(:get).with('test:key').returns('test')
|
20
|
+
assert_equal 'test', @cache.get('key')
|
21
|
+
end
|
22
|
+
|
23
|
+
test "performs set with prefix" do
|
24
|
+
@data.expects(:set).with('test:key', 'test', 30).returns(true)
|
25
|
+
assert @cache.set('key', 'test', 30)
|
26
|
+
end
|
27
|
+
|
28
|
+
test "performs add with prefix" do
|
29
|
+
@data.expects(:add).with('test:key', 'test', 30).returns(true)
|
30
|
+
assert @cache.add('key', 'test', 30)
|
31
|
+
end
|
32
|
+
|
33
|
+
test "performs delete with prefix using multi block" do
|
34
|
+
@data.expects(:multi).never # FIXME
|
35
|
+
@data.expects(:delete).with('test:key:1')
|
36
|
+
@data.expects(:delete).with('test:key:2')
|
37
|
+
assert @cache.delete('key:1', 'key:2')
|
38
|
+
end
|
39
|
+
|
40
|
+
test "modifies keys with cas when set" do
|
41
|
+
@data.expects(:cas).with('test:key', nil).yields(1).returns(true)
|
42
|
+
assert @cache.modify('key') { |value| value + 1 }
|
43
|
+
end
|
44
|
+
|
45
|
+
test "modifies keys with add when not set" do
|
46
|
+
@data.expects(:cas).with('test:key', 30).yields(nil).returns(nil)
|
47
|
+
@data.expects(:add).with('test:key', 1, 30).returns(true)
|
48
|
+
assert @cache.modify('key', 30) { |value| value.to_i + 1 }
|
49
|
+
end
|
50
|
+
|
51
|
+
test "modifies keys by retrying when cas fails" do
|
52
|
+
@data.expects(:cas).twice.with('test:key', 30).yields(1).returns(false).then.returns(true)
|
53
|
+
assert @cache.modify('key', 30) { |value| value + 1 }
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StaleConfigurationTest < ActiveSupport::TestCase
|
4
|
+
teardown do
|
5
|
+
Stale.configure
|
6
|
+
end
|
7
|
+
|
8
|
+
test "allows configuration with block" do
|
9
|
+
result = nil
|
10
|
+
Stale.configure do |config|
|
11
|
+
result = config
|
12
|
+
end
|
13
|
+
assert_equal result, Stale.configuration
|
14
|
+
end
|
15
|
+
|
16
|
+
test "has accessors for options" do
|
17
|
+
@configuration = Stale::Configuration.new
|
18
|
+
value = 1.hour
|
19
|
+
assert_equal value, @configuration[:expiration_time] = value
|
20
|
+
assert_equal value, @configuration[:expiration_time]
|
21
|
+
end
|
22
|
+
|
23
|
+
test "provides accessors for cache and interface" do
|
24
|
+
@configuration = Stale::Configuration.new
|
25
|
+
@cache = mock('cache')
|
26
|
+
@interface = mock('interface')
|
27
|
+
@configuration.cache = @cache
|
28
|
+
@configuration.interface = @interface
|
29
|
+
Stale.configuration = @configuration
|
30
|
+
assert_equal @cache, Stale.cache
|
31
|
+
assert_equal @interface, Stale.interface
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StaleControllerTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
@controller = StaleTestController.new
|
6
|
+
@controller.perform_caching = true
|
7
|
+
end
|
8
|
+
|
9
|
+
test "runs block when fragment does not exist" do
|
10
|
+
key = ['test']
|
11
|
+
@controller.expects(:stale_fragment_exist?).with(key).returns(false)
|
12
|
+
|
13
|
+
assert_throws(:perform) {
|
14
|
+
@controller.stale(key) {
|
15
|
+
throw :perform
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
test "does not run block when fragment exists" do
|
21
|
+
key = ['test']
|
22
|
+
@controller.expects(:stale_fragment_exist?).with(key).returns(true)
|
23
|
+
|
24
|
+
assert_nothing_thrown {
|
25
|
+
@controller.stale(key) {
|
26
|
+
throw :perform
|
27
|
+
}
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
test "runs block when caching is disabled" do
|
32
|
+
key = ['test']
|
33
|
+
@controller.perform_caching = false
|
34
|
+
@controller.expects(:stale_fragment_exist?).never
|
35
|
+
|
36
|
+
assert_throws(:perform) {
|
37
|
+
@controller.stale(key) {
|
38
|
+
throw :perform
|
39
|
+
}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
test "registers dependencies for the converted key" do
|
44
|
+
key = [:test]
|
45
|
+
Stale.interface.expects(:key_for_parameters).with(key, @controller).returns(['test'])
|
46
|
+
Stale.interface.expects(:register_dependencies).with(['test'])
|
47
|
+
@controller.expects(:stale_fragment_exist?).with(['test']).returns(false)
|
48
|
+
|
49
|
+
@controller.stale(key) {
|
50
|
+
nil
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StaleDependenciesTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
@cache = Stale.configuration.cache = mock('cache')
|
6
|
+
@interface = Stale.interface
|
7
|
+
end
|
8
|
+
|
9
|
+
teardown do
|
10
|
+
Stale.configure
|
11
|
+
end
|
12
|
+
|
13
|
+
test "registers no dependencies for a key without models" do
|
14
|
+
@interface.expects(:register_dependency).never
|
15
|
+
@interface.register_dependencies(['test'])
|
16
|
+
end
|
17
|
+
|
18
|
+
test "registers dependencies for a key with models" do
|
19
|
+
@interface.expects(:register_dependency).with('model:1', 'test:model:1:model:2')
|
20
|
+
@interface.expects(:register_dependency).with('model:2', 'test:model:1:model:2')
|
21
|
+
@interface.register_dependencies(['test', 'model:1', 'model:2'])
|
22
|
+
end
|
23
|
+
|
24
|
+
test "expires dependencies for a model and collection" do
|
25
|
+
@interface.expects(:expire_dependencies_from_list).with('model:1:dependencies')
|
26
|
+
@interface.expects(:expire_dependencies_from_list).with('model::dependencies')
|
27
|
+
@interface.expire_dependencies('model:1')
|
28
|
+
end
|
29
|
+
|
30
|
+
test "registers dependencies for models" do
|
31
|
+
@interface.expects(:add_dependency_to_list).with('model:1:dependencies', 'test:model:1')
|
32
|
+
@interface.register_dependency('model:1', 'test:model:1')
|
33
|
+
end
|
34
|
+
|
35
|
+
test "adds dependencies to lists" do
|
36
|
+
@cache.expects(:modify).with('model:1:dependencies').yields(nil).returns(true)
|
37
|
+
@interface.add_dependency_to_list('model:1:dependencies', 'test:model:1')
|
38
|
+
end
|
39
|
+
|
40
|
+
test "expires dependencies from lists" do
|
41
|
+
keys = ['test:model:1', 'test:model:1:attributes']
|
42
|
+
@cache.expects(:get).with('model:1:dependencies').returns(keys)
|
43
|
+
@cache.expects(:delete).with(*keys)
|
44
|
+
@interface.expire_dependencies_from_list('model:1:dependencies')
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StaleFragmentsTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
@cache = Stale.configuration.cache = mock('cache')
|
6
|
+
@view = StaleTestView.new
|
7
|
+
end
|
8
|
+
|
9
|
+
teardown do
|
10
|
+
Stale.configure
|
11
|
+
end
|
12
|
+
|
13
|
+
test "reads fragments by key" do
|
14
|
+
@cache.expects(:get).with('controller:action').returns('test')
|
15
|
+
assert_equal 'test', @view.read_stale_fragment(['controller', 'action'])
|
16
|
+
end
|
17
|
+
|
18
|
+
test "writes fragments by key with default expiration time" do
|
19
|
+
@cache.expects(:set).with('controller:action', 'test', Stale.configuration[:expiration_time])
|
20
|
+
@view.write_stale_fragment(['controller', 'action'], 'test')
|
21
|
+
end
|
22
|
+
|
23
|
+
test "writes fragments by key with given expiration time" do
|
24
|
+
@cache.expects(:set).with('controller:action', 'test', 30)
|
25
|
+
@view.write_stale_fragment(['controller', 'action'], 'test', 30)
|
26
|
+
end
|
27
|
+
|
28
|
+
test "checks if fragments exist by reading" do
|
29
|
+
@cache.expects(:get).with('controller:action').returns('test')
|
30
|
+
assert @view.stale_fragment_exist?(['controller', 'action'])
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StaleInterfaceTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
@interface = Stale.interface
|
6
|
+
end
|
7
|
+
|
8
|
+
test "delegates converting parameters to configuration" do
|
9
|
+
controller_mock = mock('controller')
|
10
|
+
Stale.configuration.expects(:key_for_parameters).with(['test'], controller_mock)
|
11
|
+
@interface.key_for_parameters(['test'], controller_mock)
|
12
|
+
end
|
13
|
+
|
14
|
+
test "generates keys for models" do
|
15
|
+
assert_equal 'stale_test_model:1', @interface.key_for_model(StaleTestModel, 1)
|
16
|
+
assert_equal 'stale_test_model:', @interface.key_for_model(StaleTestModel)
|
17
|
+
assert_equal 'stale_test_model:1', @interface.key_for_model('stale_test_model', 1)
|
18
|
+
assert_equal 'stale_test_model:1:attributes', @interface.key_for_model('stale_test_model', 1, :attributes)
|
19
|
+
end
|
20
|
+
|
21
|
+
test "converts key arrays to strings" do
|
22
|
+
assert_equal 'test:model:1', @interface.key_as_string(['test', 'model:1'])
|
23
|
+
end
|
24
|
+
|
25
|
+
test "extracts model keys from arrays" do
|
26
|
+
assert_equal ['model:1', 'model:2:attributes'], @interface.models_from_key(['test', 'model:1', 'model:2:attributes'])
|
27
|
+
end
|
28
|
+
|
29
|
+
test "extracts collections from model keys" do
|
30
|
+
assert_equal 'model:', @interface.collection_from_key('model:1')
|
31
|
+
end
|
32
|
+
end
|
data/test/model_test.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StaleModelTest < ActiveSupport::TestCase
|
4
|
+
test "expires dependencies for model key" do
|
5
|
+
@model = StaleTestModel.new(1)
|
6
|
+
Stale.interface.expects(:expire_dependencies).with(@model.stale_key)
|
7
|
+
|
8
|
+
@model.expire_stale_dependencies
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StaleParametersTest < ActiveSupport::TestCase
|
4
|
+
def controller_mock
|
5
|
+
mock('controller', :controller_name => 'test')
|
6
|
+
end
|
7
|
+
|
8
|
+
setup do
|
9
|
+
@configuration = Stale::Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
test "defines named parameters with block" do
|
13
|
+
@configuration.define_named_parameter :controller_name do |controller|
|
14
|
+
controller.controller_name
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_equal 'test', @configuration.key_for_parameter(:controller_name, controller_mock)
|
18
|
+
end
|
19
|
+
|
20
|
+
test "defines named parameters without block" do
|
21
|
+
@configuration.define_named_parameter :controller_name
|
22
|
+
|
23
|
+
assert_equal 'test', @configuration.key_for_parameter(:controller_name, controller_mock)
|
24
|
+
end
|
25
|
+
|
26
|
+
test "converts model parameters to keys" do
|
27
|
+
assert_equal 'stale_test_model:1', @configuration.key_for_parameter(StaleTestModel.new(1))
|
28
|
+
end
|
29
|
+
|
30
|
+
test "converts class parameters to keys" do
|
31
|
+
assert_equal 'stale_test_model:', @configuration.key_for_parameter(StaleTestModel)
|
32
|
+
end
|
33
|
+
|
34
|
+
test "converts array parameters to keys" do
|
35
|
+
assert_equal 'stale_test_model:2', @configuration.key_for_parameter([StaleTestModel, 2])
|
36
|
+
end
|
37
|
+
|
38
|
+
test "converts string parameters to keys" do
|
39
|
+
assert_equal 'test', @configuration.key_for_parameter('test')
|
40
|
+
end
|
41
|
+
|
42
|
+
test "raises when converting unknown named parameters to keys" do
|
43
|
+
# FIXME
|
44
|
+
assert_raises(NoMethodError) {
|
45
|
+
@configuration.key_for_parameter(:test, mock('controller'))
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
test "converts multiple parameters to keys in order" do
|
50
|
+
@configuration.define_named_parameter :controller_name
|
51
|
+
|
52
|
+
assert_equal ['test', 'index'], @configuration.key_for_parameters([:controller_name, 'index'], controller_mock)
|
53
|
+
end
|
54
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'stale'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
class StaleTestController
|
7
|
+
include Stale::Controller
|
8
|
+
|
9
|
+
attr_accessor :perform_caching
|
10
|
+
|
11
|
+
def controller_name
|
12
|
+
'stale_test_controller'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class StaleTestModel
|
17
|
+
include Stale::Model
|
18
|
+
|
19
|
+
attr_reader :id
|
20
|
+
|
21
|
+
def initialize(id)
|
22
|
+
@id = id
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_param
|
26
|
+
id
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class StaleTestView
|
31
|
+
include Stale::View
|
32
|
+
|
33
|
+
attr_reader :controller, :output_buffer
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@controller = StaleTestController.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def safe_concat(value)
|
40
|
+
@output_buffer = value
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_stale_fragment
|
44
|
+
yield
|
45
|
+
end
|
46
|
+
end
|
data/test/view_test.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StaleViewTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
@view = StaleTestView.new
|
6
|
+
@view.controller.perform_caching = true
|
7
|
+
end
|
8
|
+
|
9
|
+
test "builds and writes fragment when fragment does not exist" do
|
10
|
+
key = ['test']
|
11
|
+
@view.expects(:read_stale_fragment).with(key).returns(nil)
|
12
|
+
@view.expects(:write_stale_fragment).with(key, 'perform')
|
13
|
+
|
14
|
+
@view.stale(key) {
|
15
|
+
'perform'
|
16
|
+
}
|
17
|
+
assert_equal 'perform', @view.output_buffer
|
18
|
+
end
|
19
|
+
|
20
|
+
test "does not build fragment when fragment exists" do
|
21
|
+
key = ['test']
|
22
|
+
@view.expects(:read_stale_fragment).with(key).returns('test')
|
23
|
+
@view.expects(:write_stale_fragment).never
|
24
|
+
|
25
|
+
assert_nothing_thrown {
|
26
|
+
@view.stale(key) {
|
27
|
+
throw :perform
|
28
|
+
}
|
29
|
+
}
|
30
|
+
assert_equal 'test', @view.output_buffer
|
31
|
+
end
|
32
|
+
|
33
|
+
test "builds fragment without reading when caching is disabled" do
|
34
|
+
key = ['test']
|
35
|
+
@view.controller.perform_caching = false
|
36
|
+
@view.expects(:read_stale_fragment).never
|
37
|
+
@view.expects(:write_stale_fragment).never
|
38
|
+
|
39
|
+
@view.stale(key) {
|
40
|
+
'perform'
|
41
|
+
}
|
42
|
+
assert_equal 'perform', @view.output_buffer
|
43
|
+
end
|
44
|
+
|
45
|
+
test "registers dependencies for the converted key" do
|
46
|
+
key = [:test]
|
47
|
+
Stale.interface.expects(:key_for_parameters).with(key, @view.controller).returns(['test'])
|
48
|
+
Stale.interface.expects(:register_dependencies).with(['test'])
|
49
|
+
@view.expects(:read_stale_fragment).with(['test']).returns('test')
|
50
|
+
|
51
|
+
@view.stale(key) {
|
52
|
+
nil
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stale
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Norbert Crombach
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.2'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.2'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: mocha
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.12.1
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.12.1
|
46
|
+
description:
|
47
|
+
email:
|
48
|
+
- norbert.crombach@primetheory.org
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- .travis.yml
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- lib/stale.rb
|
60
|
+
- lib/stale/cache.rb
|
61
|
+
- lib/stale/configuration.rb
|
62
|
+
- lib/stale/controller.rb
|
63
|
+
- lib/stale/dependencies.rb
|
64
|
+
- lib/stale/fragments.rb
|
65
|
+
- lib/stale/interface.rb
|
66
|
+
- lib/stale/model.rb
|
67
|
+
- lib/stale/parameters.rb
|
68
|
+
- lib/stale/version.rb
|
69
|
+
- lib/stale/view.rb
|
70
|
+
- stale.gemspec
|
71
|
+
- test/cache_test.rb
|
72
|
+
- test/configuration_test.rb
|
73
|
+
- test/controller_test.rb
|
74
|
+
- test/dependencies_test.rb
|
75
|
+
- test/fragments_test.rb
|
76
|
+
- test/interface_test.rb
|
77
|
+
- test/model_test.rb
|
78
|
+
- test/parameters_test.rb
|
79
|
+
- test/test_helper.rb
|
80
|
+
- test/view_test.rb
|
81
|
+
homepage: http://github.com/norbert/stale
|
82
|
+
licenses: []
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project: stale
|
101
|
+
rubygems_version: 1.8.23
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: Experimental fragment caching layer.
|
105
|
+
test_files:
|
106
|
+
- test/cache_test.rb
|
107
|
+
- test/configuration_test.rb
|
108
|
+
- test/controller_test.rb
|
109
|
+
- test/dependencies_test.rb
|
110
|
+
- test/fragments_test.rb
|
111
|
+
- test/interface_test.rb
|
112
|
+
- test/model_test.rb
|
113
|
+
- test/parameters_test.rb
|
114
|
+
- test/test_helper.rb
|
115
|
+
- test/view_test.rb
|