benny_cache 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
19
+ benny_cache.iml
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in benny_cache.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Steven Hilton
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,281 @@
1
+ # BennyCache
2
+
3
+ BennyCache is a model, data and method caching library that uses the ActiveRecord API, but does not try to get in between you and
4
+ ActiveRecord. The main motivation for creating BennyCache was to make it possible to implicitly and efficiently
5
+ clear the cache of one record when changes to related records are made.
6
+
7
+ For example, suppose an Agent has a set of Items in its Inventory. We have an agent's data is populated in the cache,
8
+ and we want to change the agent's inventory, either add a new item, or update the remaining ammo of a weapon.
9
+ With BennyCache, we can update an individual Item and the Agent's inventory cache is cleared. We do not have
10
+ to load the agent into memory, and the agent's basic information remains unchanged in the cache -- only the Inventory
11
+ data for that Agent is flushed.
12
+
13
+ BennyCache uses Rails.cache if available, or you can provide your own caching engine. Otherwise, it uses an
14
+ internal memory cache by default. The internal memory cache is meant for testing and evaluation purposes only.
15
+
16
+
17
+ ### Contrasting BennyCache with other similar caching tools:
18
+
19
+ * [CacheFu](https://github.com/defunkt/cache_fu) or [Rails 3 compatible fork](https://github.com/kreetitech/cache_fu)
20
+
21
+ * [CacheMoney](https://github.com/nkallen/cache-money)
22
+
23
+ * [CacheMethod](https://github.com/seamusabshere/cache_method)
24
+
25
+ __Differences__
26
+
27
+ - BennyCache is marginally aware of ActiveRecord, but doesn't touch the internals, so it should be
28
+ forward-compatible, or as much as it can be.
29
+ - Usage of BennyCache is explicit: It doesn't try to do hide itself from the code.
30
+ - Method caching in BennyCache was a bit inspired by CacheMethod. CacheMethod is a more robust solution for method
31
+ caching, especially if you are passing complex data structures as parameters.
32
+
33
+ ## Installation
34
+
35
+ Add this line to your application's Gemfile:
36
+
37
+ gem 'benny_cache'
38
+
39
+ And then execute:
40
+
41
+ $ bundle
42
+
43
+ Or install it yourself as:
44
+
45
+ $ gem install benny_cache
46
+
47
+ ## Usage
48
+
49
+ BennyCache will cache three separate but related types of information.
50
+
51
+ * __Model Cache__ Model Caches are a cached representation of a model
52
+ * __Data Cache__ Data Caches are cached representation of data related to a model, but not the model itself.
53
+ * __Method Cache__ Method Caches are cached result of a call to model method.
54
+
55
+ Model, data and method caches are independent of each other. A model is cached and uncached independently
56
+ of the data cache or method cache related ot the model. You can cache a model method without
57
+ caching the model itself.
58
+
59
+ Another important concept in BennyCache is the __Related Index__ When related indexes are defined, a link is
60
+ created between one model and another model's data or method caches. For example, when a "skill" (an instance
61
+ of the Skill class) is upgraded, the related Robot will automatically have its skills method cache cleared,
62
+ without having to directly reference a Robot model. The relationship is created by defining a method_index in
63
+ the Robot class, and a related_index in the Skill class.
64
+
65
+ ### Defining the cache store
66
+
67
+ By default, BennyCache uses Rails.cache store if available. You can explicitly set the cache store by calling:
68
+
69
+ BennyCache::Config.store = Rails.cache
70
+
71
+ The cache is expected to support the `#read`, `#write`, `#delete` and `#fetch` methods of the
72
+ `ActiveSuport::Cache::Store` interface.
73
+
74
+ If Rails.cache is not defined, and you don't explicitly initialize a cache, BennyCache uses an internal
75
+ `BennyCache::Cache` object, which is just an in-memory key-value hash. The internal cache
76
+ object is not intended for production use.
77
+
78
+
79
+ ### Model Indexes
80
+
81
+ Include `BennyCache::Model` into your ActiveRecord model and declare your indexes:
82
+
83
+ class Robot < ActiveRecord::Base
84
+ include BennyCache::Model
85
+ benny_model_index :user_id
86
+ end
87
+
88
+ class Location < ActiveRecord::Base
89
+ include BennyCache::Model
90
+ benny_model_index [:x, :y]
91
+ end
92
+
93
+
94
+ Once that is done, loading item from the cache is easy:
95
+
96
+ robot = Robot.benny_model_cache(user_id: current_user.id)
97
+
98
+ location = Location.benny_model_cache(123) # cached by primary key
99
+ location = Location.benny_model_cache(x: 30, y: 50) # cached by coordinates
100
+
101
+ Calling these will populate the my_model object in the cache with different keys. If you
102
+ change `location` and call `#save()` or `#destroy()`, all instances will be cleared from the cache.
103
+
104
+
105
+ ### Data Indexes
106
+
107
+ A data index is a piece of data related to a specific model that is created by a block of code. Data caches
108
+ are maintained separate from the model cache itself. Data caches are useful for data about a model
109
+ that is expensive to load and/or calculate, or changes infrequently in relation to the model itself.
110
+
111
+ Data caches and model caches are independent of each other. A model cache is maintained independently
112
+ of any data caches related to the model.
113
+
114
+ To use data indexes, you must first declare a data index key. When loading the key, you also pass a block that
115
+ is used to populate the cache.
116
+
117
+ class MyModel < ActiveRecord::Base
118
+ include BennyCache::Model
119
+
120
+ benny_data_index :my_data_index
121
+ end
122
+
123
+ Then, when using the model...
124
+
125
+ my_model.benny_data_cache(:my_data_index) {
126
+ self.expensive_data_to_calculate()
127
+ }
128
+
129
+ The return value of self.expensive_data_to_calculate() is used to populate the cache for the :my_data_index key.
130
+ Further calls my_model.benny_data_cache(:my_data_index) will return the cached value until the cache is cleared.
131
+ Usually, some external process will invalidate a data cache.
132
+
133
+
134
+ #### Clearing data index cache
135
+ To manually clear a data index cache for a model, you need the primary key
136
+ of the model and use a class method. You do not need to instantiate the model.
137
+
138
+ MyModel.benny_data_cache_delete(123, :my_data_index)
139
+
140
+ If changes to one model might need to invalidate the data caches of another mother, this can be managed automatically
141
+ with the `BennyCache::Related` mixin describe below.
142
+
143
+ ### Method Indexes
144
+
145
+ A method index is a cached result of a call to a model method. Like Data caches, Method caches
146
+ are created and deleted separate from the model cache itself. Also like Data caches, Method caches are useful
147
+ for caching data about a model that is expensive to load or calculate, or changes at different intervals in
148
+ relation to the model itself.
149
+
150
+ To use method indexes, declare a method index. Method indexes use ruby method aliasing, so the
151
+ source method must be defined *before* declaring the method index.
152
+
153
+ class MyModel < ActiveRecord::Base
154
+ include BennyCache::Model
155
+
156
+ def method_name # source method first
157
+ [expensive_code]
158
+ end
159
+ benny_method_index :method_name # index second
160
+ end
161
+
162
+ Arguments passed to a cached method are hashed to create a unique signature per parameter list, and the
163
+ cached value is based on the method name and the args hash sig. In the following example:
164
+
165
+ rv1 = agent.method_name :foo
166
+ rv2 = agent.method_name :bar
167
+
168
+ If agent#method_name is declared as a method_index, rv1 and and rv2 will be two different cached values.
169
+
170
+ #### Local caching
171
+
172
+ When a BennyCache method index is called, benny cache keeps an model-specific copy of the cache in local memory,
173
+ so multiple calls to the same method return the same object with the same object_id.
174
+ This supports in process updates to the data. For example:
175
+
176
+ rv1 = agent.method_name #=> [:a, :b, :c]
177
+ rv1.push :d
178
+ rv2 = agent.method_name #=> [:a, :b, :c, :d]
179
+
180
+ rv1.size == 4 #=> true
181
+ rv1.object_id == rv2.object_id #=> true
182
+
183
+ This behavior works for my needs, but may not suit all users. I may add the ability to change this behavior.
184
+
185
+ I have not fully tested the benny_method_index functionality with all of the ActiveRelation's varied functionality.
186
+ Using the two together and exercising different parts of ActiveRelation may have unexpected results. However, in my
187
+ simple case, where I use basic :has_many relationships and don't use #where, #include, etc, it works the way I need
188
+ it to work.
189
+
190
+ Simple use cases should work without issue, but passing complex data structures to cached methods may
191
+ confuse BennyCache. For more robust method caching, checkout out [CacheMethod](https://github.com/seamusabshere/cache_method).
192
+
193
+
194
+ #### Clearing model index cache
195
+ Method indexes will cache data on per-args_hash basis, but clearing the cache for a model index is more of a shotgun
196
+ approach: clearing a model index cache will clear _all_ cached data for all args hashes.
197
+
198
+ To manually clear a method index cache for a model, you need the primary key of the model, the method name, and use a
199
+ class method. You do not need to instantiate the model.
200
+
201
+ MyModel.benny_method_cache_delete(123, :method_name)
202
+
203
+ If changes to one model might need to invalidate the data caches of another mother, this can be managed
204
+ with the `BennyCache::Related` mixin.
205
+
206
+
207
+ ### Related Indexes
208
+
209
+ Related indexes are used when you know that a change to one model will need to invalidate
210
+ a data or method cache of another model. Defining a related index will make the cache invalidation automatic.
211
+
212
+ BennyCache::Related installs and after_save/after_destroy callback to clear the related data indexes of other models.
213
+
214
+ Defined the two classes like so, a class that uses BennyCache::Model with a data_index, and a class that
215
+ uses BennyCache::Related that defines a benny_related_index that points to the main class's date_index
216
+
217
+ class MainModel < ActiveRecord::Base
218
+ has_many :related_models
219
+
220
+ include BennyCache::Model
221
+
222
+ benny_data_index :my_related_items # data cache
223
+ benny_model_index :my_related_method # method cache
224
+
225
+ end
226
+
227
+ class RelatedModel < ActiveRecord::Base
228
+ belongs_to :main_model
229
+
230
+ include BennyCache::Related
231
+ benny_related_index ":main_model_id/MainModel/my_related_items"
232
+ benny_related_method ":main_model_id/MainModel/my_related_method"
233
+
234
+ end
235
+
236
+ The benny_related_index call sets up all RelatedModel instances to clear the `:my_related_items` data index of the
237
+ MainModel instance withe a primary key of `related_model.main_model_id` whenever the RelatedModel instance is created,
238
+ updated, or deleted.
239
+
240
+ The benny_related_method call sets up all RelatedModel instances to clear the `:my_related_method` method cache data of the
241
+ MainModel instance withe a primary key of `related_model.main_model_id` whenever the RelatedModel instance is created,
242
+ updated, or deleted.
243
+
244
+ ### Cache namespacing
245
+
246
+ Internally, BennyCache uses the class name of the classes using BennyCache::Model as part of the key name for
247
+ caching. Sometimes that might not be what you want, and will need to explicitly declare the namespace. This happens
248
+ when you use classes that take advantage of ActiveRecord's single table inheritance:
249
+
250
+
251
+ class Location < ActiveRecord::Base
252
+ include BennyCache::Model
253
+ benny_cache_ns 'Location'
254
+ end
255
+
256
+ class IndustrialComplex < Location
257
+
258
+ end
259
+
260
+ class RecreationArea < Location
261
+
262
+ end
263
+
264
+ By declaring the above namespace, these two calls reference the same key:
265
+
266
+ l = Location.benny_cache_model 123
267
+ l = RecreationArea.benny_cache_model 123
268
+
269
+
270
+ ## Bugs
271
+
272
+ There are probably bugs. Until there is an issue tracking system, send email to
273
+ `mshiltonj@gmail.com` and put 'BennyCache' in the subject.
274
+
275
+ ## Contributing
276
+
277
+ 1. Fork it
278
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
279
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
280
+ 4. Push to the branch (`git push origin my-new-feature`)
281
+ 5. Create new Pull Request
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env rake
2
+ require 'rspec'
3
+ require "bundler/gem_tasks"
4
+ require 'rspec/core/rake_task'
5
+
6
+
7
+ RSpec::Core::RakeTask.new('spec')
8
+
9
+ namespace :spec do
10
+ desc "Create rspec coverage"
11
+ task :coverage do
12
+ ENV['COVERAGE'] = 'true'
13
+ Rake::Task['spec'].execute
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/benny_cache/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Steven Hilton"]
6
+ gem.email = ["mshiltonj@gmail.com"]
7
+ gem.description = %q{A model caching library with indirect cached clearing}
8
+ gem.summary = %q{A model caching library with indirect cached clearing}
9
+ gem.homepage = "https://github.com/mshiltonj/benny_cache"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "benny_cache"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = BennyCache::VERSION
17
+
18
+
19
+ gem.add_development_dependency('rspec')
20
+ gem.add_development_dependency('mocha')
21
+ gem.add_development_dependency('ZenTest')
22
+ gem.add_development_dependency('simplecov')
23
+ end
@@ -0,0 +1,7 @@
1
+ require "benny_cache/version"
2
+ require "benny_cache/cache"
3
+ require "benny_cache/config"
4
+ require "benny_cache/base"
5
+ require "benny_cache/model"
6
+ require "benny_cache/related"
7
+
@@ -0,0 +1,36 @@
1
+ module BennyCache
2
+ module Base
3
+ def self.included(base) #:nodoc:
4
+ base.extend(BennyCache::Base::ClassMethods)
5
+ end
6
+
7
+ def benny_constantize(string) #:nodoc:
8
+ if string.respond_to?(:constantize)
9
+ # use ActiveSupport directly if possible
10
+ string.constantize
11
+ else
12
+ names = string.split('::')
13
+ names.shift if names.empty? || names.first.empty?
14
+ constant = Object
15
+ names.each do |name|
16
+ constant = constant.const_get(name)
17
+ end
18
+ constant
19
+ end
20
+ end
21
+
22
+
23
+ module ClassMethods
24
+
25
+ def benny_model_ns(ns)
26
+ self.class_variable_set(:@@BENNY_MODEL_NS, ns.to_s)
27
+ end
28
+
29
+ def get_benny_model_ns #:nodoc:
30
+ ns = self.class_variable_defined?(:@@BENNY_MODEL_NS) ? self.class_variable_get(:@@BENNY_MODEL_NS) : self.to_s
31
+ "Benny/#{ns}"
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ module BennyCache
2
+ class Cache
3
+ def initialize
4
+ @cache = {}
5
+ end
6
+
7
+ def fetch(key, options = nil, &block)
8
+ val = @cache[key]
9
+
10
+ if val.nil? && block_given?
11
+ val = block.call()
12
+ @cache[key] = val
13
+ end
14
+
15
+ begin
16
+ val = val.dup unless val.nil?
17
+ rescue TypeError
18
+ #okay
19
+ end
20
+ val
21
+
22
+ end
23
+
24
+ def read(key, options = nil)
25
+ val = @cache[key]
26
+ val = val.dup unless val.nil?
27
+
28
+ end
29
+
30
+ def write(key, val, options = nil)
31
+ @cache[key] = val.dup
32
+ return true
33
+ end
34
+
35
+ def delete(key, options = nil)
36
+ @cache.delete(key)
37
+ end
38
+
39
+ def clear(options = nil)
40
+ @cache = {}
41
+ end
42
+
43
+ end
44
+ end