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