ultracache 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rvmrc +71 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +55 -0
- data/README.md +130 -0
- data/Rakefile +20 -0
- data/lib/ultracache.rb +29 -0
- data/lib/ultracache/cached.rb +102 -0
- data/lib/ultracache/configurations.rb +79 -0
- data/lib/ultracache/macro.rb +40 -0
- data/lib/ultracache/models/mongoid_extension.rb +9 -0
- data/lib/ultracache/railtie.rb +16 -0
- data/lib/ultracache/relationship.rb +24 -0
- data/lib/ultracache/relationship/belongs_as_cached_queue.rb +96 -0
- data/lib/ultracache/relationship/has_cached_attribute.rb +51 -0
- data/lib/ultracache/relationship/has_cached_queue.rb +31 -0
- data/lib/ultracache/relationships.rb +41 -0
- data/lib/ultracache/serializer/base.rb +19 -0
- data/lib/ultracache/serializer/json_serializer.rb +15 -0
- data/lib/ultracache/storage/redis.rb +63 -0
- data/lib/ultracache/version.rb +3 -0
- data/spec/models/admin.rb +13 -0
- data/spec/models/customer.rb +7 -0
- data/spec/models/order.rb +11 -0
- data/spec/models/person.rb +15 -0
- data/spec/models/post.rb +9 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/unit/cached_spec.rb +51 -0
- data/spec/unit/configurations_spec.rb +49 -0
- data/spec/unit/relationship/belongs_as_cached_queue_spec.rb +18 -0
- data/spec/unit/relationship/has_cached_attribute_spec.rb +20 -0
- data/spec/unit/relationship/has_cached_queue_spec.rb +20 -0
- data/spec/unit/relationship_spec.rb +12 -0
- data/spec/unit/relationships_spec.rb +72 -0
- data/spec/unit/serializer/json_serializer_spec.rb +24 -0
- data/ultracache.gemspec +30 -0
- metadata +184 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
7
|
+
environment_id="ruby-1.9.3-p0@ultracache"
|
8
|
+
|
9
|
+
#
|
10
|
+
# Uncomment following line if you want options to be set only for given project.
|
11
|
+
#
|
12
|
+
# PROJECT_JRUBY_OPTS=( --1.9 )
|
13
|
+
#
|
14
|
+
# The variable PROJECT_JRUBY_OPTS requires the following to be run in shell:
|
15
|
+
#
|
16
|
+
# chmod +x ${rvm_path}/hooks/after_use_jruby_opts
|
17
|
+
#
|
18
|
+
|
19
|
+
#
|
20
|
+
# First we attempt to load the desired environment directly from the environment
|
21
|
+
# file. This is very fast and efficient compared to running through the entire
|
22
|
+
# CLI and selector. If you want feedback on which environment was used then
|
23
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
24
|
+
#
|
25
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
26
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
27
|
+
then
|
28
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
29
|
+
|
30
|
+
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
31
|
+
then
|
32
|
+
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
33
|
+
fi
|
34
|
+
else
|
35
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
36
|
+
if ! rvm --create "$environment_id"
|
37
|
+
then
|
38
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
39
|
+
return 1
|
40
|
+
fi
|
41
|
+
fi
|
42
|
+
|
43
|
+
#
|
44
|
+
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
45
|
+
# it be automatically loaded. Uncomment the following and adjust the filename if
|
46
|
+
# necessary.
|
47
|
+
#
|
48
|
+
# filename=".gems"
|
49
|
+
# if [[ -s "$filename" ]]
|
50
|
+
# then
|
51
|
+
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
52
|
+
# fi
|
53
|
+
|
54
|
+
# If you use bundler, this might be useful to you:
|
55
|
+
# if [[ -s Gemfile ]] && ! command -v bundle >/dev/null
|
56
|
+
# then
|
57
|
+
# printf "The rubygem 'bundler' is not installed. Installing it now.\n"
|
58
|
+
# gem install bundler
|
59
|
+
# fi
|
60
|
+
# if [[ -s Gemfile ]] && command -v bundle
|
61
|
+
# then
|
62
|
+
# bundle install
|
63
|
+
# fi
|
64
|
+
|
65
|
+
if [[ $- == *i* ]] # check for interactive shells
|
66
|
+
then
|
67
|
+
echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
|
68
|
+
else
|
69
|
+
echo "Using: $GEM_HOME" # don't use colors in interactive shells
|
70
|
+
fi
|
71
|
+
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ultracache (0.1.0)
|
5
|
+
activemodel (~> 3.0)
|
6
|
+
activesupport (~> 3.0)
|
7
|
+
json
|
8
|
+
redis (~> 2.2)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: http://rubygems.org/
|
12
|
+
specs:
|
13
|
+
activemodel (3.1.3)
|
14
|
+
activesupport (= 3.1.3)
|
15
|
+
builder (~> 3.0.0)
|
16
|
+
i18n (~> 0.6)
|
17
|
+
activesupport (3.1.3)
|
18
|
+
multi_json (~> 1.0)
|
19
|
+
bson (1.5.2)
|
20
|
+
bson_ext (1.5.2)
|
21
|
+
bson (= 1.5.2)
|
22
|
+
builder (3.0.0)
|
23
|
+
diff-lcs (1.1.3)
|
24
|
+
i18n (0.6.0)
|
25
|
+
json (1.6.4)
|
26
|
+
metaclass (0.0.1)
|
27
|
+
mocha (0.10.0)
|
28
|
+
metaclass (~> 0.0.1)
|
29
|
+
mongo (1.5.2)
|
30
|
+
bson (= 1.5.2)
|
31
|
+
mongoid (2.4.0)
|
32
|
+
activemodel (~> 3.1)
|
33
|
+
mongo (~> 1.3)
|
34
|
+
tzinfo (~> 0.3.22)
|
35
|
+
multi_json (1.0.4)
|
36
|
+
redis (2.2.2)
|
37
|
+
rspec (2.7.0)
|
38
|
+
rspec-core (~> 2.7.0)
|
39
|
+
rspec-expectations (~> 2.7.0)
|
40
|
+
rspec-mocks (~> 2.7.0)
|
41
|
+
rspec-core (2.7.1)
|
42
|
+
rspec-expectations (2.7.0)
|
43
|
+
diff-lcs (~> 1.1.2)
|
44
|
+
rspec-mocks (2.7.0)
|
45
|
+
tzinfo (0.3.31)
|
46
|
+
|
47
|
+
PLATFORMS
|
48
|
+
ruby
|
49
|
+
|
50
|
+
DEPENDENCIES
|
51
|
+
bson_ext
|
52
|
+
mocha
|
53
|
+
mongoid
|
54
|
+
rspec (~> 2.6)
|
55
|
+
ultracache!
|
data/README.md
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
Ultracache
|
2
|
+
==========
|
3
|
+
|
4
|
+
Ultracache provides model-level interfaces for better abstraction of
|
5
|
+
your cached data. You can organize and store/fetch cached data easily
|
6
|
+
through the provided interfaces. You can boost up the response time of
|
7
|
+
your actions through appropriate caching of persisted data.
|
8
|
+
|
9
|
+
Ultracache uses a conventional key-value store as its backend. Currently
|
10
|
+
it only supports Redis, but we are planning to include other storages
|
11
|
+
like Memcached into our feature set.
|
12
|
+
|
13
|
+
Installation
|
14
|
+
------------
|
15
|
+
|
16
|
+
Add Ultracache to your `Gemfile`.
|
17
|
+
|
18
|
+
gem 'ultracache'
|
19
|
+
|
20
|
+
Or, install Ultracache manually with Rubygems.
|
21
|
+
|
22
|
+
|
23
|
+
Configurations
|
24
|
+
--------------
|
25
|
+
|
26
|
+
Generate an initializer for Ultracache in
|
27
|
+
`config/initializers/ultracache.rb`.
|
28
|
+
|
29
|
+
# config/initializers/ultracache.rb
|
30
|
+
|
31
|
+
Ultracache.config do |config|
|
32
|
+
config.storage = Ultracache::Storage::Redis.new(:urls => ['localhost'])
|
33
|
+
config.serializer = Ultracache::Serializer::JsonSerializer.new
|
34
|
+
end
|
35
|
+
|
36
|
+
Usage
|
37
|
+
-----
|
38
|
+
|
39
|
+
Ultracache associates cache with your model objects. Model objects can
|
40
|
+
have cached attributes or queues of other cached models. You can decide
|
41
|
+
appropriate method for caching which meets your requirements.
|
42
|
+
|
43
|
+
### Cached Attributes
|
44
|
+
|
45
|
+
You need to mixin `Ultracache::Cached` module into your model objects to
|
46
|
+
which you want to associate your cache.
|
47
|
+
|
48
|
+
Cached attributes is the simplest form to associate caches with your
|
49
|
+
models. Here's an example:
|
50
|
+
|
51
|
+
class Person
|
52
|
+
include Ultracache::Cached
|
53
|
+
|
54
|
+
has_cached_attribute :cached_colleagues do |obj|
|
55
|
+
obj.colleagues.as_json(:with_contact => true)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Keep in mind that return value of presented block is serialized to
|
60
|
+
a string by the serailizer specified in configurations. If block is not
|
61
|
+
presented, Ultracache tries to serialize the object with `as_json`
|
62
|
+
method.
|
63
|
+
|
64
|
+
class Person
|
65
|
+
include Ultracache::Cached
|
66
|
+
|
67
|
+
has_cached_attribute :serialized_string # Objects are serialized with Person#as_json
|
68
|
+
end
|
69
|
+
|
70
|
+
Cached attributes are useful when cost to generate the desired data is
|
71
|
+
high, like JSON serializations. Once we have noticed severe performance
|
72
|
+
degradation caused by plain JSON serializations.
|
73
|
+
|
74
|
+
### Cached Queues
|
75
|
+
|
76
|
+
Cached queue is suitable when your actions require collection of
|
77
|
+
objects, like the most index actions. Like `has_many` relationship of
|
78
|
+
ActiveRecord, cached queues are made from a relationship of two model
|
79
|
+
classes.
|
80
|
+
|
81
|
+
class Person
|
82
|
+
include Ultracache::Cached
|
83
|
+
|
84
|
+
has_cached_queue :notifications
|
85
|
+
end
|
86
|
+
|
87
|
+
class Notification
|
88
|
+
include Ultracache::Cached
|
89
|
+
|
90
|
+
cached_queue_in :person do |obj|
|
91
|
+
{
|
92
|
+
:id => obj.id,
|
93
|
+
:content => obj.content
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Cached queues are especially useful when cost to fetch additional data
|
99
|
+
from your persistent storage is significantly high. Queuries including
|
100
|
+
referenced relationships of MongoDB or heavy join operations fit for
|
101
|
+
this type of concerns.
|
102
|
+
|
103
|
+
p = Person.find params[:id]
|
104
|
+
notifications = p.notifications
|
105
|
+
|
106
|
+
Note that entries in queues are stored as string. If you want to fetch
|
107
|
+
or modify part of the model objects, you need to deserialize them.
|
108
|
+
|
109
|
+
p = Person.find params[:id]
|
110
|
+
notifications = p.notifications :deserialized => true
|
111
|
+
|
112
|
+
notifications.first['content'] # Content cached in your queue previously
|
113
|
+
|
114
|
+
### Keeping them with Integrity
|
115
|
+
|
116
|
+
Lifecycle of caches generated by Ultracache are synchronized with it of
|
117
|
+
model objects. Ultracache adds `save_cache`, `update_cache`,
|
118
|
+
`destroy_cache` methods to models which include Ultracache::Cached.
|
119
|
+
These methods are registered as ActiveModel callbacks semantically
|
120
|
+
corresponding to each method.
|
121
|
+
|
122
|
+
By default, contents of cached attributes are updated along with status
|
123
|
+
of model objects they belong to. On the other hand, you can decide
|
124
|
+
whether entries in cached queues are updated or not.
|
125
|
+
|
126
|
+
Further Documentations
|
127
|
+
----------------------
|
128
|
+
|
129
|
+
Refer comments in source files for more detailed information. We are
|
130
|
+
preparing detailed documentations which will be published soon.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
desc 'Default: run specs.'
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
desc "Run specs"
|
8
|
+
RSpec::Core::RakeTask.new do |t|
|
9
|
+
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
10
|
+
# Put spec opts in a file named .rspec in root
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Generate code coverage"
|
14
|
+
RSpec::Core::RakeTask.new(:coverage) do |t|
|
15
|
+
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
16
|
+
t.rcov = true
|
17
|
+
t.rcov_opts = ['--exclude', 'spec']
|
18
|
+
end
|
19
|
+
|
20
|
+
Bundler::GemHelper.install_tasks
|
data/lib/ultracache.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'ultracache/configurations'
|
6
|
+
require 'ultracache/storage/redis'
|
7
|
+
require 'ultracache/serializer/base'
|
8
|
+
require 'ultracache/serializer/json_serializer'
|
9
|
+
require 'ultracache/macro'
|
10
|
+
require 'ultracache/cached'
|
11
|
+
require 'ultracache/relationships'
|
12
|
+
require 'ultracache/relationship'
|
13
|
+
require 'ultracache/relationship/belongs_as_cached_queue'
|
14
|
+
require 'ultracache/relationship/has_cached_queue'
|
15
|
+
require 'ultracache/relationship/has_cached_attribute'
|
16
|
+
|
17
|
+
if defined?(Rails)
|
18
|
+
require "ultracache/railtie"
|
19
|
+
end
|
20
|
+
|
21
|
+
module Ultracache
|
22
|
+
class << self
|
23
|
+
def config
|
24
|
+
yield Configurations
|
25
|
+
end
|
26
|
+
|
27
|
+
alias :configuration :config
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Ultracache
|
2
|
+
# This class should be included to model classes related to cache. It adds
|
3
|
+
# primitive methods, attributes and callbacks required for caching.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
#
|
7
|
+
# class Person < ActiveRecord::Base
|
8
|
+
# include Ultracache::Cached
|
9
|
+
#
|
10
|
+
# # Cache definitions ...
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Including this class adds a Relationships object which is stored into
|
14
|
+
# Ultracache::Configurations. Every relationship added to the class is
|
15
|
+
# represented by a Relationship object inside Relationships object regarding
|
16
|
+
# the class.
|
17
|
+
module Cached
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
include Macro
|
21
|
+
|
22
|
+
included do
|
23
|
+
extend ActiveModel::Callbacks
|
24
|
+
define_model_callbacks :save, :destroy
|
25
|
+
|
26
|
+
after_create :save_cache
|
27
|
+
after_update :update_cache
|
28
|
+
after_destroy :destroy_cache
|
29
|
+
|
30
|
+
unless Ultracache::Configurations.find_relationships(self, :strict => true)
|
31
|
+
Ultracache::Configurations.add_relationships(self)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Read cache from storage. Cache to read is specified by the `name` parameter.
|
36
|
+
# Relationship object should exist in Relationships related to the class.
|
37
|
+
def read_cache(name, options={})
|
38
|
+
relationship = self.class.relationships.find(name.to_sym)
|
39
|
+
strings = relationship.read_cache(self, options)
|
40
|
+
|
41
|
+
if options[:deserialized]
|
42
|
+
return strings.map do |str|
|
43
|
+
Ultracache::Configurations.serializer.deserialize str
|
44
|
+
end
|
45
|
+
else
|
46
|
+
return strings
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Save caches of all relationships assigned to the class. If `:only` option is
|
51
|
+
# provided, only caches specified in the option are saved.
|
52
|
+
#
|
53
|
+
# Example
|
54
|
+
#
|
55
|
+
# p = Person.find params[:id]
|
56
|
+
# p.save_cache # Saves all caches related to Person class
|
57
|
+
# p.save_cache :only => [:cached_name] # Saves cached_name only
|
58
|
+
#
|
59
|
+
# `save_cache` is registered as `after_save` callback for ActiveModel classes
|
60
|
+
# which mixin `Ultracache::Cached`.
|
61
|
+
def save_cache(options = {})
|
62
|
+
target = self.class.relationships.keys
|
63
|
+
target &= options[:only] if options[:only]
|
64
|
+
|
65
|
+
target.each do |name|
|
66
|
+
self.class.relationships[name].save_cache(self)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Remove all cache related to the object from the storage. `destroy_cache` is
|
71
|
+
# registered as `after_destroy` callback for ActiveModel classes which mixin
|
72
|
+
# `Ultracache::Cached`.
|
73
|
+
def destroy_cache
|
74
|
+
self.class.relationships.each do |name, relationship|
|
75
|
+
relationship.destroy_cache(self)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Updates all cache related to the class, like `save_cache` does. `:only`
|
80
|
+
# option also can be used for this method. This method is registered as
|
81
|
+
# `after_update` callback.
|
82
|
+
def update_cache(options = {})
|
83
|
+
target = self.class.relationships.keys
|
84
|
+
target &= options[:only] if options[:only]
|
85
|
+
|
86
|
+
target.each do |name|
|
87
|
+
self.class.relationships[name].update_cache(self)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
module ClassMethods
|
92
|
+
# Finds all Relationship objects associated with the class. The relationships
|
93
|
+
# include cache associations defined in parent classes of the caller class.
|
94
|
+
# A new Relationships object is created as return value of this method.
|
95
|
+
def relationships(options = {})
|
96
|
+
rs = Ultracache::Configurations.find_relationships(self, options)
|
97
|
+
return Ultracache::Configurations.add_relationships(self) unless rs
|
98
|
+
rs
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Ultracache
|
2
|
+
# This class contains configurations for Ultracache. Proper configurations
|
3
|
+
# should be set before caching objects and attributes you need.
|
4
|
+
#
|
5
|
+
# Put configurations for your Rails project into initializer:
|
6
|
+
#
|
7
|
+
# # config/initializers/ultracache.rb
|
8
|
+
# Ultracache.config do |config|
|
9
|
+
# config.storage = Ultracache::Storage::Redis.new
|
10
|
+
# config.serializer = Ultracache::Serializer::JsonSerializer.new
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Configurable attributes include `storage` and `serializer` for now.
|
14
|
+
class Configurations
|
15
|
+
class << self
|
16
|
+
# Setter for storage handler of Ultracache.
|
17
|
+
def storage=(storage)
|
18
|
+
@storage = storage
|
19
|
+
end
|
20
|
+
|
21
|
+
# Getter for storage handler of Ultracache.
|
22
|
+
def storage
|
23
|
+
@storage
|
24
|
+
end
|
25
|
+
|
26
|
+
# Setter for serializer of Ultracache.
|
27
|
+
# `Ultracache::Serializer::JsonSerializer` is set as default value.
|
28
|
+
def serializer=(serializer)
|
29
|
+
@serializer = serializer
|
30
|
+
end
|
31
|
+
|
32
|
+
# Getter for serializer of Ultracache.
|
33
|
+
def serializer
|
34
|
+
@serializer
|
35
|
+
end
|
36
|
+
|
37
|
+
def relationships_container
|
38
|
+
@relationships ||= Hash.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_relationships(klass)
|
42
|
+
unless relationships_container[klass]
|
43
|
+
rs = Relationships.new(klass)
|
44
|
+
relationships_container[klass] = rs
|
45
|
+
rs
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_relationships(klass, options = {})
|
52
|
+
rs_set = []
|
53
|
+
|
54
|
+
relationships_container.each do |k, rs|
|
55
|
+
if klass == k
|
56
|
+
return rs if options[:strict]
|
57
|
+
rs_set << rs
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
return nil if options[:strict]
|
62
|
+
|
63
|
+
relationships_container.each do |k, rs|
|
64
|
+
if klass <= k
|
65
|
+
rs_set << rs
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if rs_set.empty?
|
70
|
+
nil
|
71
|
+
else
|
72
|
+
rs_set.inject(Relationships.new(klass)) do |base, rs|
|
73
|
+
base.merge(rs)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|