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