cache-machine 0.1.10 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|