cache-machine 0.1.6c

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ # test database
2
+ db
3
+
4
+ # rcov generated
5
+ coverage
6
+
7
+ # rdoc generated
8
+ rdoc
9
+
10
+ # yard generated
11
+ doc
12
+ .yardoc
13
+
14
+ # bundler
15
+ .bundle
16
+
17
+ # jeweler generated
18
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rails"
4
+
5
+ group :development do
6
+ gem "bundler", "~> 1.0.0"
7
+ gem "jeweler", "~> 1.6.2"
8
+ end
9
+
10
+ group :test do
11
+ gem "rcov", ">= 0"
12
+ gem 'rspec'
13
+ gem 'rspec-rails'
14
+ gem "sqlite3-ruby"
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,102 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ abstract (1.0.0)
5
+ actionmailer (3.0.9)
6
+ actionpack (= 3.0.9)
7
+ mail (~> 2.2.19)
8
+ actionpack (3.0.9)
9
+ activemodel (= 3.0.9)
10
+ activesupport (= 3.0.9)
11
+ builder (~> 2.1.2)
12
+ erubis (~> 2.6.6)
13
+ i18n (~> 0.5.0)
14
+ rack (~> 1.2.1)
15
+ rack-mount (~> 0.6.14)
16
+ rack-test (~> 0.5.7)
17
+ tzinfo (~> 0.3.23)
18
+ activemodel (3.0.9)
19
+ activesupport (= 3.0.9)
20
+ builder (~> 2.1.2)
21
+ i18n (~> 0.5.0)
22
+ activerecord (3.0.9)
23
+ activemodel (= 3.0.9)
24
+ activesupport (= 3.0.9)
25
+ arel (~> 2.0.10)
26
+ tzinfo (~> 0.3.23)
27
+ activeresource (3.0.9)
28
+ activemodel (= 3.0.9)
29
+ activesupport (= 3.0.9)
30
+ activesupport (3.0.9)
31
+ arel (2.0.10)
32
+ builder (2.1.2)
33
+ diff-lcs (1.1.2)
34
+ erubis (2.6.6)
35
+ abstract (>= 1.0.0)
36
+ git (1.2.5)
37
+ i18n (0.5.0)
38
+ jeweler (1.6.2)
39
+ bundler (~> 1.0)
40
+ git (>= 1.2.5)
41
+ rake
42
+ mail (2.2.19)
43
+ activesupport (>= 2.3.6)
44
+ i18n (>= 0.4.0)
45
+ mime-types (~> 1.16)
46
+ treetop (~> 1.4.8)
47
+ mime-types (1.16)
48
+ polyglot (0.3.1)
49
+ rack (1.2.3)
50
+ rack-mount (0.6.14)
51
+ rack (>= 1.0.0)
52
+ rack-test (0.5.7)
53
+ rack (>= 1.0)
54
+ rails (3.0.9)
55
+ actionmailer (= 3.0.9)
56
+ actionpack (= 3.0.9)
57
+ activerecord (= 3.0.9)
58
+ activeresource (= 3.0.9)
59
+ activesupport (= 3.0.9)
60
+ bundler (~> 1.0)
61
+ railties (= 3.0.9)
62
+ railties (3.0.9)
63
+ actionpack (= 3.0.9)
64
+ activesupport (= 3.0.9)
65
+ rake (>= 0.8.7)
66
+ rdoc (~> 3.4)
67
+ thor (~> 0.14.4)
68
+ rake (0.9.2)
69
+ rcov (0.9.9)
70
+ rdoc (3.6.1)
71
+ rspec (2.6.0)
72
+ rspec-core (~> 2.6.0)
73
+ rspec-expectations (~> 2.6.0)
74
+ rspec-mocks (~> 2.6.0)
75
+ rspec-core (2.6.4)
76
+ rspec-expectations (2.6.0)
77
+ diff-lcs (~> 1.1.2)
78
+ rspec-mocks (2.6.0)
79
+ rspec-rails (2.6.1)
80
+ actionpack (~> 3.0)
81
+ activesupport (~> 3.0)
82
+ railties (~> 3.0)
83
+ rspec (~> 2.6.0)
84
+ sqlite3 (1.3.3)
85
+ sqlite3-ruby (1.3.3)
86
+ sqlite3 (>= 1.3.3)
87
+ thor (0.14.6)
88
+ treetop (1.4.9)
89
+ polyglot (>= 0.3.1)
90
+ tzinfo (0.3.28)
91
+
92
+ PLATFORMS
93
+ ruby
94
+
95
+ DEPENDENCIES
96
+ bundler (~> 1.0.0)
97
+ jeweler (~> 1.6.2)
98
+ rails
99
+ rcov
100
+ rspec
101
+ rspec-rails
102
+ sqlite3-ruby
data/LICENSE.txt ADDED
@@ -0,0 +1,175 @@
1
+ Copyright (c) 2011 PartyEarth LLC
2
+
3
+ GNU LESSER GENERAL PUBLIC LICENSE
4
+ Version 3, 29 June 2007
5
+
6
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
7
+ Everyone is permitted to copy and distribute verbatim copies
8
+ of this license document, but changing it is not allowed.
9
+
10
+
11
+ This version of the GNU Lesser General Public License incorporates
12
+ the terms and conditions of version 3 of the GNU General Public
13
+ License, supplemented by the additional permissions listed below.
14
+
15
+ 0. Additional Definitions.
16
+
17
+ As used herein, "this License" refers to version 3 of the GNU Lesser
18
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
19
+ General Public License.
20
+
21
+ "The Library" refers to a covered work governed by this License,
22
+ other than an Application or a Combined Work as defined below.
23
+
24
+ An "Application" is any work that makes use of an interface provided
25
+ by the Library, but which is not otherwise based on the Library.
26
+ Defining a subclass of a class defined by the Library is deemed a mode
27
+ of using an interface provided by the Library.
28
+
29
+ A "Combined Work" is a work produced by combining or linking an
30
+ Application with the Library. The particular version of the Library
31
+ with which the Combined Work was made is also called the "Linked
32
+ Version".
33
+
34
+ The "Minimal Corresponding Source" for a Combined Work means the
35
+ Corresponding Source for the Combined Work, excluding any source code
36
+ for portions of the Combined Work that, considered in isolation, are
37
+ based on the Application, and not on the Linked Version.
38
+
39
+ The "Corresponding Application Code" for a Combined Work means the
40
+ object code and/or source code for the Application, including any data
41
+ and utility programs needed for reproducing the Combined Work from the
42
+ Application, but excluding the System Libraries of the Combined Work.
43
+
44
+ 1. Exception to Section 3 of the GNU GPL.
45
+
46
+ You may convey a covered work under sections 3 and 4 of this License
47
+ without being bound by section 3 of the GNU GPL.
48
+
49
+ 2. Conveying Modified Versions.
50
+
51
+ If you modify a copy of the Library, and, in your modifications, a
52
+ facility refers to a function or data to be supplied by an Application
53
+ that uses the facility (other than as an argument passed when the
54
+ facility is invoked), then you may convey a copy of the modified
55
+ version:
56
+
57
+ a) under this License, provided that you make a good faith effort to
58
+ ensure that, in the event an Application does not supply the
59
+ function or data, the facility still operates, and performs
60
+ whatever part of its purpose remains meaningful, or
61
+
62
+ b) under the GNU GPL, with none of the additional permissions of
63
+ this License applicable to that copy.
64
+
65
+ 3. Object Code Incorporating Material from Library Header Files.
66
+
67
+ The object code form of an Application may incorporate material from
68
+ a header file that is part of the Library. You may convey such object
69
+ code under terms of your choice, provided that, if the incorporated
70
+ material is not limited to numerical parameters, data structure
71
+ layouts and accessors, or small macros, inline functions and templates
72
+ (ten or fewer lines in length), you do both of the following:
73
+
74
+ a) Give prominent notice with each copy of the object code that the
75
+ Library is used in it and that the Library and its use are
76
+ covered by this License.
77
+
78
+ b) Accompany the object code with a copy of the GNU GPL and this license
79
+ document.
80
+
81
+ 4. Combined Works.
82
+
83
+ You may convey a Combined Work under terms of your choice that,
84
+ taken together, effectively do not restrict modification of the
85
+ portions of the Library contained in the Combined Work and reverse
86
+ engineering for debugging such modifications, if you also do each of
87
+ the following:
88
+
89
+ a) Give prominent notice with each copy of the Combined Work that
90
+ the Library is used in it and that the Library and its use are
91
+ covered by this License.
92
+
93
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
94
+ document.
95
+
96
+ c) For a Combined Work that displays copyright notices during
97
+ execution, include the copyright notice for the Library among
98
+ these notices, as well as a reference directing the user to the
99
+ copies of the GNU GPL and this license document.
100
+
101
+ d) Do one of the following:
102
+
103
+ 0) Convey the Minimal Corresponding Source under the terms of this
104
+ License, and the Corresponding Application Code in a form
105
+ suitable for, and under terms that permit, the user to
106
+ recombine or relink the Application with a modified version of
107
+ the Linked Version to produce a modified Combined Work, in the
108
+ manner specified by section 6 of the GNU GPL for conveying
109
+ Corresponding Source.
110
+
111
+ 1) Use a suitable shared library mechanism for linking with the
112
+ Library. A suitable mechanism is one that (a) uses at run time
113
+ a copy of the Library already present on the user's computer
114
+ system, and (b) will operate properly with a modified version
115
+ of the Library that is interface-compatible with the Linked
116
+ Version.
117
+
118
+ e) Provide Installation Information, but only if you would otherwise
119
+ be required to provide such information under section 6 of the
120
+ GNU GPL, and only to the extent that such information is
121
+ necessary to install and execute a modified version of the
122
+ Combined Work produced by recombining or relinking the
123
+ Application with a modified version of the Linked Version. (If
124
+ you use option 4d0, the Installation Information must accompany
125
+ the Minimal Corresponding Source and Corresponding Application
126
+ Code. If you use option 4d1, you must provide the Installation
127
+ Information in the manner specified by section 6 of the GNU GPL
128
+ for conveying Corresponding Source.)
129
+
130
+ 5. Combined Libraries.
131
+
132
+ You may place library facilities that are a work based on the
133
+ Library side by side in a single library together with other library
134
+ facilities that are not Applications and are not covered by this
135
+ License, and convey such a combined library under terms of your
136
+ choice, if you do both of the following:
137
+
138
+ a) Accompany the combined library with a copy of the same work based
139
+ on the Library, uncombined with any other library facilities,
140
+ conveyed under the terms of this License.
141
+
142
+ b) Give prominent notice with the combined library that part of it
143
+ is a work based on the Library, and explaining where to find the
144
+ accompanying uncombined form of the same work.
145
+
146
+ 6. Revised Versions of the GNU Lesser General Public License.
147
+
148
+ The Free Software Foundation may publish revised and/or new versions
149
+ of the GNU Lesser General Public License from time to time. Such new
150
+ versions will be similar in spirit to the present version, but may
151
+ differ in detail to address new problems or concerns.
152
+
153
+ Each version is given a distinguishing version number. If the
154
+ Library as you received it specifies that a certain numbered version
155
+ of the GNU Lesser General Public License "or any later version"
156
+ applies to it, you have the option of following the terms and
157
+ conditions either of that published version or of any later version
158
+ published by the Free Software Foundation. If the Library as you
159
+ received it does not specify a version number of the GNU Lesser
160
+ General Public License, you may choose any version of the GNU Lesser
161
+ General Public License ever published by the Free Software Foundation.
162
+
163
+ If the Library as you received it specifies that a proxy can decide
164
+ whether future versions of the GNU Lesser General Public License shall
165
+ apply, that proxy's public statement of acceptance of any version is
166
+ permanent authorization for you to choose that version for the
167
+ Library.
168
+
169
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
170
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
171
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
172
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
173
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
174
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
175
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,137 @@
1
+ = cache-machine
2
+
3
+ An ActiveRecord mixin that helps managing cached content in a Ruby on Rails application with complex data update dependencies.
4
+
5
+ Cache Machine provides
6
+ * high-level methods for accessing cached content using page names, numbers, time stamps etc,
7
+ * a DSL to describe update dependencies between the data models underlying the cached content,
8
+ * automatic cache invalidation based on those explicitly modeled data update dependencies.
9
+
10
+ You will find Cache Machine useful if you:
11
+ * use Memcache to cache fragments of a web site that contain data from a variety of underlying data models
12
+ * anytime one of the underlying data models changes, all the cached page fragments in which this data model occurs - and only those - need to be invalidated/updated
13
+ * you have many data models, cached fragments, and many data models used inside each cached fragment
14
+
15
+ Cache Machine depends only on the Rails.cache API and is thereby library agnostic.
16
+
17
+ = Usage
18
+
19
+ # Fetch cache of venues collection on model_instance.
20
+ @neighborhood.fetch_cache_of(:venues) { @neighborhood.venues.to_html }
21
+
22
+ # Specify format.
23
+ @neighborhood.fetch_cache_of(:venues, :format => :json) { @neighborhood.venues.to_json }
24
+
25
+ # Paginated content.
26
+ @neighborhood.fetch_cache_of(:venues, :format => :json, :page => 2) { @neighborhood.venues.to_json }
27
+
28
+ In you target model define <b>cache map</b>:
29
+
30
+ acts_as_cache_machine_for :venues => [:hotspots, :today_events],
31
+ :cities => [:venues],
32
+ :streets => :hotspots,
33
+ :users
34
+
35
+ This example shows you how changes of one collection affect on invalidation process.
36
+ For each record of your target model:
37
+ - Cache for <b>users</b> collection associated with object of your target model is invalidated when changing (_add_, _remove_, _update_) the _users_ collection
38
+ - Cache for <b>venues</b> collection associated with object of your target model is invalidated when changing the _venues_ collection associated with that object
39
+ - Cache for <b>venues</b> collection associated with object of your target model is invalidated when changing the _cities_ collection. In this case machine automatically invalidates _hotspots_ and _today_events_
40
+ - Cache for <b>cities</b> collection associated with object of your target model is invalidated when changing the _cities_ collection
41
+ - Cache for <b>hotspots</b> collection is invalidated when changing the _venues_ collection
42
+ - Cache for <b>hotspots</b> collection is invalidated when changing the _streets_ collection
43
+ - Cache for <b>hotspots</b> collection is invalidated when changing the _cities_ collection
44
+ - Cache for <b>today_events</b> collection is invalidated when changing the _venues_ collection
45
+ - Cache for <b>today_events</b> collection is invalidated when changing the _cities_ collection
46
+ <b>Cache map may contain any name, whatever you want. But invalidation process starts only when ActiveRecord collection is changed.</b>
47
+
48
+ == Custom cache invalidation
49
+
50
+ === Using timestamps
51
+ Suppose you need to reset cache of _schedule_of_events_ every day.
52
+
53
+ @lady_gaga.fetch_cache_of :schedule_of_events, :timestamp => lambda { Date.today } do
54
+ @lady_gaga.events.where(:date.gt => Date.today)
55
+ end
56
+
57
+ === Using Cache Machine timestamps
58
+ Suppose you need to reset cache of _tweets_ every 10 minutes.
59
+
60
+ class LadyGagaPerformer < ActiveRecord::Base
61
+ define_timestamp :tweets_timestamp, :expires_in => 10.minutes
62
+ end
63
+
64
+ #...
65
+
66
+ # Somewhere
67
+ @lady_gaga.fetch_cache_of :tweets, :timestamp => :tweets_timestamp do
68
+ TwitterApi.fetch_tweets_for @lady_gaga
69
+ end
70
+
71
+ Suppose you need custom timestamp value.
72
+
73
+ class LadyGagaPerformer < ActiveRecord::Base
74
+ define_timestamp(:tweets_timestamp) { Time.now.to_i + self.id }
75
+ end
76
+
77
+ #...
78
+
79
+ # Somewhere
80
+ @lady_gaga.fetch_cache_of :tweets, :timestamp => :tweets_timestamp do
81
+ TwitterApi.fetch_tweets_for @lady_gaga
82
+ end
83
+
84
+ === Using methods as timestamps
85
+ Suppose you have your own really custom cache key.
86
+
87
+ class LadyGagaPerformer < ActiveRecord::Base
88
+ def my_custom_cache_key
89
+ rand(100) + rand(1000) + rand(10000)
90
+ end
91
+ end
92
+
93
+ #...
94
+
95
+ # Somewere
96
+ @lady_gaga.fetch_cache_of(:something, :timestamp => :my_custom_cache_key) { '...' }
97
+
98
+ === Using class timestamps
99
+ Suppose you need to fetch cached content of one of your collections.
100
+ Rails.cache.fetch(MyModel.timestamped_key) { '...' }
101
+
102
+ Want to see collection timestamp?
103
+ MyModel.timestamp
104
+
105
+ === Manual cache invalidation
106
+ # For classes.
107
+ MyModel.reset_timestamp
108
+
109
+ # For collections.
110
+ @lady_gaga.delete_cache_of :events
111
+
112
+ # For timestamps.
113
+ @lady_gaga.reset_timestamp_of :events
114
+
115
+ == ActionView helper
116
+ From examples above:
117
+ <%= cache_for @lady_gaga, :updoming_events do %>
118
+ <p>Don't hide yourself in regret
119
+ Just love yourself and you're set</p>
120
+ <% end %>
121
+ <tt>cache_for</tt> method automatically sets EHTML format on cache key.
122
+
123
+ == Test
124
+
125
+ == Contributing to cache-machine
126
+
127
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
128
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
129
+ * Fork the project
130
+ * Start a feature/bugfix branch
131
+ * Commit and push until you are happy with your contribution
132
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
133
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
134
+
135
+ == Copyright
136
+
137
+ Copyright (c) 2011 PartyEarth LLC. See LICENSE.txt for details.
data/Rakefile ADDED
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+
3
+ ENV['BUNDLE_GEMFILE'] = 'Gemfile'
4
+
5
+ gem_root = File.expand_path(File.dirname(__FILE__))
6
+
7
+ require 'rubygems'
8
+ require 'bundler'
9
+ require 'rake'
10
+ require 'jeweler'
11
+ require 'rake'
12
+ require 'rake/testtask'
13
+ require 'rspec'
14
+ require 'rspec/core/rake_task'
15
+
16
+ begin
17
+ Bundler.setup(:default, :development)
18
+ rescue Bundler::BundlerError => e
19
+ $stderr.puts e.message
20
+ $stderr.puts "Run `bundle install` to install missing gems"
21
+ exit e.status_code
22
+ end
23
+
24
+ Jeweler::Tasks.new do |gem|
25
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
26
+ gem.name = "cache-machine"
27
+ gem.homepage = "http://github.com/partyearth/cache-machine"
28
+ gem.license = "MIT"
29
+ gem.summary = %Q{A Ruby on Rails framework to support cache management based on explicitely modeled caching dependencies.}
30
+ gem.description = %Q{Longer description of our gem}
31
+ gem.email = "kgoslar@partyearth.ru"
32
+ gem.authors = ["Kevin Goslar"]
33
+ gem.files = Dir.glob('lib/**/*.rb')
34
+ # dependencies defined in Gemfile
35
+ end
36
+ Jeweler::RubygemsDotOrgTasks.new
37
+
38
+ task :default => :spec
39
+
40
+ desc "Run the test suite"
41
+ task :spec => ['spec:setup', 'spec:lib', 'spec:cleanup']
42
+
43
+ namespace :spec do
44
+ desc "Setup the test environment"
45
+ task :setup do
46
+ system "cd #{gem_root} && bundle install && mkdir db"
47
+ end
48
+
49
+ desc "Cleanup the test environment"
50
+ task :cleanup do
51
+ FileUtils.rm_rf "#{gem_root}/db"
52
+ end
53
+
54
+ desc "Test the Cache Machine lib"
55
+ RSpec::Core::RakeTask.new(:lib) do |task|
56
+ task.pattern = gem_root + '/spec/lib/**/*_spec.rb'
57
+ end
58
+ end
59
+
60
+ require 'rdoc/task'
61
+ Rake::RDocTask.new do |rdoc|
62
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
63
+
64
+ rdoc.rdoc_dir = 'rdoc'
65
+ rdoc.title = "cache-machine #{version}"
66
+ rdoc.rdoc_files.include('README*')
67
+ rdoc.rdoc_files.include('lib/**/*.rb')
68
+ end
69
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.6b
@@ -0,0 +1,44 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{cache-machine}
8
+ s.version = "0.1.6c"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Kevin Goslar"]
12
+ s.date = %q{2011-06-30}
13
+ s.description = %q{A Ruby on Rails framework to support cache management based on explicitely modeled caching dependencies.}
14
+ s.email = %q{kgoslar@partyearth.ru}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = `git ls-files`.split("\n")
20
+ s.homepage = %q{http://github.com/partyearth/cache-machine}
21
+ s.licenses = ["MIT"]
22
+ s.require_paths = ["lib"]
23
+ s.rubygems_version = %q{1.6.2}
24
+ s.summary = %q{A Ruby on Rails framework to support cache management based on explicitely modeled caching dependencies.}
25
+
26
+ if s.respond_to? :specification_version then
27
+ s.specification_version = 3
28
+
29
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
30
+ s.add_runtime_dependency(%q<rails>, [">= 0"])
31
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
32
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
33
+ else
34
+ s.add_dependency(%q<rails>, [">= 0"])
35
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
36
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
37
+ end
38
+ else
39
+ s.add_dependency(%q<rails>, [">= 0"])
40
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
41
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
42
+ end
43
+ end
44
+
@@ -0,0 +1,10 @@
1
+ #--
2
+ # Copyright (c) 2011 {PartyEarth LLC}[http://partyearth.com]
3
+ # mailto:kgoslar@partyearth.com
4
+ #++
5
+ require "cache_machine/cache"
6
+ require "cache_machine/helpers/cache_helper"
7
+ require "cache_machine/logger"
8
+ require "cache_machine/railtie"
9
+
10
+ ActiveRecord::Base.send :include, CacheMachine::Cache
@@ -0,0 +1,61 @@
1
+ require "cache_machine/cache/map"
2
+
3
+ module CacheMachine
4
+ module Cache
5
+ extend ActiveSupport::Concern
6
+
7
+ # Supported cache formats. You can add your own.
8
+ FORMATS = [nil, :ehtml, :html, :json, :xml]
9
+
10
+ included do
11
+ after_save { self.class.reset_timestamps }
12
+ after_destroy { self.class.reset_timestamps }
13
+ end
14
+
15
+ module ClassMethods
16
+ # Initializes tracking associations to write and reset cache.
17
+ # +associations+ parameter is to represent cache map with hash.
18
+ #
19
+ # ==== Examples
20
+ # # Cache associated collections
21
+ # acts_as_cache_machine_for :cats, :dogs
22
+ #
23
+ # # Cache result of method to be expired when collection changes.
24
+ # acts_as_cache_machine_for :cats => :cat_ids
25
+ #
26
+ # # Cache and expire dependent collections (_mouse_ change invalidates all other collection caches by chain)
27
+ # acts_as_cache_machine_for :mouses => :cats, :cats => [:gods, :bears], :gods, :bears
28
+ def acts_as_cache_machine_for *associations
29
+ include CacheMachine::Cache::Map
30
+ cache_associated(associations)
31
+ end
32
+
33
+ # Returns timestamp of class collection.
34
+ def timestamp format = nil
35
+ Rails.cache.fetch(timestamp_key format) { Time.now.to_i.to_s }
36
+ end
37
+
38
+ # Returns cache key to fetch timestamp from memcached.
39
+ def timestamp_key format = nil
40
+ [self.name, format, 'timestamp'].join '_'
41
+ end
42
+
43
+ # Returns cache key of +anything+ with timestamp attached.
44
+ def timestamped_key format = nil
45
+ [timestamp_key(format), timestamp(format)].join '_'
46
+ end
47
+
48
+ # Resets timestamp of class collection.
49
+ def reset_timestamp format = nil
50
+ cache_key = timestamp_key format
51
+ CacheMachine::Logger.info "CACHE_MACHINE: reset_timestamp: deleting #{timestamp_key} with format #{format}"
52
+ Rails.cache.delete(cache_key)
53
+ end
54
+
55
+ def reset_timestamps
56
+ FORMATS.each { |format| reset_timestamp format }
57
+ end
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,188 @@
1
+ module CacheMachine
2
+ module Cache
3
+
4
+ # Module to write and expire association cache by given map.
5
+ module Map
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ CacheMachine::Logger.info "CACHE_MACHINE: bind cache-map on class #{self.name}"
10
+
11
+ # Stores associations map to be cached.
12
+ cattr_accessor :cache_map
13
+ self.cache_map = {}
14
+ end
15
+
16
+ module ClassMethods
17
+ # Fills cache map.
18
+ def cache_associated associations
19
+ [*associations].each do |association|
20
+ self.cache_map.merge! association.is_a?(Hash) ? association : {association => []}
21
+ end
22
+ end
23
+
24
+ # Defines timestamp for object.
25
+ def define_timestamp timestamp_name, options = {}, &block
26
+ options[:timestamp] = block if block
27
+
28
+ define_method timestamp_name do
29
+ fetch_cache_of(timestamp_key_of(timestamp_name), options) do
30
+ CacheMachine::Logger.info "CACHE_MACHINE: define_timestamp: deleting #{timestamp_name}"
31
+ delete_cache_of timestamp_name # Case when cache expired by time.
32
+ Time.now.to_i.to_s
33
+ end
34
+ end
35
+ end
36
+
37
+ # Deletes cache of collection with name +association_id+ for each object associated with +record+
38
+ # Called only when <tt>has_many :through</tt> collection changed.
39
+ def delete_association_cache_on record, reflection
40
+ pk = record.class.primary_key
41
+ self.joins(reflection.name).where(reflection.table_name => { pk => record.send(pk) }).each do |cache_source_record|
42
+ cache_source_record.delete_cache_of reflection.name
43
+ end
44
+ end
45
+
46
+ # Overwrites +has_many+ of +ActiveRecord+ class to hook Cache Machine.
47
+ def has_many(association_id, options = {})
48
+ # Ensure what collection should be tracked.
49
+ if (should_be_on_hook = self.cache_map.keys.include?(association_id)) && options[:through]
50
+ # If relation is _many_to_many_ track collection changes.
51
+ options[:after_add] = \
52
+ options[:before_remove] = :delete_association_cache_on
53
+ end
54
+ super
55
+ hook_cache_machine_on association_id if should_be_on_hook
56
+ end
57
+
58
+ # Overwrites +has_and_belongs_to_many+ of +ActiveRecord+ class to hook Cache Machine.
59
+ def has_and_belongs_to_many(association_id, options = {})
60
+ # Ensure what collection should be tracked.
61
+ if(should_be_on_hook = self.cache_map.keys.include?(association_id))
62
+ # If relation is _many_to_many_ track collection changes.
63
+ options[:after_add] = \
64
+ options[:before_remove] = :delete_association_cache_on
65
+ end
66
+ super
67
+ hook_cache_machine_on association_id if should_be_on_hook
68
+ end
69
+
70
+ protected
71
+
72
+ # Hooks Cache Machine on association with name +association_id+.
73
+ def hook_cache_machine_on association_id
74
+ reset_cache_proc = Proc.new do |reflection, target_class, &block|
75
+ block ||= lambda { target_class.delete_association_cache_on self, reflection }
76
+
77
+ reflection.klass.after_save &block
78
+ reflection.klass.before_destroy &block
79
+ end
80
+
81
+ case (reflection = (target_class = self).reflect_on_association association_id)
82
+ when ActiveRecord::Reflection::ThroughReflection
83
+ # If association is _many_to_many_ it should reset its cache for all associated objects with class +target_class+.
84
+ reset_cache_proc.call(reflection, target_class)
85
+ when ActiveRecord::Reflection::AssociationReflection
86
+ if reflection.macro == :has_and_belongs_to_many
87
+ reset_cache_proc.call(reflection, target_class)
88
+ else
89
+ # If association is _has_many_ or _has_one_ it should reset its cache for associated object with class +target_class+.
90
+ reset_cache_proc.call(reflection) do
91
+ target_class.where((reflection.options[:primary_key] || :id) =>
92
+ send(reflection.options[:foreign_key] || reflection.primary_key_name)).first.try(:delete_cache_of, association_id)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ module InstanceMethods
100
+ # Returns cache key of +_member+.
101
+ # TODO: describe options.
102
+ def cache_key_of _member, options = {}
103
+ [self.class.name, self.to_param, _member, options[:format], options[:page] || 1].compact.join '_'
104
+ end
105
+
106
+ # Fetches cache of +_member+ from cache map.
107
+ # TODO: Describe options.
108
+ # TODO: Describe timestamp features (we can pass methods or fields as timestamps too).
109
+ # Or we can use define_timestamp +:expires_in => 20.hours+.
110
+ def fetch_cache_of _member, options = {}, &block
111
+ cache_key = if timestamp = options[:timestamp]
112
+ # Make key dependent on collection timestamp and optional timestamp.
113
+ [timestamped_key_of(_member, options), instance_eval(&timestamp)].join '_'
114
+ else
115
+ cache_key_of(_member, options)
116
+ end
117
+
118
+ expires_in = if expires_at = options[:expires_at]
119
+ expires_at = expires_at.call if expires_at.kind_of? Proc
120
+
121
+ if expires_at.kind_of? Time
122
+ expires_at - Time.now
123
+ else
124
+ raise ArgumentError, "expires_at is not a Time"
125
+ end
126
+ else
127
+ options[:expires_in]
128
+ end
129
+
130
+ Rails.cache.fetch(cache_key, :expires_in => expires_in, &block)
131
+ end
132
+
133
+ # Recursively deletes cache by map for +_member+.
134
+ def delete_cache_of _member
135
+ delete_cache_of_only _member
136
+ if chain = self.class.cache_map[_member]
137
+ [*chain].each &method(:delete_cache_of)
138
+ end
139
+ end
140
+
141
+ # Deletes cache of only +_member+ ignoring cache map.
142
+ def delete_cache_of_only _member
143
+ CacheMachine::Cache::FORMATS.each do |cache_format|
144
+ page_nr = 0; begin
145
+ cache_key = cache_key_of(_member, {:format => cache_format, :page => page_nr += 1})
146
+ CacheMachine::Logger.info "CACHE_MACHINE: delete_cache_of_only: deleting #{cache_key}"
147
+ end while Rails.cache.delete(cache_key)
148
+ end
149
+ reset_timestamp_of _member
150
+ end
151
+
152
+ # Returns timestamp cache key for +anything+.
153
+ def timestamp_key_of anything
154
+ [self.class.name, self.to_param, anything, 'timestamp'].join '_'
155
+ end
156
+
157
+ # Returns timestamp of +anything+ from memcached.
158
+ def timestamp_of anything
159
+ Rails.cache.fetch(timestamp_key_of anything) { Time.now.to_i.to_s }
160
+ end
161
+
162
+ # Returns cache key of +anything+ with timestamp attached.
163
+ def timestamped_key_of anything, options = {}
164
+ [cache_key_of(anything, options), timestamp_of(anything)].join '_'
165
+ end
166
+
167
+ # Deletes cache of +anything+ from memory.
168
+ def reset_timestamp_of anything
169
+ cache_key = timestamp_key_of anything
170
+ CacheMachine::Logger.info "CACHE_MACHINE: reset_timestamp_of: deleting #{cache_key}"
171
+ Rails.cache.delete(cache_key)
172
+ end
173
+
174
+ protected
175
+
176
+ # Deletes cache of associated collection what contains +record+.
177
+ # Called only when <tt>has_many :through</tt> collection changed.
178
+ def delete_association_cache_on record
179
+ # Find all associations with +record+ by its class.
180
+ associations = self.class.reflect_on_all_associations.find_all { |association| association.klass == record.class }
181
+
182
+ # Delete cache of each associated collection what may contain +record+.
183
+ associations.map(&:name).each &method(:delete_cache_of)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,5 @@
1
+ module CacheMachine
2
+ module Helpers
3
+ autoload :CacheHelper, 'cache_machine/helpers/cache_helper'
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module CacheMachine
2
+ module Helpers
3
+ module CacheHelper
4
+ def cache_for record, cacheable, options = {}, &block
5
+ record.fetch_cache_of(cacheable, options.merge(:format => :ehtml)) { capture &block }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ module CacheMachine
2
+ class Logger
3
+
4
+ # The different log levels.
5
+ LOGGING_LEVELS = { :debug => 0, # Tons of log messages for tracking internal functioning of cache-machine.
6
+ :info => 1, # Log messages that visualize how cache-machine works.
7
+ :errors => 2, # Only error messages.
8
+ :none => 10 } # No output at all.
9
+
10
+ # The default log level.
11
+ @@level = LOGGING_LEVELS[:none]
12
+
13
+ class << self
14
+ LOGGING_LEVELS.keys.each do |level|
15
+ define_method(level) { |message| write level, message }
16
+ end
17
+
18
+ # Sets the log level for CacheMachine.
19
+ # Call like this in your your code, best in development.rb: ActiveRecord::CacheMachine::Logger.level = :info
20
+ def level= value
21
+ @@level = LOGGING_LEVELS[value] or raise "CACHE_MACHINE: Unknown log level: '#{value}'."
22
+ puts "CACHE_MACHINE: Setting log level to '#{value}'."
23
+ end
24
+
25
+ # Logs the given entry with the given log level.
26
+ def write level, text
27
+ puts text if @@level <= (LOGGING_LEVELS[level] or raise "CACHE_MACHINE: Unknown log level: '#{level}'.")
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ module CacheMachine
2
+ class Railtie < Rails::Railtie
3
+ initializer 'cache-machine.initialize' do
4
+ ActiveSupport.on_load(:action_view) do
5
+ include CacheMachine::Helpers::CacheHelper
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,248 @@
1
+ require 'spec_helper'
2
+
3
+ describe CacheMachine do
4
+ TARGET_TABLE_NAME = "cachers"
5
+ HABTM_TABLE_NAME = "has_and_belongs_to_many_cacheables"
6
+ HABTM_JOINS_TABLE_NAME = [TARGET_TABLE_NAME, HABTM_TABLE_NAME].join('_')
7
+ HABTM_ASSOCIATION_NAME = HABTM_TABLE_NAME.singularize
8
+ HM_TABLE_NAME = "has_many_cacheables"
9
+ HMT_JOINS_TABLE_NAME = "joins"
10
+ HMT_TABLE_NAME = "has_many_through_cacheables"
11
+ HO_TABLE_NAME = "hs_one_cacheables"
12
+ POLY_TABLE_NAME = "polymorphics"
13
+ TARGET_ASSOCIATION_NAME = TARGET_TABLE_NAME.singularize
14
+ HMT_ASSOCIATION_NAME = HMT_TABLE_NAME.singularize
15
+
16
+ class TestMigration < ActiveRecord::Migration
17
+ def self.up
18
+ self.down
19
+ create_table(HABTM_TABLE_NAME)
20
+ create_table(HM_TABLE_NAME) { |t| t.references TARGET_ASSOCIATION_NAME }
21
+ create_table(HMT_TABLE_NAME)
22
+ create_table(HMT_JOINS_TABLE_NAME) { |t| [TARGET_ASSOCIATION_NAME, HMT_ASSOCIATION_NAME].each &t.method(:references) }
23
+ create_table(POLY_TABLE_NAME) { |t| t.references(:polymorhicable); t.string(:polymorhicable_type) }
24
+ create_table(TARGET_TABLE_NAME) { |t| t.string :name; t.integer(:parent_id) }
25
+ create_table(HABTM_JOINS_TABLE_NAME, :id => false) { |t| [TARGET_ASSOCIATION_NAME, HABTM_ASSOCIATION_NAME].each &t.method(:references) }
26
+ end
27
+
28
+ def self.down
29
+ drop_table POLY_TABLE_NAME if ActiveRecord::Base.connection.tables.include? POLY_TABLE_NAME
30
+ drop_table TARGET_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(TARGET_TABLE_NAME)
31
+ drop_table HABTM_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HABTM_TABLE_NAME)
32
+ drop_table HM_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HM_TABLE_NAME)
33
+ drop_table HMT_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HMT_TABLE_NAME)
34
+ drop_table HO_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HO_TABLE_NAME)
35
+ drop_table HMT_JOINS_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HMT_JOINS_TABLE_NAME)
36
+ drop_table HABTM_JOINS_TABLE_NAME if ActiveRecord::Base.connection.tables.include?(HABTM_JOINS_TABLE_NAME)
37
+ end
38
+ end
39
+ TestMigration.up
40
+
41
+ class HasManyCacheable < ActiveRecord::Base
42
+ set_table_name HM_TABLE_NAME
43
+ belongs_to :cacher, :class_name => 'Cacher'
44
+ end
45
+
46
+ class HasManyThroughCacheable < ActiveRecord::Base
47
+ set_table_name HMT_TABLE_NAME
48
+
49
+ has_many :joins, :class_name => 'Join'
50
+ has_many :cachers, :through => :joins, :class_name => 'Cacher'
51
+ end
52
+
53
+ class Join < ActiveRecord::Base
54
+ set_table_name HMT_JOINS_TABLE_NAME
55
+
56
+ belongs_to :cacher, :class_name => 'Cacher'
57
+ belongs_to :has_many_through_cacheable, :class_name => 'HasManyThroughCacheable'
58
+ end
59
+
60
+ class HasAndBelongsToManyCacheable < ActiveRecord::Base
61
+ set_table_name HABTM_TABLE_NAME
62
+ has_and_belongs_to_many :cachers, :class_name => 'Cacher'
63
+ end
64
+
65
+ class Polymorphic < ActiveRecord::Base
66
+ set_table_name POLY_TABLE_NAME
67
+ belongs_to :polymorhicable, :polymorphic => true
68
+ end
69
+
70
+ class Cacher < ActiveRecord::Base
71
+ set_table_name TARGET_TABLE_NAME
72
+
73
+ acts_as_cache_machine_for :polymorphics,
74
+ :child_cachers,
75
+ :has_many_cacheables => :dependent_cache,
76
+ :has_many_through_cacheables => :dependent_cache,
77
+ :has_and_belongs_to_many_cacheables => :dependent_cache
78
+
79
+ define_timestamp(:dynamic_timestamp) { execute_timestamp }
80
+ define_timestamp(:static_timestamp)
81
+
82
+ has_and_belongs_to_many :has_and_belongs_to_many_cacheables, :class_name => 'HasAndBelongsToManyCacheable'
83
+ has_many :has_many_cacheables, :class_name => 'HasManyCacheable'
84
+ has_many :joins, :class_name => 'Join'
85
+ has_many :has_many_through_cacheables, :through => :joins, :class_name => 'HasManyThroughCacheable'
86
+ has_many :polymorphics, :as => :polymorhicable
87
+ has_many :child_cachers, :class_name => 'Cacher', :foreign_key => 'parent_id', :primary_key => 'id'
88
+
89
+ def to_param; name end
90
+ end
91
+
92
+ subject { Cacher.create(:name => 'foo') }
93
+
94
+ it "generates association cache keys" do
95
+ subject.cache_key_of(:has_many_cacheables, :format => :ehtml).should eql("Cacher_foo_has_many_cacheables_ehtml_1")
96
+ end
97
+
98
+ it "stores association cache" do
99
+ subject.fetch_cache_of(:has_many_cacheables) { 'cache' }
100
+ cached_result = subject.fetch_cache_of(:has_many_cacheables) { 'fresh cache' }
101
+ cached_result.should eql('cache')
102
+ end
103
+
104
+ describe "defines timestamp" do
105
+
106
+ context "dynamic" do
107
+ it "works" do
108
+ subject.stub!(:execute_timestamp).and_return(1)
109
+ subject.fetch_cache_of(:something, :timestamp => :dynamic_timestamp) { "cache" }
110
+ sleep(2)
111
+ subject.fetch_cache_of(:something, :timestamp => :dynamic_timestamp) { "foo" }.should eql("cache")
112
+ subject.stub!(:execute_timestamp).and_return(2)
113
+ sleep(2)
114
+ subject.fetch_cache_of(:something, :timestamp => :dynamic_timestamp) { "fresh cache" }.should eql("fresh cache")
115
+ end
116
+ end
117
+
118
+ context "static" do
119
+ it "works" do
120
+ sleep(2)
121
+ subject.fetch_cache_of(:something, :timestamp => :static_timestamp) { "cache" }
122
+ sleep(2)
123
+ subject.fetch_cache_of(:something, :timestamp => :static_timestamp) { "foo" }.should eql("cache")
124
+ end
125
+ end
126
+ end
127
+
128
+ describe "deletes cache" do
129
+
130
+ context "of polymorphic associations" do
131
+ it "works" do
132
+ cached_result = subject.fetch_cache_of(:polymorphics) { 'cache' }
133
+ subject.polymorphics.create
134
+ cached_result = subject.fetch_cache_of(:polymorphics) { 'new cache' }
135
+
136
+ cached_result.should eql('new cache')
137
+ end
138
+ end
139
+
140
+ context "of paginated content" do
141
+ before :each do
142
+ subject.delete_cache_of :has_many_cacheables
143
+ end
144
+
145
+ it "works" do
146
+ subject.fetch_cache_of(:has_many_cacheables, :page => 1) { 'page 1' }.should eql('page 1')
147
+ subject.fetch_cache_of(:has_many_cacheables, :page => 2) { 'page 2' }.should eql('page 2')
148
+ subject.delete_cache_of(:has_many_cacheables)
149
+ subject.fetch_cache_of(:has_many_cacheables, :page => 1) { 'fresh page 1' }.should eql('fresh page 1')
150
+ subject.fetch_cache_of(:has_many_cacheables, :page => 2) { 'fresh page 2' }.should eql('fresh page 2')
151
+ end
152
+ end
153
+
154
+ context "of self-join association" do
155
+ it "works" do
156
+ subject.fetch_cache_of(:child_cachers) { 'cache' }
157
+ child = Cacher.create(:parent_id => subject.id)
158
+ cached_result = subject.fetch_cache_of(:child_cachers) { 'new cache' }
159
+
160
+ cached_result.should eql('new cache')
161
+ end
162
+ end
163
+
164
+ context "of any member on has_many" do
165
+ before :each do
166
+ @existing_entry = subject.has_many_cacheables.create
167
+ @new_entry = HasManyCacheable.create
168
+ subject.fetch_cache_of(:has_many_cacheables) { 'cache' }
169
+ end
170
+
171
+ after :each do
172
+ cached_result = subject.fetch_cache_of(:has_many_cacheables) { 'fresh cache' }
173
+ cached_result.should eql('fresh cache')
174
+ subject.delete_cache_of(:has_many_cacheables)
175
+ end
176
+
177
+ it("works") { subject.delete_cache_of(:has_many_cacheables) }
178
+ it("on update entry in collection") { @existing_entry.save }
179
+ it("on add new entry in collection") { subject.has_many_cacheables << @new_entry }
180
+ it("on destroy intem in collection") { subject.has_many_cacheables.destroy @existing_entry }
181
+ it("on destroy intem") { HasManyCacheable.destroy @existing_entry }
182
+
183
+ context "by chain" do
184
+ it "works" do
185
+ subject.fetch_cache_of(:dependent_cache) { 'cache' }
186
+ subject.has_many_cacheables.create
187
+ subject.fetch_cache_of(:dependent_cache) { 'fresh cache' }.should eql('fresh cache')
188
+ end
189
+ end
190
+ end
191
+
192
+ context "of any member on has_many :through" do
193
+ before :each do
194
+ @existing_entry = subject.has_many_through_cacheables.create
195
+ @new_entry = HasManyThroughCacheable.create
196
+ subject.fetch_cache_of(:has_many_through_cacheables) { 'cache' }
197
+ end
198
+
199
+ after :each do
200
+ cached_result = subject.fetch_cache_of(:has_many_through_cacheables) { 'fresh cache' }
201
+ cached_result.should eql('fresh cache')
202
+ subject.delete_cache_of(:has_many_through_cacheables)
203
+ end
204
+
205
+ it("works") { subject.delete_cache_of(:has_many_through_cacheables) }
206
+ it("on update entry in collection") { @existing_entry.save }
207
+ it("on add new entry in collection") { subject.has_many_through_cacheables << @new_entry }
208
+ it("on destroy intem in collection") { subject.has_many_through_cacheables.destroy @existing_entry }
209
+ it("on destroy intem") { HasManyThroughCacheable.destroy @existing_entry }
210
+
211
+ context "by chain" do
212
+ it "works" do
213
+ subject.fetch_cache_of(:dependent_cache) { 'cache' }
214
+ subject.has_many_through_cacheables.create
215
+ subject.fetch_cache_of(:dependent_cache) { 'fresh cache' }.should eql('fresh cache')
216
+ end
217
+ end
218
+ end
219
+
220
+ context "of any member on has_and_belongs_to_many" do
221
+ before :each do
222
+ @existing_entry = subject.has_and_belongs_to_many_cacheables.create
223
+ @new_entry = HasAndBelongsToManyCacheable.create
224
+ subject.fetch_cache_of(:has_and_belongs_to_many_cacheables) { 'cache' }
225
+ end
226
+
227
+ after :each do
228
+ cached_result = subject.fetch_cache_of(:has_and_belongs_to_many_cacheables) { 'fresh cache' }
229
+ cached_result.should eql('fresh cache')
230
+ subject.delete_cache_of(:has_and_belongs_to_many_cacheables)
231
+ end
232
+
233
+ it("works") { subject.delete_cache_of(:has_and_belongs_to_many_cacheables) }
234
+ it("on update entry in collection") { @existing_entry.save }
235
+ it("on add new entry in collection") { subject.has_and_belongs_to_many_cacheables << @new_entry }
236
+ it("on destroy intem in collection") { subject.has_and_belongs_to_many_cacheables.destroy @existing_entry }
237
+ it("on destroy intem") { HasAndBelongsToManyCacheable.destroy @existing_entry }
238
+
239
+ context "by chain" do
240
+ it "works" do
241
+ subject.fetch_cache_of(:dependent_cache) { 'cache' }
242
+ subject.has_and_belongs_to_many_cacheables.create
243
+ subject.fetch_cache_of(:dependent_cache) { 'fresh cache' }.should eql('fresh cache')
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,14 @@
1
+ require "active_record"
2
+ require "rails"
3
+ require "logger"
4
+ require "rspec"
5
+ require "rspec-rails"
6
+ require "cache-machine"
7
+
8
+ RSpec.configure { |config| config.mock_with :rspec }
9
+
10
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "db/sqlite3.test.db")
11
+ CacheMachine::Logger.level = :info
12
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
13
+
14
+ Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(:memory_store) # You can set memcached
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cache-machine
3
+ version: !ruby/object:Gem::Version
4
+ hash: 56
5
+ prerelease: 5
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 6
10
+ - c
11
+ version: 0.1.6c
12
+ platform: ruby
13
+ authors:
14
+ - Kevin Goslar
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-06-30 00:00:00 Z
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: bundler
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 23
44
+ segments:
45
+ - 1
46
+ - 0
47
+ - 0
48
+ version: 1.0.0
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: jeweler
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 11
60
+ segments:
61
+ - 1
62
+ - 6
63
+ - 2
64
+ version: 1.6.2
65
+ type: :development
66
+ version_requirements: *id003
67
+ description: A Ruby on Rails framework to support cache management based on explicitely modeled caching dependencies.
68
+ email: kgoslar@partyearth.ru
69
+ executables: []
70
+
71
+ extensions: []
72
+
73
+ extra_rdoc_files:
74
+ - LICENSE.txt
75
+ - README.rdoc
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - LICENSE.txt
81
+ - README.rdoc
82
+ - Rakefile
83
+ - VERSION
84
+ - cache-machine.gemspec
85
+ - lib/cache-machine.rb
86
+ - lib/cache_machine/cache.rb
87
+ - lib/cache_machine/cache/map.rb
88
+ - lib/cache_machine/helpers.rb
89
+ - lib/cache_machine/helpers/cache_helper.rb
90
+ - lib/cache_machine/logger.rb
91
+ - lib/cache_machine/railtie.rb
92
+ - spec/lib/cache_machine_spec.rb
93
+ - spec/spec_helper.rb
94
+ homepage: http://github.com/partyearth/cache-machine
95
+ licenses:
96
+ - MIT
97
+ post_install_message:
98
+ rdoc_options: []
99
+
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ requirements: []
121
+
122
+ rubyforge_project:
123
+ rubygems_version: 1.8.10
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: A Ruby on Rails framework to support cache management based on explicitely modeled caching dependencies.
127
+ test_files: []
128
+