cashier-ftbpro 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +195 -0
- data/Rakefile +11 -0
- data/cashier.gemspec +27 -0
- data/lib/cashier.rb +257 -0
- data/lib/cashier/adapters/cache_store.rb +66 -0
- data/lib/cashier/adapters/redis_store.rb +62 -0
- data/lib/cashier/cucumber.rb +6 -0
- data/lib/cashier/matchers.rb +38 -0
- data/lib/cashier/railtie.rb +9 -0
- data/lib/cashier/version.rb +3 -0
- data/spec/dummy/.gitignore +4 -0
- data/spec/dummy/Gemfile +31 -0
- data/spec/dummy/README +256 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/home_controller.rb +7 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/helpers/home_helper.rb +2 -0
- data/spec/dummy/app/views/home/index.html.erb +1 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +44 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +25 -0
- data/spec/dummy/config/environments/production.rb +46 -0
- data/spec/dummy/config/environments/test.rb +31 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +60 -0
- data/spec/dummy/db/seeds.rb +7 -0
- data/spec/dummy/lib/tasks/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/images/rails.png +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/controls.js +965 -0
- data/spec/dummy/public/javascripts/dragdrop.js +974 -0
- data/spec/dummy/public/javascripts/effects.js +1123 -0
- data/spec/dummy/public/javascripts/prototype.js +6001 -0
- data/spec/dummy/public/javascripts/rails.js +191 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/test/performance/browsing_test.rb +9 -0
- data/spec/dummy/test/test_helper.rb +13 -0
- data/spec/dummy/vendor/plugins/.gitkeep +0 -0
- data/spec/integration/rails_cache_integration_spec.rb +68 -0
- data/spec/integration/rails_configuration_spec.rb +9 -0
- data/spec/lib/cashier/adapters/cache_store_spec.rb +92 -0
- data/spec/lib/cashier/adapters/redis_store_spec.rb +107 -0
- data/spec/lib/cashier_spec.rb +154 -0
- data/spec/spec_helper.rb +48 -0
- metadata +255 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Adam Hawkins
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
# Cashier: Tag Based Caching for Rails
|
2
|
+
|
3
|
+
Manage your cache keys with tags, forget about keys!
|
4
|
+
|
5
|
+
## What Is It?
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
# in your view
|
9
|
+
cache @some_record, :tag => 'some-component'
|
10
|
+
|
11
|
+
# in another view
|
12
|
+
cache @some_releated_record, :tag => 'some-component'
|
13
|
+
|
14
|
+
# can have multiple tags
|
15
|
+
cache @something, :tag => ['dashboard', 'settings'] # can expire from either tag
|
16
|
+
|
17
|
+
# in an observer
|
18
|
+
Cashier.expire 'some-component' # don't worry about keys! Much easier to sweep with confidence
|
19
|
+
|
20
|
+
# in your controller
|
21
|
+
caches_action :tag => 'complicated-action', :cache_path => proc { |c|
|
22
|
+
# huge complicated mess of parameters
|
23
|
+
c.params
|
24
|
+
}
|
25
|
+
|
26
|
+
# need to access the controller?
|
27
|
+
caches_action :tag => proc {|c|
|
28
|
+
# c is the controller
|
29
|
+
"users/#{c.current_user.id}/dashboard"
|
30
|
+
}
|
31
|
+
|
32
|
+
# in your sweeper, in your observers, in your Resque jobs...wherever
|
33
|
+
Cashier.expire 'complicated-action'
|
34
|
+
Cashier.expire 'tag1', 'tag2', 'tag3', 'tag4'
|
35
|
+
|
36
|
+
# It integrates smoothly with Rails.cache as well, not just the views
|
37
|
+
Rails.cache.fetch("user_1", :tag => ["users"]) { User.find(1) }
|
38
|
+
Rails.cache.fetch("user_2", :tag => ["users"]) { User.find(2) }
|
39
|
+
Rails.cache.fetch("user_3", :tag => ["users"]) { User.find(3) }
|
40
|
+
Rails.cache.fetch("admins", :tag => ["users"]) { User.where(role: "Admin").all }
|
41
|
+
|
42
|
+
# You can then expire all your users
|
43
|
+
Cashier.expire "users"
|
44
|
+
|
45
|
+
# You can also use Rails.cache.write
|
46
|
+
Rails.cache.write("foo", "bar", :tag => ["some_tag"])
|
47
|
+
|
48
|
+
# what's cached
|
49
|
+
Cashier.tags
|
50
|
+
|
51
|
+
# sweep all stored keys
|
52
|
+
Cashier.clear
|
53
|
+
```
|
54
|
+
|
55
|
+
## How it Came About
|
56
|
+
|
57
|
+
I work on an application that involves all sorts of caching. I try to use action caching whenever I possible.
|
58
|
+
I had an index action that had maybe ~20 different combination of filters and sorting. If you want to use
|
59
|
+
action caching you have to create a **unique** key for every combination. This created a nice 6 nested loop
|
60
|
+
to expire the cache. Once you had pagination, then you have even more combinations of possible cache keys.
|
61
|
+
I needed a better solution. I wanted to expire things logically as a viewed them on the page. IE, if
|
62
|
+
a record was added, I wanted to say "expire that page". Problem was that page contained ~1000 different keys.
|
63
|
+
So I needed something to store the keys for me and associate them with tags. That's exactly what cashier does.
|
64
|
+
Cache associate individual cache keys with a tag, then expire them all at once. This took my 7 layer loop
|
65
|
+
down to one line of code. It's also made managing the cache throught my application much easier.
|
66
|
+
|
67
|
+
## Why Tag Based Caching is Useful
|
68
|
+
|
69
|
+
1. You don't worry about keys. How many times have you created a complicated key for a fragment or action
|
70
|
+
then messed up when you tried to expire the cache
|
71
|
+
2. Associate your cached content into groups of related content. If you have records that are closely associated
|
72
|
+
or displayed together, then you can tag them and expire them at once.
|
73
|
+
3. **Expire cached content from anywhere.** If you've done any serious development, you know that Rails caching
|
74
|
+
does not work (easily) outside the scope of an HTTP request. If you have background jobs that manipulate data
|
75
|
+
or potentially invalidate cached data, you know how much of a pain it is to say `expire_fragment` in some random code.
|
76
|
+
4. Don't do anything differently! All you have to do is pass `:tag => 'something'` into `cache` (in the view) or `caches_action`
|
77
|
+
in the controller.
|
78
|
+
|
79
|
+
## How it Works
|
80
|
+
|
81
|
+
Cashier hooks into Rails' `store_fragment` method using `alias_method_chain` to run some code that captures the key
|
82
|
+
and tag then stores that in the rails cache.
|
83
|
+
|
84
|
+
### Adapters
|
85
|
+
|
86
|
+
Cashier has 2 adapters for the tags storing, `:cache_store` or `:redis_store`.
|
87
|
+
|
88
|
+
**IMPORTANT**: this store is ONLY for the tags, your fragments will still be stored in `Rails.cache`.
|
89
|
+
|
90
|
+
#### Setting an adapter for working with the cache as the tags storage
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
# config/environment/production.rb
|
94
|
+
|
95
|
+
config.cashier.adapter = :cache_store
|
96
|
+
# or config.cashier.adapter = :redis_store
|
97
|
+
```
|
98
|
+
|
99
|
+
#### Setting an adapter for working with Redis as the tags storage
|
100
|
+
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# config/environment/production.rb
|
104
|
+
config.cashier.adapter.redis = Redis.new(:host => '127.0.0.1', :port => '3697') # or Resque.redis or any existing redis connection
|
105
|
+
```
|
106
|
+
|
107
|
+
### Why Redis?
|
108
|
+
|
109
|
+
The reason Redis was introduced is that while the Rails.cache usage
|
110
|
+
for the tags store is clean and involves no "outer" dependencies,
|
111
|
+
since memcached is limited to read/write, it can slow down the application quite a bit.
|
112
|
+
|
113
|
+
If you work with very large arrays of keys and tags, you may see slowness in the cache communication.
|
114
|
+
|
115
|
+
Redis was introduces since it has the ability to work with "sets", and
|
116
|
+
you can add/remove tags from this set without reading the entire array.
|
117
|
+
|
118
|
+
|
119
|
+
### Benchmarking
|
120
|
+
|
121
|
+
Using the cache adapter, this piece of code takes 3 seconds on average
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
Benchmark.measure do
|
125
|
+
500.times do
|
126
|
+
key = (0...50).map{ ('a'..'z').to_a[rand(26)] }.join
|
127
|
+
tag = (0...50).map{ ('a'..'z').to_a[rand(26)] }.join
|
128
|
+
tag2 = (0...50).map{ ('a'..'z').to_a[rand(26)] }.join
|
129
|
+
Cashier.store_fragment(key, tag, tag2)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
```
|
133
|
+
Using the Redis adapter, the same piece of code takes 0.8 seconds, quite the difference :)
|
134
|
+
|
135
|
+
|
136
|
+
### Notifications
|
137
|
+
|
138
|
+
Cashier will send out events when things happen inside the library.
|
139
|
+
The events are sent out through `ActiveSupport::Notifications` so you can pretty much subscribe to the events from anywhere you want.
|
140
|
+
|
141
|
+
Here are the way you can subscribe to the events and use the data from them.
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
# Subscribe to the store fragment event, this is fired every time cashier will call the "store_fragment" method
|
145
|
+
# payload[:data] will be something like this: ["key", ["tag1", "tag2", "tag3"]]
|
146
|
+
ActiveSupport::Notifications.subscribe("store_fragment.cashier") do |name, start, finish, id, payload|
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
# Subscribe to the clear event. (no data)
|
151
|
+
ActiveSupport::Notifications.subscribe("clear.cashier") do |name, start, finish, id, payload|
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
# Subscribe to the delete_cache_key event
|
156
|
+
# this event will fire every time there's a Rails.cache.delete with the key
|
157
|
+
# payload[:data] will be the key name that's been deleted from the cache
|
158
|
+
ActiveSupport::Notifications.subscribe("delete_cache_key.cashier") do |name, start, finish, id, payload|
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
# Subscribe to the o_write_cache_key event
|
163
|
+
# this event will fire every time there's a Rails.cache.write with the key
|
164
|
+
# payload[:data] will be the key name that's been written to the cache
|
165
|
+
ActiveSupport::Notifications.subscribe("write_cache_key.cashier") do |name, start, finish, id, payload|
|
166
|
+
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
### Notifications use case
|
171
|
+
At [Gogobot](http://www.gogobot.com) we have a plugin to invalidate the external CDN cache on full pages for logged out users.
|
172
|
+
The usage is pretty unlimited.
|
173
|
+
|
174
|
+
If you think we're missing a notification, please do open an issue or be awesome and do it yourself and open a pull request.
|
175
|
+
|
176
|
+
## Contributors
|
177
|
+
|
178
|
+
* [twinturbo](http://twitter.com/adman65) - Initial Implementation
|
179
|
+
* [KensoDev](http://twitter.com/kensodev) - Adding Redis support (Again \o/)
|
180
|
+
* [KensoDev](http://twitter.com/kensodev) - Adding plugins support for callback methods
|
181
|
+
|
182
|
+
## Contributing to Cashier
|
183
|
+
|
184
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
185
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
186
|
+
* Fork the project
|
187
|
+
* Start a feature/bugfix branch
|
188
|
+
* Commit and push until you are happy with your contribution
|
189
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
190
|
+
* 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.
|
191
|
+
|
192
|
+
## Copyright
|
193
|
+
|
194
|
+
Copyright (c) 2010 Adam Hawkins. See LICENSE.txt for
|
195
|
+
further details.
|
data/Rakefile
ADDED
data/cashier.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "cashier/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "cashier-ftbpro"
|
7
|
+
s.version = Cashier::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Adam Hawkins"]
|
10
|
+
s.email = ["me@broadcastingadam.com"]
|
11
|
+
s.homepage = "https://github.com/threadedlabs/cashier"
|
12
|
+
s.summary = %q{Tag based caching for Rails using Redis or Memcached}
|
13
|
+
s.description = %q{Associate different cached content with a tag, then expire by tag instead of key}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency 'rails', '~> 3.0'
|
21
|
+
|
22
|
+
s.add_development_dependency 'rspec'
|
23
|
+
s.add_development_dependency 'rspec-rails'
|
24
|
+
s.add_development_dependency 'dalli'
|
25
|
+
s.add_development_dependency 'simplecov'
|
26
|
+
s.add_development_dependency 'redis', '~> 2.2.0'
|
27
|
+
end
|
data/lib/cashier.rb
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
module Cashier
|
2
|
+
|
3
|
+
CACHE_KEY = 'cashier-tags'
|
4
|
+
|
5
|
+
def self.container_cache_key(tag)
|
6
|
+
"cashier-tag-containers:#{tag}"
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Public: whether the module will perform caching or not. this is being set in the application layer .perform_caching configuration
|
12
|
+
#
|
13
|
+
# Examples
|
14
|
+
#
|
15
|
+
# Cashier.perform_caching?
|
16
|
+
# # => true
|
17
|
+
#
|
18
|
+
def perform_caching?
|
19
|
+
::ApplicationController.perform_caching
|
20
|
+
end
|
21
|
+
|
22
|
+
# Public: store a fragment with an array of tags for this fragment.
|
23
|
+
#
|
24
|
+
# fragment - cached fragment.
|
25
|
+
# tags - array of tags you want to assign this fragments.
|
26
|
+
#
|
27
|
+
# Examples
|
28
|
+
#
|
29
|
+
# Cachier.store_fragment('foo', 'tag1', 'tag2', 'tag3')
|
30
|
+
#
|
31
|
+
def store_fragment(fragment, *tags)
|
32
|
+
return unless perform_caching?
|
33
|
+
|
34
|
+
tags = tags.flatten
|
35
|
+
tags = canonize_tags(tags)
|
36
|
+
|
37
|
+
ActiveSupport::Notifications.instrument("store_fragment.cashier", :data => [fragment, tags]) do
|
38
|
+
tags.each do |tag|
|
39
|
+
# store the fragment
|
40
|
+
adapter.store_fragment_in_tag(fragment, tag)
|
41
|
+
end
|
42
|
+
|
43
|
+
# now store the tag for book keeping
|
44
|
+
adapter.store_tags(tags)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: expire tags. expiring the keys 'assigned' to the tags you expire and removes the tags from the tags list
|
49
|
+
#
|
50
|
+
# tags - array of tags to expire.
|
51
|
+
#
|
52
|
+
# Examples
|
53
|
+
#
|
54
|
+
# Cashier.expire('tag1', 'tag2')
|
55
|
+
#
|
56
|
+
def expire(*tags)
|
57
|
+
return unless perform_caching?
|
58
|
+
|
59
|
+
# add tags of container fragments to expired tags list
|
60
|
+
tags = canonize_tags(tags)
|
61
|
+
containers = adapter.get_tags_containers(tags) || []
|
62
|
+
tags = (tags + containers).compact.uniq
|
63
|
+
|
64
|
+
ActiveSupport::Notifications.instrument("expire.cashier", :data => tags) do
|
65
|
+
# delete them from the cache
|
66
|
+
tags.each do |tag|
|
67
|
+
fragment_keys = adapter.get_fragments_for_tag(tag)
|
68
|
+
|
69
|
+
fragment_keys.each do |fragment_key|
|
70
|
+
Rails.cache.delete(fragment_key)
|
71
|
+
end
|
72
|
+
|
73
|
+
adapter.delete_tag(tag)
|
74
|
+
end
|
75
|
+
|
76
|
+
# now remove them from the list
|
77
|
+
# of stored tags
|
78
|
+
adapter.remove_tags(tags)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Public: returns the array of tags stored in the tags store.
|
83
|
+
#
|
84
|
+
#
|
85
|
+
# Examples
|
86
|
+
#
|
87
|
+
# Cashier.tags
|
88
|
+
# # => ['tag1', 'tag2']
|
89
|
+
#
|
90
|
+
def tags
|
91
|
+
adapter.tags
|
92
|
+
end
|
93
|
+
|
94
|
+
# Public: clears the tags.
|
95
|
+
#
|
96
|
+
#
|
97
|
+
# Examples
|
98
|
+
#
|
99
|
+
# Cashier.clear
|
100
|
+
#
|
101
|
+
def clear
|
102
|
+
ActiveSupport::Notifications.instrument("clear.cashier") do
|
103
|
+
adapter.clear
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Public: get all the keys names as an array.
|
108
|
+
#
|
109
|
+
#
|
110
|
+
# Examples
|
111
|
+
#
|
112
|
+
# Cachier.keys
|
113
|
+
# # => ['key1', 'key2', 'key3']
|
114
|
+
#
|
115
|
+
def keys
|
116
|
+
adapter.keys
|
117
|
+
end
|
118
|
+
|
119
|
+
# Public: get all the keys for a specific tag as an array.
|
120
|
+
#
|
121
|
+
#
|
122
|
+
# Examples
|
123
|
+
#
|
124
|
+
# Cashier.tags_for('tag1')
|
125
|
+
# # => ['key1', 'key2', 'key3']
|
126
|
+
#
|
127
|
+
def keys_for(tag)
|
128
|
+
tag = canonize_tags(tag)
|
129
|
+
adapter.get_fragments_for_tag(tag)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Public: adapter which is used by cashier.
|
133
|
+
#
|
134
|
+
# Examples
|
135
|
+
#
|
136
|
+
# Cashier.adapter
|
137
|
+
# # => Cashier::Adapters::CacheStore
|
138
|
+
#
|
139
|
+
# Cashier.adapter
|
140
|
+
# # => Cashier::Adapters::RedisStore
|
141
|
+
#
|
142
|
+
def adapter
|
143
|
+
if @@adapter == :cache_store
|
144
|
+
Cashier::Adapters::CacheStore
|
145
|
+
else
|
146
|
+
Cashier::Adapters::RedisStore
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Public: set the adapter the Cashier module will use to store the keys
|
151
|
+
#
|
152
|
+
# cache_adapter - :cache_store / :redis_store
|
153
|
+
#
|
154
|
+
# Examples
|
155
|
+
#
|
156
|
+
# Cashier.adapter = :redis_store
|
157
|
+
#
|
158
|
+
def adapter=(cache_adapter)
|
159
|
+
@@adapter = cache_adapter
|
160
|
+
end
|
161
|
+
|
162
|
+
# Public: add tags of a container fragment into the current container stack (used internally by ActiveSupport::Notifications)
|
163
|
+
#
|
164
|
+
# cache_adapter - :cache_store / :redis_store
|
165
|
+
#
|
166
|
+
# Examples
|
167
|
+
#
|
168
|
+
# Cashier.push_container(['section2'])
|
169
|
+
#
|
170
|
+
def push_container(*tags)
|
171
|
+
return unless perform_caching?
|
172
|
+
@@container_stack ||= []
|
173
|
+
tags = canonize_tags(tags)
|
174
|
+
adapter.add_tags_containers(tags, @@container_stack)
|
175
|
+
@@container_stack.push tags
|
176
|
+
end
|
177
|
+
|
178
|
+
# Public: remove tags of a container fragment from the current container stack
|
179
|
+
#
|
180
|
+
# cache_adapter - :cache_store / :redis_store
|
181
|
+
#
|
182
|
+
# Examples
|
183
|
+
#
|
184
|
+
# Cashier.pop_container()
|
185
|
+
#
|
186
|
+
def pop_container()
|
187
|
+
return unless perform_caching?
|
188
|
+
@@container_stack ||= []
|
189
|
+
container = @@container_stack.pop
|
190
|
+
container
|
191
|
+
end
|
192
|
+
|
193
|
+
# Public: get the tags of containers for the given fragment tags
|
194
|
+
#
|
195
|
+
# cache_adapter - :cache_store / :redis_store
|
196
|
+
#
|
197
|
+
# Examples
|
198
|
+
#
|
199
|
+
# Cashier.get_containers(['article1'])
|
200
|
+
# # => ['section2', 'section3']
|
201
|
+
#
|
202
|
+
def get_containers(tags)
|
203
|
+
tags = canonize_tags(tags)
|
204
|
+
adapter.get_tags_containers(tags)
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# Public: canonize tags: convert ActiveRecord objects to string (inc. id)
|
209
|
+
#
|
210
|
+
# cache_adapter - :cache_store / :redis_store
|
211
|
+
#
|
212
|
+
# Examples
|
213
|
+
#
|
214
|
+
# Cashier.canonize_tags([1, :a, Article.find(123)])
|
215
|
+
# # => [1, :a, "Article-123"]
|
216
|
+
#
|
217
|
+
def canonize_tags(tags)
|
218
|
+
tags = [tags || []].flatten
|
219
|
+
tags.map do |tag|
|
220
|
+
if tag.is_a?(ActiveRecord::Base)
|
221
|
+
"#{tag.class.name}-#{tag.to_param}"
|
222
|
+
else
|
223
|
+
tag
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
require 'rails'
|
233
|
+
require 'cashier/railtie'
|
234
|
+
require 'cashier/adapters/cache_store'
|
235
|
+
require 'cashier/adapters/redis_store'
|
236
|
+
|
237
|
+
# Connect cashier up to the low level Rails cache:
|
238
|
+
|
239
|
+
# When Rails cache is missing a fragment, it is going to be rendered - add its tags to the container stack
|
240
|
+
ActiveSupport::Notifications.subscribe("cache_read.active_support") do |*args|
|
241
|
+
payload = ActiveSupport::Notifications::Event.new(*args).payload
|
242
|
+
tag = payload[:tag]
|
243
|
+
# if not a cache hit, we're going to build the fragment - add to container stack
|
244
|
+
Cashier.push_container(*tag) if tag && !payload[:hit]
|
245
|
+
end
|
246
|
+
|
247
|
+
# When a fragment was written into Rails cache it is now rendered and done - remove its tags from the container stack
|
248
|
+
ActiveSupport::Notifications.subscribe("cache_write.active_support") do |*args|
|
249
|
+
payload = ActiveSupport::Notifications::Event.new(*args).payload
|
250
|
+
tag = payload[:tag]
|
251
|
+
|
252
|
+
if tag
|
253
|
+
Cashier.store_fragment(payload[:key], tag)
|
254
|
+
Cashier.pop_container
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|