cashier 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/..gemspec +21 -0
  2. data/.gitignore +42 -0
  3. data/.infinity_test +19 -0
  4. data/Gemfile +2 -14
  5. data/Gemfile.lock +107 -22
  6. data/Rakefile +1 -37
  7. data/cashier.gemspec +22 -62
  8. data/lib/cashier.rb +47 -46
  9. data/lib/cashier/controller_helper.rb +2 -6
  10. data/lib/cashier/cucumber.rb +6 -0
  11. data/lib/cashier/matchers.rb +39 -0
  12. data/lib/cashier/railtie.rb +8 -0
  13. data/lib/cashier/version.rb +3 -0
  14. data/readme.md +40 -25
  15. data/spec/application_controller_spec.rb +31 -0
  16. data/spec/cashier_spec.rb +84 -2
  17. data/spec/spec_helper.rb +6 -0
  18. data/spec/test_app/.gitignore +4 -0
  19. data/spec/test_app/Gemfile +31 -0
  20. data/spec/test_app/Gemfile.lock +73 -0
  21. data/spec/test_app/README +256 -0
  22. data/spec/test_app/Rakefile +7 -0
  23. data/spec/test_app/app/controllers/application_controller.rb +3 -0
  24. data/spec/test_app/app/controllers/home_controller.rb +7 -0
  25. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  26. data/spec/test_app/app/helpers/home_helper.rb +2 -0
  27. data/spec/test_app/app/views/home/index.html.erb +1 -0
  28. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  29. data/spec/test_app/config.ru +4 -0
  30. data/spec/test_app/config/application.rb +48 -0
  31. data/spec/test_app/config/boot.rb +10 -0
  32. data/spec/test_app/config/database.yml +22 -0
  33. data/spec/test_app/config/environment.rb +5 -0
  34. data/spec/test_app/config/environments/development.rb +28 -0
  35. data/spec/test_app/config/environments/production.rb +49 -0
  36. data/spec/test_app/config/environments/test.rb +36 -0
  37. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  38. data/spec/test_app/config/initializers/inflections.rb +10 -0
  39. data/spec/test_app/config/initializers/mime_types.rb +5 -0
  40. data/spec/test_app/config/initializers/secret_token.rb +7 -0
  41. data/spec/test_app/config/initializers/session_store.rb +8 -0
  42. data/spec/test_app/config/locales/en.yml +5 -0
  43. data/spec/test_app/config/routes.rb +60 -0
  44. data/spec/test_app/db/seeds.rb +7 -0
  45. data/spec/test_app/lib/tasks/.gitkeep +0 -0
  46. data/spec/test_app/public/404.html +26 -0
  47. data/spec/test_app/public/422.html +26 -0
  48. data/spec/test_app/public/500.html +26 -0
  49. data/spec/test_app/public/favicon.ico +0 -0
  50. data/spec/test_app/public/images/rails.png +0 -0
  51. data/spec/test_app/public/javascripts/application.js +2 -0
  52. data/spec/test_app/public/javascripts/controls.js +965 -0
  53. data/spec/test_app/public/javascripts/dragdrop.js +974 -0
  54. data/spec/test_app/public/javascripts/effects.js +1123 -0
  55. data/spec/test_app/public/javascripts/prototype.js +6001 -0
  56. data/spec/test_app/public/javascripts/rails.js +191 -0
  57. data/spec/test_app/public/robots.txt +5 -0
  58. data/spec/test_app/public/stylesheets/.gitkeep +0 -0
  59. data/spec/test_app/script/rails +6 -0
  60. data/spec/test_app/test/performance/browsing_test.rb +9 -0
  61. data/spec/test_app/test/test_helper.rb +13 -0
  62. data/spec/test_app/vendor/plugins/.gitkeep +0 -0
  63. metadata +144 -39
  64. 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
- passed_tags = case options[:tag].class.to_s
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
- tags = passed_tags.is_a?(Array) ? passed_tags : [passed_tags]
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,6 @@
1
+ World(Cashier::Matchers)
2
+
3
+ Before('@caching') do
4
+ Cashier.clear
5
+ Rails.cache.clear
6
+ 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
@@ -0,0 +1,8 @@
1
+ module Cashier
2
+ class Railtie < Rails::Railtie
3
+ initializer 'cashier.initialize' do
4
+ ApplicationController.send :include, Cashier::ControllerHelper
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,3 @@
1
+ module Cashier
2
+ VERSION = "0.2.0"
3
+ 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 your sweeper
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.wipe
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 two one line of code. It's also made managing the cache throught my application much easier.
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
- 1. You don't worry about keys. How many times have you created a complicated key for a fragment or action
57
- then messed up when you tried to expire the cache
58
- 2. Associate your cached content into groups of related content. If you have records that are closely associated
59
- or displayed together, then you can tag them and expire them at once.
60
- 3. **Expire cached content from anywhere.** If you've done any serious development, you know that Rails caching
61
- does not work (easily) outside the scope of an HTTP request. If you have background jobs that manipulate data
62
- or potentially invalidate cached data, you know how much of a pain it is to say `expire_fragment` in some random code.
63
- 4. Don't do anything differently! All you have to do is pass `:tag => 'something'` into `cache` (in the view) or `caches_action`
64
- in the controller.
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 as a set in redis. Then uses the set members to loop over keys to deleting using `Rails.cache.delete`
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
- Cashier needs Redis to function correctly. Create a yaml file. You may call it `config/cashier.yml`
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
- development: localhost:6379
76
- test: localhost:6379/test
83
+ ## Testing
77
84
 
78
- Then write a simple initializer to configure Cahiser. Drop this file in in `config/initializers/cashier.rb`
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
- resque_config = YAML.load_file(Raisl.root.join 'config', 'cashiser.yml')
81
- Cashier.redis = resque_config[Rails.env]
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
- Now in your `application_controller.rb` file just include the module
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
- class ApplicationController < ActionController::Base
86
- include Cashier::ControllerHelper
99
+ Then /the dashboard should be cached/ do
100
+ "dashboard".should be_cached
87
101
  end
88
102
 
89
- Now you're good to go!
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
- it "fails" do
5
- fail "hey buddy, you should probably rename this file and start specing for real"
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,4 @@
1
+ .bundle
2
+ db/*.sqlite3
3
+ log/*.log
4
+ tmp/
@@ -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