cashier-ftbpro 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +9 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +20 -0
  4. data/README.md +195 -0
  5. data/Rakefile +11 -0
  6. data/cashier.gemspec +27 -0
  7. data/lib/cashier.rb +257 -0
  8. data/lib/cashier/adapters/cache_store.rb +66 -0
  9. data/lib/cashier/adapters/redis_store.rb +62 -0
  10. data/lib/cashier/cucumber.rb +6 -0
  11. data/lib/cashier/matchers.rb +38 -0
  12. data/lib/cashier/railtie.rb +9 -0
  13. data/lib/cashier/version.rb +3 -0
  14. data/spec/dummy/.gitignore +4 -0
  15. data/spec/dummy/Gemfile +31 -0
  16. data/spec/dummy/README +256 -0
  17. data/spec/dummy/Rakefile +7 -0
  18. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  19. data/spec/dummy/app/controllers/home_controller.rb +7 -0
  20. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  21. data/spec/dummy/app/helpers/home_helper.rb +2 -0
  22. data/spec/dummy/app/views/home/index.html.erb +1 -0
  23. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  24. data/spec/dummy/config.ru +4 -0
  25. data/spec/dummy/config/application.rb +44 -0
  26. data/spec/dummy/config/boot.rb +10 -0
  27. data/spec/dummy/config/environment.rb +5 -0
  28. data/spec/dummy/config/environments/development.rb +25 -0
  29. data/spec/dummy/config/environments/production.rb +46 -0
  30. data/spec/dummy/config/environments/test.rb +31 -0
  31. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  32. data/spec/dummy/config/initializers/inflections.rb +10 -0
  33. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  34. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  35. data/spec/dummy/config/initializers/session_store.rb +8 -0
  36. data/spec/dummy/config/locales/en.yml +5 -0
  37. data/spec/dummy/config/routes.rb +60 -0
  38. data/spec/dummy/db/seeds.rb +7 -0
  39. data/spec/dummy/lib/tasks/.gitkeep +0 -0
  40. data/spec/dummy/public/404.html +26 -0
  41. data/spec/dummy/public/422.html +26 -0
  42. data/spec/dummy/public/500.html +26 -0
  43. data/spec/dummy/public/favicon.ico +0 -0
  44. data/spec/dummy/public/images/rails.png +0 -0
  45. data/spec/dummy/public/javascripts/application.js +2 -0
  46. data/spec/dummy/public/javascripts/controls.js +965 -0
  47. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  48. data/spec/dummy/public/javascripts/effects.js +1123 -0
  49. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  50. data/spec/dummy/public/javascripts/rails.js +191 -0
  51. data/spec/dummy/public/robots.txt +5 -0
  52. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  53. data/spec/dummy/script/rails +6 -0
  54. data/spec/dummy/test/performance/browsing_test.rb +9 -0
  55. data/spec/dummy/test/test_helper.rb +13 -0
  56. data/spec/dummy/vendor/plugins/.gitkeep +0 -0
  57. data/spec/integration/rails_cache_integration_spec.rb +68 -0
  58. data/spec/integration/rails_configuration_spec.rb +9 -0
  59. data/spec/lib/cashier/adapters/cache_store_spec.rb +92 -0
  60. data/spec/lib/cashier/adapters/redis_store_spec.rb +107 -0
  61. data/spec/lib/cashier_spec.rb +154 -0
  62. data/spec/spec_helper.rb +48 -0
  63. metadata +255 -0
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ spec/dummy/log
2
+ spec/dummy/tmp/pids
3
+ spec/dummy/tmp/cache
4
+ Gemfile.lock
5
+ .rvmrc
6
+ coverage*
7
+ .tags
8
+ .tags_sorted_by_file
9
+ .rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ..gemspec
4
+ gemspec
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
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec) do |spec|
8
+ spec.pattern = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+ task :default => :spec
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
+