ultracache 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.gitignore +2 -0
  2. data/.rvmrc +71 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +55 -0
  5. data/README.md +130 -0
  6. data/Rakefile +20 -0
  7. data/lib/ultracache.rb +29 -0
  8. data/lib/ultracache/cached.rb +102 -0
  9. data/lib/ultracache/configurations.rb +79 -0
  10. data/lib/ultracache/macro.rb +40 -0
  11. data/lib/ultracache/models/mongoid_extension.rb +9 -0
  12. data/lib/ultracache/railtie.rb +16 -0
  13. data/lib/ultracache/relationship.rb +24 -0
  14. data/lib/ultracache/relationship/belongs_as_cached_queue.rb +96 -0
  15. data/lib/ultracache/relationship/has_cached_attribute.rb +51 -0
  16. data/lib/ultracache/relationship/has_cached_queue.rb +31 -0
  17. data/lib/ultracache/relationships.rb +41 -0
  18. data/lib/ultracache/serializer/base.rb +19 -0
  19. data/lib/ultracache/serializer/json_serializer.rb +15 -0
  20. data/lib/ultracache/storage/redis.rb +63 -0
  21. data/lib/ultracache/version.rb +3 -0
  22. data/spec/models/admin.rb +13 -0
  23. data/spec/models/customer.rb +7 -0
  24. data/spec/models/order.rb +11 -0
  25. data/spec/models/person.rb +15 -0
  26. data/spec/models/post.rb +9 -0
  27. data/spec/spec_helper.rb +15 -0
  28. data/spec/unit/cached_spec.rb +51 -0
  29. data/spec/unit/configurations_spec.rb +49 -0
  30. data/spec/unit/relationship/belongs_as_cached_queue_spec.rb +18 -0
  31. data/spec/unit/relationship/has_cached_attribute_spec.rb +20 -0
  32. data/spec/unit/relationship/has_cached_queue_spec.rb +20 -0
  33. data/spec/unit/relationship_spec.rb +12 -0
  34. data/spec/unit/relationships_spec.rb +72 -0
  35. data/spec/unit/serializer/json_serializer_spec.rb +24 -0
  36. data/ultracache.gemspec +30 -0
  37. metadata +184 -0
@@ -0,0 +1,2 @@
1
+ pkg/*
2
+ *.gem
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
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
@@ -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!
@@ -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.
@@ -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
@@ -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