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