stale 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.8.7
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'dalli', :require => false
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.
@@ -0,0 +1,7 @@
1
+ # Stale
2
+
3
+ Experimental fragment caching layer for Rails.
4
+
5
+ ## Resources
6
+
7
+ This library is heavily influenced by [Interlock](https://github.com/evan/interlock) and [Primer](https://github.com/jcoglan/primer).
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << "test"
7
+ t.pattern = 'test/*_test.rb'
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -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'
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Stale
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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