cashier 0.1.0 → 0.2.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/..gemspec +21 -0
- data/.gitignore +42 -0
- data/.infinity_test +19 -0
- data/Gemfile +2 -14
- data/Gemfile.lock +107 -22
- data/Rakefile +1 -37
- data/cashier.gemspec +22 -62
- data/lib/cashier.rb +47 -46
- data/lib/cashier/controller_helper.rb +2 -6
- data/lib/cashier/cucumber.rb +6 -0
- data/lib/cashier/matchers.rb +39 -0
- data/lib/cashier/railtie.rb +8 -0
- data/lib/cashier/version.rb +3 -0
- data/readme.md +40 -25
- data/spec/application_controller_spec.rb +31 -0
- data/spec/cashier_spec.rb +84 -2
- data/spec/spec_helper.rb +6 -0
- data/spec/test_app/.gitignore +4 -0
- data/spec/test_app/Gemfile +31 -0
- data/spec/test_app/Gemfile.lock +73 -0
- data/spec/test_app/README +256 -0
- data/spec/test_app/Rakefile +7 -0
- data/spec/test_app/app/controllers/application_controller.rb +3 -0
- data/spec/test_app/app/controllers/home_controller.rb +7 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/helpers/home_helper.rb +2 -0
- data/spec/test_app/app/views/home/index.html.erb +1 -0
- data/spec/test_app/app/views/layouts/application.html.erb +14 -0
- data/spec/test_app/config.ru +4 -0
- data/spec/test_app/config/application.rb +48 -0
- data/spec/test_app/config/boot.rb +10 -0
- data/spec/test_app/config/database.yml +22 -0
- data/spec/test_app/config/environment.rb +5 -0
- data/spec/test_app/config/environments/development.rb +28 -0
- data/spec/test_app/config/environments/production.rb +49 -0
- data/spec/test_app/config/environments/test.rb +36 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/inflections.rb +10 -0
- data/spec/test_app/config/initializers/mime_types.rb +5 -0
- data/spec/test_app/config/initializers/secret_token.rb +7 -0
- data/spec/test_app/config/initializers/session_store.rb +8 -0
- data/spec/test_app/config/locales/en.yml +5 -0
- data/spec/test_app/config/routes.rb +60 -0
- data/spec/test_app/db/seeds.rb +7 -0
- data/spec/test_app/lib/tasks/.gitkeep +0 -0
- data/spec/test_app/public/404.html +26 -0
- data/spec/test_app/public/422.html +26 -0
- data/spec/test_app/public/500.html +26 -0
- data/spec/test_app/public/favicon.ico +0 -0
- data/spec/test_app/public/images/rails.png +0 -0
- data/spec/test_app/public/javascripts/application.js +2 -0
- data/spec/test_app/public/javascripts/controls.js +965 -0
- data/spec/test_app/public/javascripts/dragdrop.js +974 -0
- data/spec/test_app/public/javascripts/effects.js +1123 -0
- data/spec/test_app/public/javascripts/prototype.js +6001 -0
- data/spec/test_app/public/javascripts/rails.js +191 -0
- data/spec/test_app/public/robots.txt +5 -0
- data/spec/test_app/public/stylesheets/.gitkeep +0 -0
- data/spec/test_app/script/rails +6 -0
- data/spec/test_app/test/performance/browsing_test.rb +9 -0
- data/spec/test_app/test/test_helper.rb +13 -0
- data/spec/test_app/vendor/plugins/.gitkeep +0 -0
- metadata +144 -39
- data/VERSION +0 -1
@@ -17,17 +17,13 @@ module Cashier
|
|
17
17
|
klass.class_eval do
|
18
18
|
def write_fragment_with_tagged_key(key, content, options = nil)
|
19
19
|
if options && options[:tag] && Cashier.perform_caching?
|
20
|
-
|
20
|
+
tags = case options[:tag].class.to_s
|
21
21
|
when 'Proc', 'Lambda'
|
22
22
|
options[:tag].call(self)
|
23
23
|
else
|
24
24
|
options[:tag]
|
25
25
|
end
|
26
|
-
|
27
|
-
tags.each do |tag|
|
28
|
-
Cashier.redis.sadd tag, fragment_cache_key(key)
|
29
|
-
Cashier.redis.sadd Cashier::STORAGE_KEY, tag
|
30
|
-
end
|
26
|
+
Cashier.store_fragment fragment_cache_key(key), *tags
|
31
27
|
end
|
32
28
|
write_fragment_without_tagged_key(key, content, options)
|
33
29
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Cashier
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
def be_cached
|
5
|
+
Cache.new
|
6
|
+
end
|
7
|
+
|
8
|
+
class Cache
|
9
|
+
def matches?(target)
|
10
|
+
@target = target
|
11
|
+
|
12
|
+
@test_results = Cashier.keys_for(@target).inject({}) do |hash, key|
|
13
|
+
hash.merge(key => Rails.cache.exist?(key))
|
14
|
+
end
|
15
|
+
|
16
|
+
flag = @test_results.values.inject(true) { |f, v| f && v }
|
17
|
+
flag && Cashier.keys_for(@target).present?
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message_for_should
|
21
|
+
<<-msg
|
22
|
+
expected the Rails.cache to include all these keys:
|
23
|
+
#{Cashier.keys_for(@target).to_sentence}, but
|
24
|
+
it did not include these keys:
|
25
|
+
#{@test_results.keys.select {|k| @test_results[k] == false }.to_sentence}
|
26
|
+
msg
|
27
|
+
end
|
28
|
+
|
29
|
+
def failure_message_for_should_not
|
30
|
+
<<-msg
|
31
|
+
expected the Rails.cache to not include all these keys:
|
32
|
+
#{Cashier.keys_for(@target).to_sentence}, but
|
33
|
+
it did include these keys:
|
34
|
+
#{@test_results.keys.select {|k| @test_results[k] == true }.to_sentence}
|
35
|
+
msg
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/readme.md
CHANGED
@@ -6,14 +6,14 @@ Manage your cache keys with tags, forget about keys!
|
|
6
6
|
|
7
7
|
# in your view
|
8
8
|
cache @some_record, :tag => 'some-component'
|
9
|
-
|
9
|
+
|
10
10
|
# in another view
|
11
11
|
cache @some_releated_record, :tag => 'some-component'
|
12
12
|
|
13
13
|
# can have multiple tags
|
14
14
|
cache @something, :tag => ['dashboard', 'settings'] # can expire from either tag
|
15
15
|
|
16
|
-
# in
|
16
|
+
# in an observer
|
17
17
|
Cashier.expire 'some-component' # don't worry about keys! Much easier to sweep with confidence
|
18
18
|
|
19
19
|
# in your controller
|
@@ -22,7 +22,6 @@ Manage your cache keys with tags, forget about keys!
|
|
22
22
|
c.params
|
23
23
|
}
|
24
24
|
|
25
|
-
|
26
25
|
# need to access the controller?
|
27
26
|
caches_action :tag => proc {|c|
|
28
27
|
# c is the controller
|
@@ -37,7 +36,7 @@ Manage your cache keys with tags, forget about keys!
|
|
37
36
|
Cashier.tags
|
38
37
|
|
39
38
|
# sweep all stored keys
|
40
|
-
Cashier.
|
39
|
+
Cashier.clear
|
41
40
|
|
42
41
|
## How it Came About
|
43
42
|
|
@@ -49,44 +48,60 @@ I needed a better solution. I wanted to expire things logically as a viewed them
|
|
49
48
|
a record was added, I wanted to say "expire that page". Problem was that page contained ~1000 different keys.
|
50
49
|
So I needed something to store the keys for me and associate them with tags. That's exactly what cashier does.
|
51
50
|
Cache associate individual cache keys with a tag, then expire them all at once. This took my 7 layer loop
|
52
|
-
down
|
51
|
+
down to one line of code. It's also made managing the cache throught my application much easier.
|
53
52
|
|
54
53
|
## Why Tag Based Caching is Useful
|
55
54
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
55
|
+
1. You don't worry about keys. How many times have you created a complicated key for a fragment or action
|
56
|
+
then messed up when you tried to expire the cache
|
57
|
+
2. Associate your cached content into groups of related content. If you have records that are closely associated
|
58
|
+
or displayed together, then you can tag them and expire them at once.
|
59
|
+
3. **Expire cached content from anywhere.** If you've done any serious development, you know that Rails caching
|
60
|
+
does not work (easily) outside the scope of an HTTP request. If you have background jobs that manipulate data
|
61
|
+
or potentially invalidate cached data, you know how much of a pain it is to say `expire_fragment` in some random code.
|
62
|
+
4. Don't do anything differently! All you have to do is pass `:tag => 'something'` into `cache` (in the view) or `caches_action`
|
63
|
+
in the controller.
|
65
64
|
|
66
65
|
## How it Works
|
67
66
|
|
68
67
|
Cashier hooks into Rails' `expire_fragment` method using `alias_method_chain` to run some code that captures the key
|
69
|
-
and tag then stores that
|
68
|
+
and tag then stores that in the rails cache. **No external processes are
|
69
|
+
needed. All tag/fragment information is stored in the Rails.cache.**
|
70
70
|
|
71
71
|
## Configuration
|
72
72
|
|
73
|
-
|
73
|
+
**if you're using Rails 3, there is no configuration.** If you're using
|
74
|
+
Rails 2, include `Cashier::ControllerHelper` into ApplicationController
|
75
|
+
like so:
|
76
|
+
|
77
|
+
require 'cashier'
|
78
|
+
|
79
|
+
class ApplicationController
|
80
|
+
include Cashier::ControllerHelper
|
81
|
+
end
|
74
82
|
|
75
|
-
|
76
|
-
test: localhost:6379/test
|
83
|
+
## Testing
|
77
84
|
|
78
|
-
|
85
|
+
I've also included some Rspec Matchers and a cucumber helper for testing
|
86
|
+
caching. The rspec matchers can be used like this:
|
79
87
|
|
80
|
-
|
81
|
-
|
88
|
+
describe "get index" do
|
89
|
+
it "should cache the action" do
|
90
|
+
get :index
|
91
|
+
'some-tag'.should be_cached
|
92
|
+
end
|
93
|
+
end
|
82
94
|
|
83
|
-
|
95
|
+
Testing w/cucumber is more involved. **Make sure you set perform_caching = true in test.rb**
|
96
|
+
Then require `cashier/cucumber` to use the matchers in your steps. Here
|
97
|
+
is an example of a possible step
|
84
98
|
|
85
|
-
|
86
|
-
|
99
|
+
Then /the dashboard should be cached/ do
|
100
|
+
"dashboard".should be_cached
|
87
101
|
end
|
88
102
|
|
89
|
-
|
103
|
+
Including `cashier/cucumber` will also wipe the cache before every
|
104
|
+
scenario.
|
90
105
|
|
91
106
|
## Contributing to Cashier
|
92
107
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'rspec/rails'
|
4
|
+
|
5
|
+
class ApplicationController
|
6
|
+
include Cashier::ControllerHelper
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ApplicationController do
|
10
|
+
include Rspec::Rails::ControllerExampleGroup
|
11
|
+
|
12
|
+
it "should be able to tag framgents" do
|
13
|
+
Cashier.should_receive(:store_fragment).with('views/key', 'tag')
|
14
|
+
controller.write_fragment('key', 'content', :tag => 'tag')
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should be able write a fragment with multiple tags" do
|
18
|
+
Cashier.should_receive(:store_fragment).with('views/key', 'tag1', 'tag2')
|
19
|
+
controller.write_fragment('key', 'content', :tag => %w(tag1 tag2))
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should able to create a tag with a proc" do
|
23
|
+
Cashier.should_receive(:store_fragment).with('views/key', 'tag')
|
24
|
+
controller.write_fragment('key', 'content', :tag => proc {|c| 'tag' })
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should able to create a tag with a lambda" do
|
28
|
+
Cashier.should_receive(:store_fragment).with('views/key', 'tag')
|
29
|
+
controller.write_fragment('key', 'content', :tag => lambda {|c| 'tag' })
|
30
|
+
end
|
31
|
+
end
|
data/spec/cashier_spec.rb
CHANGED
@@ -1,7 +1,89 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
3
|
describe "Cashier" do
|
4
|
-
|
5
|
-
|
4
|
+
subject { Cashier }
|
5
|
+
|
6
|
+
let(:cache) { Rails.cache }
|
7
|
+
|
8
|
+
describe "#store_fragment" do
|
9
|
+
it "should write the tag to the cache" do
|
10
|
+
subject.store_fragment('fragment-key', 'dashboard')
|
11
|
+
|
12
|
+
cache.fetch('dashboard').should eql(['fragment-key'])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should store the tag for book keeping" do
|
16
|
+
subject.store_fragment('fragment-key', 'dashboard', 'settings')
|
17
|
+
cache.fetch(Cashier::CACHE_KEY).should eql(%w(dashboard settings))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#expire" do
|
22
|
+
before do
|
23
|
+
subject.store_fragment('fragment-key', 'dashboard')
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should remove delete the fragment key" do
|
27
|
+
subject.expire('dashboard')
|
28
|
+
Rails.cache.fetch('fragment-key').should be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should remove the tag" do
|
32
|
+
subject.expire('dashboard')
|
33
|
+
Rails.cache.fetch('dashboard').should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should remove the tag from the list of tracked tags" do
|
37
|
+
subject.expire('dashboard')
|
38
|
+
Rails.cache.fetch(Cashier::CACHE_KEY).should eql([])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#tags" do
|
43
|
+
it "should return a list of active tags" do
|
44
|
+
subject.store_fragment('key1', 'dashboard')
|
45
|
+
subject.store_fragment('key2', 'settings')
|
46
|
+
subject.store_fragment('key3', 'email')
|
47
|
+
|
48
|
+
subject.tags.should eql(%w(dashboard settings email))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#clear' do
|
53
|
+
before(:each) do
|
54
|
+
subject.store_fragment('key1', 'dashboard')
|
55
|
+
subject.store_fragment('key2', 'settings')
|
56
|
+
subject.store_fragment('key3', 'email')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should expire all tags" do
|
60
|
+
subject.should_receive(:expire).with('dashboard','settings','email')
|
61
|
+
subject.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should clear the list of tracked tags" do
|
65
|
+
subject.clear
|
66
|
+
cache.fetch(Cashier::CACHE_KEY).should be_nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#keys' do
|
71
|
+
it "should return an array of all the tracked keys" do
|
72
|
+
subject.store_fragment('key1', 'dashboard')
|
73
|
+
subject.store_fragment('key2', 'settings')
|
74
|
+
subject.store_fragment('key3', 'email')
|
75
|
+
|
76
|
+
subject.keys.should eql(%w(key1 key2 key3))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#keys_for' do
|
81
|
+
it "should return an array of all the keys for the tag" do
|
82
|
+
subject.store_fragment('key1', 'dashboard')
|
83
|
+
subject.store_fragment('key2', 'dashboard')
|
84
|
+
subject.store_fragment('key3', 'dashboard')
|
85
|
+
|
86
|
+
subject.keys_for('dashboard').should eql(%w(key1 key2 key3))
|
87
|
+
end
|
6
88
|
end
|
7
89
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,10 +3,16 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
3
3
|
require 'rspec'
|
4
4
|
require 'cashier'
|
5
5
|
|
6
|
+
ENV['RAILS_ENV'] = 'test'
|
7
|
+
require 'test_app/config/environment'
|
8
|
+
|
6
9
|
# Requires supporting files with custom matchers and macros, etc,
|
7
10
|
# in ./support/ and its subdirectories.
|
8
11
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
12
|
|
10
13
|
RSpec.configure do |config|
|
11
14
|
|
15
|
+
config.before(:each) do
|
16
|
+
Rails.cache.clear
|
17
|
+
end
|
12
18
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
gem 'rails', '3.0.4'
|
4
|
+
|
5
|
+
# Bundle edge Rails instead:
|
6
|
+
# gem 'rails', :git => 'git://github.com/rails/rails.git'
|
7
|
+
|
8
|
+
gem 'sqlite3'
|
9
|
+
|
10
|
+
# Use unicorn as the web server
|
11
|
+
# gem 'unicorn'
|
12
|
+
|
13
|
+
# Deploy with Capistrano
|
14
|
+
# gem 'capistrano'
|
15
|
+
|
16
|
+
# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
|
17
|
+
# gem 'ruby-debug'
|
18
|
+
# gem 'ruby-debug19'
|
19
|
+
|
20
|
+
# Bundle the extra gems:
|
21
|
+
# gem 'bj'
|
22
|
+
# gem 'nokogiri'
|
23
|
+
# gem 'sqlite3-ruby', :require => 'sqlite3'
|
24
|
+
# gem 'aws-s3', :require => 'aws/s3'
|
25
|
+
|
26
|
+
# Bundle gems for the local environment. Make sure to
|
27
|
+
# put test-only gems in this group so their generators
|
28
|
+
# and rake tasks are available in development mode:
|
29
|
+
# group :development, :test do
|
30
|
+
# gem 'webrat'
|
31
|
+
# end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
abstract (1.0.0)
|
5
|
+
actionmailer (3.0.4)
|
6
|
+
actionpack (= 3.0.4)
|
7
|
+
mail (~> 2.2.15)
|
8
|
+
actionpack (3.0.4)
|
9
|
+
activemodel (= 3.0.4)
|
10
|
+
activesupport (= 3.0.4)
|
11
|
+
builder (~> 2.1.2)
|
12
|
+
erubis (~> 2.6.6)
|
13
|
+
i18n (~> 0.4)
|
14
|
+
rack (~> 1.2.1)
|
15
|
+
rack-mount (~> 0.6.13)
|
16
|
+
rack-test (~> 0.5.7)
|
17
|
+
tzinfo (~> 0.3.23)
|
18
|
+
activemodel (3.0.4)
|
19
|
+
activesupport (= 3.0.4)
|
20
|
+
builder (~> 2.1.2)
|
21
|
+
i18n (~> 0.4)
|
22
|
+
activerecord (3.0.4)
|
23
|
+
activemodel (= 3.0.4)
|
24
|
+
activesupport (= 3.0.4)
|
25
|
+
arel (~> 2.0.2)
|
26
|
+
tzinfo (~> 0.3.23)
|
27
|
+
activeresource (3.0.4)
|
28
|
+
activemodel (= 3.0.4)
|
29
|
+
activesupport (= 3.0.4)
|
30
|
+
activesupport (3.0.4)
|
31
|
+
arel (2.0.8)
|
32
|
+
builder (2.1.2)
|
33
|
+
erubis (2.6.6)
|
34
|
+
abstract (>= 1.0.0)
|
35
|
+
i18n (0.5.0)
|
36
|
+
mail (2.2.15)
|
37
|
+
activesupport (>= 2.3.6)
|
38
|
+
i18n (>= 0.4.0)
|
39
|
+
mime-types (~> 1.16)
|
40
|
+
treetop (~> 1.4.8)
|
41
|
+
mime-types (1.16)
|
42
|
+
polyglot (0.3.1)
|
43
|
+
rack (1.2.1)
|
44
|
+
rack-mount (0.6.13)
|
45
|
+
rack (>= 1.0.0)
|
46
|
+
rack-test (0.5.7)
|
47
|
+
rack (>= 1.0)
|
48
|
+
rails (3.0.4)
|
49
|
+
actionmailer (= 3.0.4)
|
50
|
+
actionpack (= 3.0.4)
|
51
|
+
activerecord (= 3.0.4)
|
52
|
+
activeresource (= 3.0.4)
|
53
|
+
activesupport (= 3.0.4)
|
54
|
+
bundler (~> 1.0)
|
55
|
+
railties (= 3.0.4)
|
56
|
+
railties (3.0.4)
|
57
|
+
actionpack (= 3.0.4)
|
58
|
+
activesupport (= 3.0.4)
|
59
|
+
rake (>= 0.8.7)
|
60
|
+
thor (~> 0.14.4)
|
61
|
+
rake (0.8.7)
|
62
|
+
sqlite3 (1.3.3)
|
63
|
+
thor (0.14.6)
|
64
|
+
treetop (1.4.9)
|
65
|
+
polyglot (>= 0.3.1)
|
66
|
+
tzinfo (0.3.24)
|
67
|
+
|
68
|
+
PLATFORMS
|
69
|
+
ruby
|
70
|
+
|
71
|
+
DEPENDENCIES
|
72
|
+
rails (= 3.0.4)
|
73
|
+
sqlite3
|