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.
@@ -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