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.
Files changed (38) hide show
  1. data/.gitignore +3 -1
  2. data/.travis.yml +6 -0
  3. data/Gemfile +0 -2
  4. data/Gemfile.lock +0 -2
  5. data/README.md +258 -0
  6. data/VERSION +1 -1
  7. data/cache-machine.gemspec +7 -22
  8. data/db/.gitignore +1 -0
  9. data/lib/cache-machine.rb +6 -2
  10. data/lib/cache_machine/adapter.rb +127 -0
  11. data/lib/cache_machine/adapters/rails.rb +58 -0
  12. data/lib/cache_machine/cache.rb +34 -85
  13. data/lib/cache_machine/cache/associations.rb +29 -0
  14. data/lib/cache_machine/cache/class_timestamp.rb +31 -0
  15. data/lib/cache_machine/cache/collection.rb +131 -0
  16. data/lib/cache_machine/cache/map.rb +97 -241
  17. data/lib/cache_machine/cache/mapper.rb +135 -0
  18. data/lib/cache_machine/cache/resource.rb +108 -0
  19. data/lib/cache_machine/cache/scope.rb +24 -0
  20. data/lib/cache_machine/cache/timestamp_builder.rb +58 -0
  21. data/lib/cache_machine/helpers/cache_helper.rb +3 -3
  22. data/lib/cache_machine/logger.rb +9 -8
  23. data/lib/cache_machine/railtie.rb +11 -0
  24. data/lib/cache_machine/tasks.rb +10 -0
  25. data/spec/fixtures.rb +11 -6
  26. data/spec/lib/cache_machine/adapters/rails_spec.rb +149 -0
  27. data/spec/lib/cache_machine/cache/associations_spec.rb +32 -0
  28. data/spec/lib/cache_machine/cache/class_timestam_spec.rb +29 -0
  29. data/spec/lib/cache_machine/cache/collection_spec.rb +106 -0
  30. data/spec/lib/cache_machine/cache/map_spec.rb +34 -0
  31. data/spec/lib/cache_machine/cache/mapper_spec.rb +61 -0
  32. data/spec/lib/cache_machine/cache/resource_spec.rb +86 -0
  33. data/spec/lib/cache_machine/cache/scope_spec.rb +50 -0
  34. data/spec/lib/cache_machine/cache/timestamp_builder_spec.rb +52 -0
  35. data/spec/spec_helper.rb +9 -3
  36. metadata +35 -61
  37. data/README.rdoc +0 -186
  38. 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 EHTML content.
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 record, cacheable, options = {}, &block
17
- record.fetch_cache_of(cacheable, options.merge(:format => :ehtml)) { capture &block }
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
@@ -8,7 +8,10 @@ module CacheMachine
8
8
  :none => 10 } # No output at all.
9
9
 
10
10
  # The default log level.
11
- @@level = LOGGING_LEVELS[:none]
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= value
27
+ def level=(value)
25
28
  @@level = LOGGING_LEVELS[value] or raise "CACHE_MACHINE: Unknown log level: '#{value}'."
26
- if @@level <= LOGGING_LEVELS[:info]
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 level, text
36
- if @@level <= (LOGGING_LEVELS[level] or raise "CACHE_MACHINE: Unknown log level: '#{level}'.")
37
- puts text
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
@@ -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
- cache_map :polymorphics,
71
- :child_cachers,
72
- :has_many_cacheables => :dependent_cache,
73
- :has_many_through_cacheables => :dependent_cache,
74
- :has_and_belongs_to_many_cacheables => :dependent_cache
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
- define_timestamp(:dynamic_timestamp) { execute_timestamp }
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'