cache-machine 0.1.10 → 0.2.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 +3 -1
- data/.travis.yml +6 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +0 -2
- data/README.md +258 -0
- data/VERSION +1 -1
- data/cache-machine.gemspec +7 -22
- data/db/.gitignore +1 -0
- data/lib/cache-machine.rb +6 -2
- data/lib/cache_machine/adapter.rb +127 -0
- data/lib/cache_machine/adapters/rails.rb +58 -0
- data/lib/cache_machine/cache.rb +34 -85
- data/lib/cache_machine/cache/associations.rb +29 -0
- data/lib/cache_machine/cache/class_timestamp.rb +31 -0
- data/lib/cache_machine/cache/collection.rb +131 -0
- data/lib/cache_machine/cache/map.rb +97 -241
- data/lib/cache_machine/cache/mapper.rb +135 -0
- data/lib/cache_machine/cache/resource.rb +108 -0
- data/lib/cache_machine/cache/scope.rb +24 -0
- data/lib/cache_machine/cache/timestamp_builder.rb +58 -0
- data/lib/cache_machine/helpers/cache_helper.rb +3 -3
- data/lib/cache_machine/logger.rb +9 -8
- data/lib/cache_machine/railtie.rb +11 -0
- data/lib/cache_machine/tasks.rb +10 -0
- data/spec/fixtures.rb +11 -6
- data/spec/lib/cache_machine/adapters/rails_spec.rb +149 -0
- data/spec/lib/cache_machine/cache/associations_spec.rb +32 -0
- data/spec/lib/cache_machine/cache/class_timestam_spec.rb +29 -0
- data/spec/lib/cache_machine/cache/collection_spec.rb +106 -0
- data/spec/lib/cache_machine/cache/map_spec.rb +34 -0
- data/spec/lib/cache_machine/cache/mapper_spec.rb +61 -0
- data/spec/lib/cache_machine/cache/resource_spec.rb +86 -0
- data/spec/lib/cache_machine/cache/scope_spec.rb +50 -0
- data/spec/lib/cache_machine/cache/timestamp_builder_spec.rb +52 -0
- data/spec/spec_helper.rb +9 -3
- metadata +35 -61
- data/README.rdoc +0 -186
- data/spec/lib/cache_machine_spec.rb +0 -161
@@ -0,0 +1,135 @@
|
|
1
|
+
module CacheMachine
|
2
|
+
module Cache
|
3
|
+
require 'cache_machine/cache/collection'
|
4
|
+
require 'cache_machine/cache/resource'
|
5
|
+
require 'cache_machine/cache/class_timestamp'
|
6
|
+
|
7
|
+
# CacheMachine::Cache::Map.draw do
|
8
|
+
# resource Venue, :timestamp => false do # Says what Venue class should be used as a source of ids for map
|
9
|
+
# collection :events, :scopes => :active, :on => :after_save do # Says what every event should fill the map with venue ids and use callback to reset cache for every venue.
|
10
|
+
# member :upcoming_events # Says what this method also needs to be reset.
|
11
|
+
# members :similar_events, :festivals
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# resource Event # Says what Event class should use timestamp on update (same as resource Event :timestamp => true)
|
16
|
+
# end
|
17
|
+
|
18
|
+
class Mapper
|
19
|
+
DEFAULT_RESOURCE_OPTIONS = { :timestamp => true, :scopes => :scoped }
|
20
|
+
DEFAULT_COLLECTION_OPTIONS = { :on => :after_save, :scopes => :scoped }
|
21
|
+
|
22
|
+
attr_reader :cache_resource
|
23
|
+
attr_reader :scope
|
24
|
+
|
25
|
+
def initialize(&block)
|
26
|
+
change_scope! nil, :root
|
27
|
+
instance_eval(&block) if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Defines model as a source of ids for map.
|
31
|
+
#
|
32
|
+
# @param [Class] model
|
33
|
+
# @param [Hash]options
|
34
|
+
def resource(model, options = {}, &block)
|
35
|
+
scoped :root, :resource do
|
36
|
+
@cache_resource = model
|
37
|
+
|
38
|
+
unless @cache_resource.include? CacheMachine::Cache::Resource
|
39
|
+
@cache_resource.send :include, CacheMachine::Cache::Resource
|
40
|
+
end
|
41
|
+
|
42
|
+
options.reverse_merge! DEFAULT_RESOURCE_OPTIONS
|
43
|
+
|
44
|
+
# Scopes are used for filtering records what we do not want to store in cache-map.
|
45
|
+
@cache_resource.cache_scopes |= [*options[:scopes]]
|
46
|
+
|
47
|
+
# Timestamp is used for tracking changes in whole collection (outside any scope).
|
48
|
+
if options[:timestamp]
|
49
|
+
unless @cache_resource.include? CacheMachine::Cache::ClassTimestamp
|
50
|
+
@cache_resource.send(:include, CacheMachine::Cache::ClassTimestamp)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Hook on associated collections.
|
55
|
+
instance_eval(&block) if block_given?
|
56
|
+
|
57
|
+
# Register model as a cache-resource.
|
58
|
+
CacheMachine::Cache::Map.registered_models |= [@cache_resource]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# Adds callbacks to fill the map with model ids and uses callback to reset cache for every instance of the model.
|
65
|
+
#
|
66
|
+
# @param [String, Symbol] collection_name
|
67
|
+
# @param [Hash] options
|
68
|
+
def collection(collection_name, options = {}, &block)
|
69
|
+
reflection = @cache_resource.reflect_on_association(collection_name)
|
70
|
+
reflection or raise ArgumentError, "Relation '#{collection_name}' is not set on the class #{@cache_resource}"
|
71
|
+
|
72
|
+
scoped :resource, :collection do
|
73
|
+
options.reverse_merge! DEFAULT_COLLECTION_OPTIONS
|
74
|
+
|
75
|
+
collection_klass = reflection.klass
|
76
|
+
collection_members = get_members(&block)
|
77
|
+
|
78
|
+
unless collection_klass.include? CacheMachine::Cache::Collection
|
79
|
+
collection_klass.send :include, CacheMachine::Cache::Collection
|
80
|
+
end
|
81
|
+
|
82
|
+
collection_klass.register_cache_dependency @cache_resource, collection_name, { :scopes => options[:scopes],
|
83
|
+
:members => collection_members,
|
84
|
+
:on => options[:on] }
|
85
|
+
@cache_resource.cached_collections |= [collection_name]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Appends member to the collection.
|
90
|
+
#
|
91
|
+
# @param [Array<String, Symbol>] member_names
|
92
|
+
def member(*member_names)
|
93
|
+
scoped :collection, :member do
|
94
|
+
@members = (@members || []) | member_names
|
95
|
+
end
|
96
|
+
end
|
97
|
+
alias members member
|
98
|
+
|
99
|
+
# Returns members of collection in scope.
|
100
|
+
#
|
101
|
+
# @return [Hash]
|
102
|
+
def get_members(&block)
|
103
|
+
@members = []
|
104
|
+
instance_eval(&block) if block_given?
|
105
|
+
@members
|
106
|
+
end
|
107
|
+
|
108
|
+
# Checks if method can be called from the scope.
|
109
|
+
#
|
110
|
+
# @param [Symbol] scope
|
111
|
+
def validate_scope!(scope)
|
112
|
+
raise "#{scope} can not be called in #{@scope} scope" if @scope != scope
|
113
|
+
end
|
114
|
+
|
115
|
+
# Changes scope from one to another.
|
116
|
+
#
|
117
|
+
# @param [Symbol] from
|
118
|
+
# @param [Symbol] to
|
119
|
+
def change_scope!(from, to)
|
120
|
+
validate_scope!(from)
|
121
|
+
@scope = to
|
122
|
+
end
|
123
|
+
|
124
|
+
# Runs code in the given scope.
|
125
|
+
#
|
126
|
+
# @param [Symbol] from
|
127
|
+
# @param [Symbol] to
|
128
|
+
def scoped(from, to)
|
129
|
+
change_scope! from, to
|
130
|
+
yield
|
131
|
+
change_scope! to, from
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module CacheMachine
|
2
|
+
module Cache
|
3
|
+
module Resource
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
CacheMachine::Logger.info "CACHE_MACHINE: bind cache-map on class #{self.name}"
|
8
|
+
|
9
|
+
include CacheMachine::Cache::Associations
|
10
|
+
include CacheMachine::Cache::Scope
|
11
|
+
|
12
|
+
cattr_accessor :cached_collections
|
13
|
+
self.cached_collections = []
|
14
|
+
|
15
|
+
# Returns cache key of the member.
|
16
|
+
#
|
17
|
+
# @param [ String, Symbol ] member
|
18
|
+
#
|
19
|
+
# @return [ String ]
|
20
|
+
def cache_key_of(member)
|
21
|
+
CacheMachine::Cache::Map.resource_cache_key(self.class, self.send(self.class.primary_key), member)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Fetches cache of the member.
|
25
|
+
#
|
26
|
+
# @example Fetch cache of associated collection to be refreshed every hour.
|
27
|
+
# @instance.fetch_cache_of :association, :timestamp => lambda { custom_instance_method },
|
28
|
+
# :expires_in => 1.hour
|
29
|
+
#
|
30
|
+
# @param [ Symbol ] _member
|
31
|
+
# @param [ Hash ] options
|
32
|
+
#
|
33
|
+
# @return [ * ]
|
34
|
+
def fetch_cache_of(_member, options = {}, &block)
|
35
|
+
expires_in = if expires_at = options[:expires_at]
|
36
|
+
expires_at = expires_at.call if expires_at.kind_of? Proc
|
37
|
+
|
38
|
+
if expires_at.is_a? Date
|
39
|
+
expires_at = expires_at.to_time
|
40
|
+
end
|
41
|
+
|
42
|
+
if expires_at.kind_of? DateTime
|
43
|
+
expires_at - DateTime.now
|
44
|
+
else
|
45
|
+
raise ArgumentError, "expires_at is not a Date or DateTime"
|
46
|
+
end
|
47
|
+
else
|
48
|
+
options[:expires_in]
|
49
|
+
end
|
50
|
+
|
51
|
+
if expires_in && expires_in < 0
|
52
|
+
yield
|
53
|
+
else
|
54
|
+
if options.has_key? :timestamp
|
55
|
+
unless self.class.respond_to? options[:timestamp]
|
56
|
+
raise ArgumentError, "Undefined timestamp '#{options[:timestamp]}' for class #{self.class}"
|
57
|
+
end
|
58
|
+
|
59
|
+
_member = CacheMachine::Cache::Map.timestamped_resource_member_key(self.class,
|
60
|
+
self.send(self.class.primary_key),
|
61
|
+
_member,
|
62
|
+
self.class.send(options[:timestamp]))
|
63
|
+
end
|
64
|
+
CacheMachine::Logger.info "CACHE_MACHINE (fetch_cache_of): reading '#{cache_key_of(_member)}'."
|
65
|
+
CacheMachine::Cache::storage_adapter.fetch(cache_key_of(_member), :expires_in => expires_in, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
alias fetch_cache fetch_cache_of
|
69
|
+
|
70
|
+
# Removes all caches using map.
|
71
|
+
def delete_all_caches
|
72
|
+
self.class.cached_collections.each &method(:delete_cache_of)
|
73
|
+
end
|
74
|
+
alias reset_all_caches delete_all_caches
|
75
|
+
|
76
|
+
# Recursively deletes cache by map starting from the member.
|
77
|
+
#
|
78
|
+
# @param [ Symbol ] member
|
79
|
+
def delete_cache_of(member)
|
80
|
+
reflection = self.class.reflect_on_association(member)
|
81
|
+
|
82
|
+
if reflection
|
83
|
+
reflection.klass.reset_resource_cache(self, member)
|
84
|
+
else
|
85
|
+
delete_cache_of_only member
|
86
|
+
end
|
87
|
+
end
|
88
|
+
alias delete_cache delete_cache_of
|
89
|
+
|
90
|
+
# Deletes cache of the only member ignoring cache map.
|
91
|
+
#
|
92
|
+
# @param [ Symbol ] member
|
93
|
+
def delete_cache_of_only(member)
|
94
|
+
CacheMachine::Cache::Map.reset_cache_on_map(self.class, self.send(self.class.primary_key), member)
|
95
|
+
end
|
96
|
+
alias delete_member_cache delete_cache_of_only
|
97
|
+
|
98
|
+
# Deletes cache of anything from memory.
|
99
|
+
#
|
100
|
+
# @param [ String, Symbol ] anything
|
101
|
+
def reset_timestamp_of(anything)
|
102
|
+
CacheMachine::Cache::Map.reset_resource_timestamp(self.class, self.send(self.class.primary_key), anything)
|
103
|
+
end
|
104
|
+
alias reset_timestamp reset_timestamp_of
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CacheMachine
|
2
|
+
module Cache
|
3
|
+
module Scope
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
cattr_accessor :cache_scopes
|
8
|
+
self.cache_scopes = []
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# Returns scope used for cache-map defined for this class.
|
14
|
+
#
|
15
|
+
# @return [ ActiveRecord::Relation ]
|
16
|
+
def under_cache_scopes
|
17
|
+
result = self.scoped
|
18
|
+
[*self.cache_scopes].each { |scope| result = result.send(scope) }
|
19
|
+
result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module CacheMachine
|
2
|
+
module Cache
|
3
|
+
module TimestampBuilder
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
# Defines timestamp for object.
|
9
|
+
#
|
10
|
+
# @example Define timestamp to be updated every hour.
|
11
|
+
# class MyModel < ActiveRecord::Base
|
12
|
+
# define_timestamp(:my_timestamp, :expires_in => 1.hour) { [ Date.today.to_s, self.last.updated_at ] }
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# @param [ String, Symbol ] timestamp_name
|
16
|
+
# @param [ Hash ] options
|
17
|
+
def define_timestamp(timestamp_name, options = {}, &block)
|
18
|
+
(class << self; self end).send(:define_method, timestamp_name) do
|
19
|
+
|
20
|
+
# Block affecting on timestamp.
|
21
|
+
stamp_value = block_given? ? ([*instance_eval(&block)] << 'stamp').join('_') : 'stamp'
|
22
|
+
|
23
|
+
# The key of timestamp itself.
|
24
|
+
stamp_key_value = timestamp_key_of("#{timestamp_name}_#{stamp_value}")
|
25
|
+
|
26
|
+
# The key of the key of timestamp.
|
27
|
+
stamp_key = timestamp_key_of(timestamp_name)
|
28
|
+
|
29
|
+
# The key of timestamp from the cache (previous value).
|
30
|
+
cached_stamp_key_value = CacheMachine::Cache::timestamps_adapter.fetch_timestamp(stamp_key) { stamp_key_value }
|
31
|
+
|
32
|
+
# Timestamp is updated. Delete old key from cache to do not pollute it with dead-keys.
|
33
|
+
if stamp_key_value != cached_stamp_key_value
|
34
|
+
CacheMachine::Cache::timestamps_adapter.reset_timestamp(stamp_key)
|
35
|
+
CacheMachine::Cache::timestamps_adapter.reset_timestamp(cached_stamp_key_value)
|
36
|
+
end
|
37
|
+
|
38
|
+
CacheMachine::Cache::timestamps_adapter.fetch_timestamp(stamp_key_value, options) do
|
39
|
+
Time.now.to_i.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns timestamp cache key for anything.
|
45
|
+
#
|
46
|
+
# @param [ String, Symbol ] anything
|
47
|
+
#
|
48
|
+
# @return [ String ]
|
49
|
+
def timestamp_key_of(anything)
|
50
|
+
"#{self.name}~#{anything}~ts"
|
51
|
+
end
|
52
|
+
alias timestamp_key timestamp_key_of
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
ActiveRecord::Base.send :include, CacheMachine::Cache::TimestampBuilder
|
@@ -2,7 +2,7 @@ module CacheMachine
|
|
2
2
|
module Helpers
|
3
3
|
module CacheHelper
|
4
4
|
|
5
|
-
# Returns cached
|
5
|
+
# Returns cached content for your views.
|
6
6
|
#
|
7
7
|
# @example Return html from cache.
|
8
8
|
# = cache_for @instance, :association do
|
@@ -13,8 +13,8 @@ module CacheMachine
|
|
13
13
|
# @param [ Hash ] options
|
14
14
|
#
|
15
15
|
# @return [ String ]
|
16
|
-
def cache_for
|
17
|
-
record.fetch_cache_of(cacheable, options
|
16
|
+
def cache_for(record, cacheable, options = {}, &block)
|
17
|
+
record.fetch_cache_of(cacheable, options) { capture &block }
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
data/lib/cache_machine/logger.rb
CHANGED
@@ -8,7 +8,10 @@ module CacheMachine
|
|
8
8
|
:none => 10 } # No output at all.
|
9
9
|
|
10
10
|
# The default log level.
|
11
|
-
|
11
|
+
cattr_accessor :level
|
12
|
+
cattr_accessor :logger
|
13
|
+
|
14
|
+
self.level = LOGGING_LEVELS[:none]
|
12
15
|
|
13
16
|
class << self
|
14
17
|
LOGGING_LEVELS.keys.each do |level|
|
@@ -21,20 +24,18 @@ module CacheMachine
|
|
21
24
|
# ActiveRecord::CacheMachine::Logger.level = :info
|
22
25
|
#
|
23
26
|
# @param [Symbol] value
|
24
|
-
def level=
|
27
|
+
def level=(value)
|
25
28
|
@@level = LOGGING_LEVELS[value] or raise "CACHE_MACHINE: Unknown log level: '#{value}'."
|
26
|
-
|
27
|
-
puts "CACHE_MACHINE: Setting log level to '#{value}'.\n"
|
28
|
-
end
|
29
|
+
puts "CACHE_MACHINE: Setting log level to '#{value}'."
|
29
30
|
end
|
30
31
|
|
31
32
|
# Logs the given entry with the given log level.
|
32
33
|
#
|
33
34
|
# @param [ Symbol ] level
|
34
35
|
# @param [ String ] text
|
35
|
-
def write
|
36
|
-
if
|
37
|
-
|
36
|
+
def write(level, text)
|
37
|
+
if self.level <= LOGGING_LEVELS[level]
|
38
|
+
self.logger ? self.logger.info(text) : puts(text)
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|
@@ -5,5 +5,16 @@ module CacheMachine
|
|
5
5
|
include CacheMachine::Helpers::CacheHelper
|
6
6
|
end
|
7
7
|
end
|
8
|
+
|
9
|
+
config.after_initialize do
|
10
|
+
|
11
|
+
CacheMachine::Cache::Map.registered_maps.each do |block|
|
12
|
+
CacheMachine::Cache::Mapper.new.instance_eval(&block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
rake_tasks do
|
17
|
+
load 'cache_machine/tasks.rb'
|
18
|
+
end
|
8
19
|
end
|
9
20
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
namespace :cache_machine do
|
2
|
+
desc "Caches the map of associations."
|
3
|
+
task :fill_associations_map, [:models] => :environment do |t, args|
|
4
|
+
models = args[:models] ? args[:models].map(&:constantize) : CacheMachine::Cache::Map.registered_models
|
5
|
+
models.each do |model|
|
6
|
+
puts "Processing #{model}..."
|
7
|
+
CacheMachine::Cache::Map.fill_associations_map model
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/spec/fixtures.rb
CHANGED
@@ -50,6 +50,8 @@ end
|
|
50
50
|
class Join < ActiveRecord::Base
|
51
51
|
set_table_name HMT_JOINS_TABLE_NAME
|
52
52
|
|
53
|
+
scope :test_scope, joins(:cacher).where(:cachers => { :name => %w{one two} })
|
54
|
+
|
53
55
|
belongs_to :cacher, :class_name => 'Cacher'
|
54
56
|
belongs_to :has_many_through_cacheable, :class_name => 'HasManyThroughCacheable'
|
55
57
|
end
|
@@ -65,15 +67,18 @@ class Polymorphic < ActiveRecord::Base
|
|
65
67
|
end
|
66
68
|
|
67
69
|
class Cacher < ActiveRecord::Base
|
70
|
+
cattr_accessor :stamp
|
68
71
|
set_table_name TARGET_TABLE_NAME
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
define_timestamp :resource_test_timestamp do
|
74
|
+
self.stamp
|
75
|
+
end
|
76
|
+
|
77
|
+
define_timestamp :resource_test_timestamp2 do
|
78
|
+
[self.stamp, 2]
|
79
|
+
end
|
75
80
|
|
76
|
-
|
81
|
+
scope :test_scope, where(:name => %w{one two})
|
77
82
|
|
78
83
|
has_and_belongs_to_many :has_and_belongs_to_many_cacheables, :class_name => 'HasAndBelongsToManyCacheable'
|
79
84
|
has_many :has_many_cacheables, :class_name => 'HasManyCacheable'
|