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