infopark_component_cache 1.0.0

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 ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in infopark_component_cache.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Infopark's ComponentCache
2
+
3
+ Fragment caching with automatic dependency resolution and cache invalidation.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'infopark_component_cache'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install infopark_component_cache
18
+
19
+ ## Usage
20
+
21
+ Set up Rails catching (see [http://guides.rubyonrails.org/caching_with_rails.html](http://guides.rubyonrails.org/caching_with_rails.html)) and
22
+ in your view write:
23
+
24
+ <%= cache_tagged_component(@rails_connector_obj, 'any-string-as-component-name', {:additional => :parameters}) do %>
25
+ This content will be cached
26
+ <% end %>
27
+
28
+ and you are done.
29
+
30
+ Alternatively you could use component cache directly:
31
+
32
+ <%= InfoparkComponentCache::ComponentCache.new(@rails_connector_obj, 'any-string-as-component-name', {:additional => :parameters}).fetch do %>
33
+ This content will be cached
34
+ <% end %>
35
+
36
+ ## Contributing
37
+
38
+ 1. Fork it
39
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
40
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
41
+ 4. Push to the branch (`git push origin my-new-feature`)
42
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ module InfoparkComponentCacheHelper
2
+ def cache_tagged_component(obj, component, params={}, &block)
3
+ InfoparkComponentCache::ComponentCache.new(obj, component, params).fetch do
4
+ capture(&block)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'infopark_component_cache/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "infopark_component_cache"
8
+ gem.version = InfoparkComponentCache::VERSION
9
+ gem.authors = ["Tomasz Przedmojski"]
10
+ gem.email = ["tomasz.przedmojski@infopark.de"]
11
+ gem.description = %q{Easy and intelligent fragment caching for RailsConnector projects}
12
+ gem.summary = %q{Fragment caching with automatic dependency resolution and cache invalidation.}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'rails'
21
+
22
+ gem.add_dependency 'infopark_rails_connector'
23
+ end
data/lib/engine.rb ADDED
@@ -0,0 +1,14 @@
1
+ module InfoparkComponentCache
2
+
3
+ class Engine < Rails::Engine
4
+
5
+ initializer "component_cache.helpers" do
6
+ [
7
+ InfoparkComponentCacheHelper
8
+ ].each do |helper|
9
+ ActionView::Base.__send__( :include, helper)
10
+ end
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,21 @@
1
+ require "infopark_component_cache/version"
2
+
3
+ #require "infopark_component_cache/guards/always_consistent"
4
+ #require "infopark_component_cache/guards/never_consistent"
5
+ require "infopark_component_cache/guards/value_present"
6
+ require "infopark_component_cache/guards/last_changed"
7
+ require "infopark_component_cache/guards/obj_count"
8
+ require "infopark_component_cache/guards/valid_from"
9
+ require "infopark_component_cache/guards/valid_until"
10
+
11
+ require "infopark_component_cache/key_generator"
12
+ require "infopark_component_cache/consistency_guard"
13
+ require "infopark_component_cache/cache_storage"
14
+ require "infopark_component_cache/component"
15
+ require "infopark_component_cache/component_cache"
16
+
17
+ module InfoparkComponentCache
18
+
19
+ end
20
+
21
+ require File.expand_path('../engine', __FILE__) if defined?(Rails)
@@ -0,0 +1,30 @@
1
+ require 'singleton'
2
+
3
+ module InfoparkComponentCache
4
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
5
+ #
6
+ # Trivial wrapper around underlying cache storages.
7
+ # This class or any other class that inherits it *must*
8
+ # respect Rails.application.config.action_controller.perform_caching
9
+ # setting.
10
+ class CacheStorage
11
+ include Singleton
12
+
13
+ def exist?(key)
14
+ enabled? && Rails.cache.exist?(key)
15
+ end
16
+
17
+ def read(key)
18
+ Rails.cache.read(key) # you can still read the cache if you wish...
19
+ end
20
+
21
+ def write(key, value)
22
+ Rails.cache.write(key, value) if enabled?
23
+ end
24
+
25
+ # @private
26
+ def enabled?
27
+ Rails.application.config.action_controller.perform_caching
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ require 'infopark_component_cache/key_generator'
2
+
3
+ module InfoparkComponentCache
4
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
5
+ #
6
+ # Cache entity (component) consists of an obj, a name
7
+ # and some parameters (hash). It should be used to point
8
+ # to some data stored in a particular context
9
+ class Component
10
+ attr_reader :obj, :name, :params
11
+
12
+ def initialize(obj, component, params={})
13
+ @obj, @component, @params = obj, component, params
14
+ end
15
+
16
+ def cache_key(meta_prefix=nil)
17
+ if meta_prefix
18
+ meta_prefix + "_" + KeyGenerator.generate_key(identity_hash)
19
+ else
20
+ KeyGenerator.generate_key(identity_hash)
21
+ end
22
+ end
23
+
24
+ def identity_hash
25
+ @params.merge({:obj_name=>@obj.name, :obj_id=>@obj.id, :obj_component=>@component})
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,103 @@
1
+ require 'infopark_component_cache/component'
2
+ require 'infopark_component_cache/cache_storage'
3
+
4
+ require 'infopark_component_cache/guards/value_present'
5
+ require 'infopark_component_cache/guards/last_changed'
6
+ require 'infopark_component_cache/guards/obj_count'
7
+ require 'infopark_component_cache/guards/valid_from'
8
+ require 'infopark_component_cache/guards/valid_until'
9
+
10
+ module InfoparkComponentCache
11
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
12
+ # This class provides user-level access to component cache.
13
+ #
14
+ # @example Do some expensive computation on a page
15
+ # InfoparkComponentCache::ComponentCache.new(@obj, 'processed_body', :page => 14) do
16
+ # expensive_process_body @obj.body
17
+ # end
18
+ #
19
+ # By default ComponentCache comes with a set of Guards, i.e.
20
+ # classes that check the consistency of cache and invalidate it,
21
+ # should it be neccesary.
22
+ # @see ComponentCache#initialize
23
+ class ComponentCache
24
+ attr_reader :component, :guards
25
+
26
+ # First three parameters are used to construct
27
+ # the component
28
+ # @see Component
29
+ #
30
+ # @param [Array<Class>] guards list of guard
31
+ # classes used when deciding whether cache is valid
32
+ # when left empty the default set is used:
33
+ # @see Guard::ValuePresent
34
+ # @see Guard::LastChanged
35
+ # @see Guard::ObjCount
36
+ def initialize(obj, name, params={}, guards=[])
37
+ @component = Component.new(obj, name, params)
38
+ if guards.empty?
39
+ @guards = [
40
+ Guards::ValuePresent.new(@component),
41
+ Guards::LastChanged.new(@component),
42
+ Guards::ObjCount.new(@component),
43
+ Guards::ValidFrom.new(@component),
44
+ Guards::ValidUntil.new(@component)
45
+ ]
46
+ else
47
+ @guards = guards.map do |klass|
48
+ klass.new(@component)
49
+ end
50
+ end
51
+ end
52
+
53
+ # Checks if cache is valid (in consistent state).
54
+ # It delegates work to specified #guards
55
+ #
56
+ # For any unexpected case it returns true.
57
+ #
58
+ # @return true if cache is valid and in consistent state
59
+ def expired?
60
+ return !guards.all?(&:consistent?)
61
+
62
+ rescue => exception
63
+ raise exception if Rails.env.test?
64
+ return true
65
+ end
66
+
67
+ # Checks if the cache is in consistent state and cached value
68
+ # can be returned.
69
+ # In such case it returns cached value.
70
+ # Otherwise it evaluates passed block and updates the cache.
71
+ #
72
+ # @see {#expired?}
73
+ # @yieldreturn value to be used in case of cache miss
74
+ # @return cached value or the return value of the block
75
+ def fetch(&block)
76
+ if expired?
77
+ value = yield
78
+ begin
79
+ cache.write(component.cache_key, value)
80
+ ensure_consistency!
81
+ return value
82
+
83
+ rescue => exception
84
+ raise exception if Rails.env.test?
85
+ return value
86
+ end
87
+ else
88
+ cache.read(component.cache_key)
89
+ end
90
+ end
91
+
92
+ # @private
93
+ # @return [CacheStorage] instance of CacheStorage to use
94
+ def cache
95
+ CacheStorage.instance
96
+ end
97
+
98
+ # @private
99
+ def ensure_consistency!
100
+ guards.each(&:guard!)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,44 @@
1
+ require 'infopark_component_cache/cache_storage'
2
+
3
+ module InfoparkComponentCache
4
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
5
+ #
6
+ # @abstract
7
+ # This is abstract base class for any Cache Guards.
8
+ # Any class inheriting ConsistencyGuard should implement #consistent?
9
+ # and #guard!
10
+ #
11
+ # Cache Guard is a class that promises some consistency, for example
12
+ # that no changes to the database accured between calls to #guard!
13
+ # and #consistent?
14
+ #
15
+ # This consistency is crucial for the function of cache, because
16
+ # inconsistent cache is (automatically) invalidated.
17
+ class ConsistencyGuard
18
+ attr_reader :component
19
+
20
+ def initialize(component)
21
+ @component = component
22
+ end
23
+
24
+ # @return [CacheStorage] instance of CacheStorage
25
+ def cache
26
+ CacheStorage.instance
27
+ end
28
+
29
+ # @abstract
30
+ # This method returns true if the consistenty guarded by this class
31
+ # is fulfilled and false otherwise.
32
+ def consistent?
33
+ raise TypeError, "Abstract method consistent? called"
34
+ end
35
+
36
+ # @abstract
37
+ # This method is called whenever cache is updated for the component.
38
+ # Any values checked by #consistent? can be persisted here. Use #{cache}
39
+ # for persistence but mind the possible inconsistencies.
40
+ def guard!
41
+ raise TypeError, "Abstract method guard! called"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ require 'infopark_component_cache/consistency_guard'
2
+
3
+ module InfoparkComponentCache
4
+ module Guards
5
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
6
+ #
7
+ # This is a dummy Guard, mostly for tests purposes.
8
+ # It ensures that the cache is always consistent (provided
9
+ # other guards are also consistent!)
10
+ class AlwaysConsistent < ConsistencyGuard
11
+ def consistent?
12
+ true
13
+ end
14
+
15
+ def guard!
16
+ # noop
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ require 'infopark_component_cache/consistency_guard'
2
+
3
+ module InfoparkComponentCache
4
+ module Guards
5
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
6
+ #
7
+ # This Guard class ensures that the objects in database do not change inbetween.
8
+ # (It caches the newest timestamp and compares it to the current value)
9
+ class LastChanged < ConsistencyGuard
10
+ def consistent?
11
+ last_changed_known? && no_changes_since?
12
+ end
13
+
14
+ def guard!
15
+ cache.write(cache_key, current_last_changed)
16
+ end
17
+
18
+ # @return true if a timestamp can be read from cache with {#cache_key}
19
+ def last_changed_known?
20
+ cache.exist?(cache_key) && cache.read(cache_key).kind_of?(Time)
21
+ end
22
+
23
+ # @return true if no obj has been changed since last {#guard!}
24
+ def no_changes_since?
25
+ current_last_changed <= cache.read(cache_key)
26
+ end
27
+
28
+ # @return [String] the cache key for storing {#current_last_changed}
29
+ def cache_key
30
+ component.cache_key('last_changed')
31
+ end
32
+
33
+ # @return [Time] the timestamp of the most recent change to any Obj
34
+ def current_last_changed
35
+ RailsConnector::DateAttribute.parse(RailsConnector::Obj.maximum(:last_changed))
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,19 @@
1
+ require 'infopark_component_cache/consistency_guard'
2
+
3
+ module InfoparkComponentCache
4
+ module Guards
5
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
6
+ #
7
+ # This is a dummy Guard, mostly for tests purposes.
8
+ # It ensures that the cache is never consistent
9
+ class NeverConsistent < ConsistencyGuard
10
+ def consistent?
11
+ false
12
+ end
13
+
14
+ def guard!
15
+ # noop
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ require 'infopark_component_cache/consistency_guard'
2
+
3
+ module InfoparkComponentCache
4
+ module Guards
5
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
6
+ #
7
+ # This Guard class ensures that the total count of Objs in the database
8
+ # does not change inbetween. (It caches the number of Objs and compares
9
+ # it to the current count)
10
+ class ObjCount < ConsistencyGuard
11
+ def consistent?
12
+ count_known? && no_changes_since?
13
+ end
14
+
15
+ def guard!
16
+ cache.write(cache_key, current_count)
17
+ end
18
+
19
+ # @return true if obj count can be read from cache with {#cache_key}
20
+ def count_known?
21
+ cache.exist?(cache_key) && cache.read(cache_key).kind_of?(Fixnum)
22
+ end
23
+
24
+ # @return true if no obj has been deleted or added since last {#guard!}
25
+ def no_changes_since?
26
+ current_count == cache.read(cache_key)
27
+ end
28
+
29
+ # @return [String] the cache key for storing {#current_count}
30
+ def cache_key
31
+ component.cache_key('obj_count')
32
+ end
33
+
34
+ # @return [Fixnum] the number of Objs in the database
35
+ def current_count
36
+ RailsConnector::Obj.count
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,48 @@
1
+ require 'infopark_component_cache/consistency_guard'
2
+
3
+ module InfoparkComponentCache
4
+ module Guards
5
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
6
+ #
7
+ # This guard ensures that any object that becomes valid (through valid_from)
8
+ # will cause inconsistency
9
+ class ValidFrom < ConsistencyGuard
10
+ def consistent?
11
+ if min_valid_from_known?
12
+ return no_changes_since?
13
+ else
14
+ return current_min_valid_from.nil?
15
+ end
16
+ end
17
+
18
+ def guard!
19
+ cache.write(cache_key, current_min_valid_from)
20
+ end
21
+
22
+ # @return true if a timestamp can be read from cache with {#cache_key}
23
+ def min_valid_from_known?
24
+ cache.exist?(cache_key) && cache.read(cache_key).kind_of?(Time)
25
+ end
26
+
27
+ # @return true if no obj has been changed since last {#guard!}
28
+ def no_changes_since?
29
+ current_min_valid_from == cache.read(cache_key) && current_min_valid_from > Time.now
30
+ end
31
+
32
+ # @return [String] the cache key for storing {#current_min_valid_from}
33
+ def cache_key
34
+ component.cache_key('min_valid_from')
35
+ end
36
+
37
+ # @return [Time] the timestamp of the most recent valid_from, or nil if none found
38
+ def current_min_valid_from
39
+ str_value = RailsConnector::Obj.where('valid_from > ?', Time.now.to_iso).minimum(:valid_from)
40
+ if str_value.present?
41
+ RailsConnector::DateAttribute.parse(str_value)
42
+ else
43
+ nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ require 'infopark_component_cache/consistency_guard'
2
+
3
+ module InfoparkComponentCache
4
+ module Guards
5
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
6
+ #
7
+ # This guard ensures that any object, whose valid until date has been passed
8
+ # will cause inconsistency.
9
+ class ValidUntil < ConsistencyGuard
10
+ def consistent?
11
+ if min_valid_until_known?
12
+ return no_changes_since?
13
+ else
14
+ return current_min_valid_until.nil?
15
+ end
16
+ end
17
+
18
+ def guard!
19
+ cache.write(cache_key, current_min_valid_until)
20
+ end
21
+
22
+ # @return true if a timestamp can be read from cache with {#cache_key}
23
+ def min_valid_until_known?
24
+ cache.exist?(cache_key) && cache.read(cache_key).kind_of?(Time)
25
+ end
26
+
27
+ # @return true if no obj has been changed since last {#guard!}
28
+ def no_changes_since?
29
+ current_min_valid_until == cache.read(cache_key) && current_min_valid_until > Time.now
30
+ end
31
+
32
+ # @return [String] the cache key for storing {#current_min_valid_until}
33
+ def cache_key
34
+ component.cache_key('min_valid_until')
35
+ end
36
+
37
+ # @return [Time] the timestamp of the the object that will be deactivated in nearest future
38
+ def current_min_valid_until
39
+ str_value = RailsConnector::Obj.where('valid_until > ?', Time.now.to_iso).minimum(:valid_until)
40
+ if str_value.present?
41
+ RailsConnector::DateAttribute.parse(str_value)
42
+ else
43
+ nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,20 @@
1
+ require 'infopark_component_cache/consistency_guard'
2
+
3
+ module InfoparkComponentCache
4
+ module Guards
5
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
6
+ #
7
+ # This Guard class ensures that the cache object
8
+ # for the component exists, i.e. when reading
9
+ # the cache with component's key one does get a value
10
+ class ValuePresent < ConsistencyGuard
11
+ def consistent?
12
+ cache.exist?(component.cache_key)
13
+ end
14
+
15
+ def guard!
16
+ # noop
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ require 'digest/sha2'
2
+
3
+ module InfoparkComponentCache
4
+ module KeyGenerator
5
+ # @author Tomasz Przedmojski <tomasz.przedmojski@infopark.de>
6
+ #
7
+ # Generates a key identifing an Object
8
+ #
9
+ # @param [#cache_key, #to_param] anything an Object that responds to #cache_key.
10
+ # It is assumed that calls to #cache_key produce consistent and deterministic
11
+ # results. Futhermore for no two distinct objects should their #cache_key be equal
12
+ # @return [String] string that does not contain file-system insecure characters (\, / etc.)
13
+ def self.generate_key(anything)
14
+ encode_key(Rails.cache.send(:expanded_key, anything))
15
+ end
16
+
17
+ # Encodes a provided key yielding a string that only consists of
18
+ # alphanumeric characters of limited length. Underlying implementation
19
+ # uses some kind of hashing algorithm and therefore has the same
20
+ # characteristics: equal inputs yield equal outputs, but different
21
+ # inputs can yield same outputs (although it is very very unlikely)
22
+ #
23
+ # @param [String] string input string to be encoded
24
+ # @return [String] string that is guaranteed to
25
+ # consist only of alphanumeric characters,
26
+ # and not to exceed 100 characters
27
+ def self.encode_key(string) #:nodoc:
28
+ Digest::SHA2.hexdigest(string)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module InfoparkComponentCache
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: infopark_component_cache
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Tomasz Przedmojski
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-12-14 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rails
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: infopark_rails_connector
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ description: Easy and intelligent fragment caching for RailsConnector projects
50
+ email:
51
+ - tomasz.przedmojski@infopark.de
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - .gitignore
60
+ - Gemfile
61
+ - LICENSE.txt
62
+ - README.md
63
+ - Rakefile
64
+ - app/helpers/infopark_component_cache_helper.rb
65
+ - infopark_component_cache.gemspec
66
+ - lib/engine.rb
67
+ - lib/infopark_component_cache.rb
68
+ - lib/infopark_component_cache/cache_storage.rb
69
+ - lib/infopark_component_cache/component.rb
70
+ - lib/infopark_component_cache/component_cache.rb
71
+ - lib/infopark_component_cache/consistency_guard.rb
72
+ - lib/infopark_component_cache/guards/always_consistent.rb
73
+ - lib/infopark_component_cache/guards/last_changed.rb
74
+ - lib/infopark_component_cache/guards/never_consistent.rb
75
+ - lib/infopark_component_cache/guards/obj_count.rb
76
+ - lib/infopark_component_cache/guards/valid_from.rb
77
+ - lib/infopark_component_cache/guards/valid_until.rb
78
+ - lib/infopark_component_cache/guards/value_present.rb
79
+ - lib/infopark_component_cache/key_generator.rb
80
+ - lib/infopark_component_cache/version.rb
81
+ has_rdoc: true
82
+ homepage: ""
83
+ licenses: []
84
+
85
+ post_install_message:
86
+ rdoc_options: []
87
+
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 3
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ requirements: []
109
+
110
+ rubyforge_project:
111
+ rubygems_version: 1.5.2
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Fragment caching with automatic dependency resolution and cache invalidation.
115
+ test_files: []
116
+