redis_object 0.5.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/.coveralls.yml +1 -0
- data/.gitignore +6 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/README.markdown +179 -0
- data/Rakefile +10 -0
- data/lib/redis_object.rb +47 -0
- data/lib/redis_object/base.rb +408 -0
- data/lib/redis_object/collection.rb +388 -0
- data/lib/redis_object/defaults.rb +42 -0
- data/lib/redis_object/experimental/history.rb +49 -0
- data/lib/redis_object/ext/benchmark.rb +34 -0
- data/lib/redis_object/ext/cleaner.rb +14 -0
- data/lib/redis_object/ext/filters.rb +68 -0
- data/lib/redis_object/ext/script_cache.rb +92 -0
- data/lib/redis_object/ext/shardable.rb +18 -0
- data/lib/redis_object/ext/triggers.rb +101 -0
- data/lib/redis_object/ext/view_caching.rb +258 -0
- data/lib/redis_object/ext/views.rb +102 -0
- data/lib/redis_object/external_index.rb +25 -0
- data/lib/redis_object/indices.rb +97 -0
- data/lib/redis_object/inheritance_tracking.rb +23 -0
- data/lib/redis_object/keys.rb +37 -0
- data/lib/redis_object/storage.rb +93 -0
- data/lib/redis_object/storage/adapter.rb +46 -0
- data/lib/redis_object/storage/aws.rb +71 -0
- data/lib/redis_object/storage/mysql.rb +47 -0
- data/lib/redis_object/storage/redis.rb +119 -0
- data/lib/redis_object/timestamps.rb +74 -0
- data/lib/redis_object/tpl.rb +17 -0
- data/lib/redis_object/types.rb +276 -0
- data/lib/redis_object/validation.rb +89 -0
- data/lib/redis_object/version.rb +5 -0
- data/redis_object.gemspec +26 -0
- data/spec/adapter_spec.rb +43 -0
- data/spec/base_spec.rb +90 -0
- data/spec/benchmark_spec.rb +46 -0
- data/spec/collections_spec.rb +144 -0
- data/spec/defaults_spec.rb +56 -0
- data/spec/filters_spec.rb +29 -0
- data/spec/indices_spec.rb +45 -0
- data/spec/rename_class_spec.rb +96 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/timestamp_spec.rb +28 -0
- data/spec/trigger_spec.rb +51 -0
- data/spec/types_spec.rb +103 -0
- data/spec/view_caching_spec.rb +130 -0
- data/spec/views_spec.rb +72 -0
- metadata +172 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Seabright
|
2
|
+
module Benchmark
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
def benchmark(*method_names)
|
7
|
+
method_names.each do |method_name|
|
8
|
+
original_method = instance_method(method_name)
|
9
|
+
define_method(method_name) do |*args,&blk|
|
10
|
+
st = Time.now
|
11
|
+
out = original_method.bind(self).call(*args,&blk)
|
12
|
+
self.class.benchmark_out(method_name,args,Time.now - st)
|
13
|
+
out
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def benchmark_out(method,args,time)
|
19
|
+
puts "[RedisObject::Benchmark] #{method}(#{args.join(",")}): #{time}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def benchmark!
|
23
|
+
benchmark :set, :get, :<<
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.included(base)
|
29
|
+
base.extend(ClassMethods)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Seabright
|
2
|
+
module RedisObjectCleaner
|
3
|
+
def self.clean!
|
4
|
+
RedisObject.store.keys("*:collections").each do |key|
|
5
|
+
if obj = RedisObject.find_by_key(key.gsub(/:collections$/,''))
|
6
|
+
obj.collections.each do |nm,col|
|
7
|
+
puts "Cleaning: #{nm} #{col.class} #{col.inspect}" if DEBUG
|
8
|
+
col.cleanup!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Seabright
|
2
|
+
module Filters
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
def intercept_for_filters!
|
7
|
+
return if @intercept_for_filters
|
8
|
+
self.class_eval do
|
9
|
+
|
10
|
+
def filtered_method_call(method,*args)
|
11
|
+
if filters = self.class.filters_for(method)
|
12
|
+
filters.each do |f|
|
13
|
+
args = send(f,*args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
send("unfiltered_#{method.to_s}".to_sym,*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method :unfiltered_get, :get unless method_defined?(:unfiltered_get)
|
20
|
+
def get(k)
|
21
|
+
filtered_method_call(:get,k)
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :unfiltered_set, :set unless method_defined?(:unfiltered_set)
|
25
|
+
def set(k,v)
|
26
|
+
filtered_method_call(:set,k,v)
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :unfiltered_setnx, :setnx unless method_defined?(:unfiltered_setnx)
|
30
|
+
def setnx(k,v)
|
31
|
+
filtered_method_call(:setnx,k,v)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
@intercept_for_filters = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_filter(filter)
|
39
|
+
filter_method(:set,filter)
|
40
|
+
filter_method(:setnx,filter)
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_filter(filter)
|
44
|
+
filter_method(:get,filter)
|
45
|
+
end
|
46
|
+
|
47
|
+
def filter_method(method, filter)
|
48
|
+
method_filters[method.to_sym] ||= []
|
49
|
+
method_filters[method.to_sym] << filter.to_sym unless method_filters[method.to_sym].include?(filter.to_sym)
|
50
|
+
intercept_for_filters!
|
51
|
+
end
|
52
|
+
|
53
|
+
def method_filters
|
54
|
+
@method_filters ||= {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def filters_for(method)
|
58
|
+
method_filters[method.to_sym]
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.included(base)
|
64
|
+
base.extend(ClassMethods)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
$ScriptSHAMap = {}
|
2
|
+
|
3
|
+
module Seabright
|
4
|
+
|
5
|
+
class RedisObject
|
6
|
+
module ScriptSources; end
|
7
|
+
end
|
8
|
+
|
9
|
+
module CachedScripts
|
10
|
+
|
11
|
+
def run_script(name,keys=[],args=[],source=nil)
|
12
|
+
self.class.run_script(name,keys,args,source,store)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
NoScriptError = "NOSCRIPT No matching script. Please use EVAL.".freeze
|
18
|
+
|
19
|
+
def run_script(name,keys=[],args=[],source=nil,stor=nil)
|
20
|
+
@tmp_store = stor if stor
|
21
|
+
@rescue_recurse ||= 0
|
22
|
+
begin
|
23
|
+
out = (@tmp_store || store).evalsha(get_script_sha(name,source),keys,args)
|
24
|
+
rescue Redis::CommandError => e
|
25
|
+
if e.message == NoScriptError && @rescue_recurse < 3
|
26
|
+
puts "Rescuing NOSCRIPT error for #{name} - running again..." if DEBUG
|
27
|
+
untrack_script name
|
28
|
+
@rescue_recurse += 1
|
29
|
+
out = (@tmp_store || store).evalsha(get_script_sha(name,source),keys,args)
|
30
|
+
else
|
31
|
+
@rescue_recurse = 0
|
32
|
+
raise e
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@rescue_recurse = 0
|
36
|
+
remove_instance_variable(:@tmp_store) if @tmp_store
|
37
|
+
out
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_script_sha(name,source=nil)
|
41
|
+
$ScriptSHAMap[name] ||= (script_sha_from_key(name) || store_script(name,source))
|
42
|
+
end
|
43
|
+
|
44
|
+
def script_sha_from_key(name)
|
45
|
+
(@tmp_store || store).get(script_sha_key(name))
|
46
|
+
end
|
47
|
+
|
48
|
+
class SourceNotFoundError < RuntimeError
|
49
|
+
def initialize
|
50
|
+
super("Could not locate script source")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def store_script(name,source=nil)
|
55
|
+
source ||= script_source_from_const(name)
|
56
|
+
raise SourceNotFoundError unless source
|
57
|
+
sha = (@tmp_store || store).script(:load,source)
|
58
|
+
(@tmp_store || store).set(script_sha_key(name),sha)
|
59
|
+
sha
|
60
|
+
end
|
61
|
+
|
62
|
+
def untrack_script(name)
|
63
|
+
ScriptSHAMap.delete name
|
64
|
+
(@tmp_store || store).del(script_sha_key(name))
|
65
|
+
end
|
66
|
+
|
67
|
+
def script_source_from_const(name)
|
68
|
+
(self.const_defined?(name.to_sym) && self.const_get(name.to_sym)) || (RedisObject::ScriptSources.const_defined?(name.to_sym) && RedisObject::ScriptSources.const_get(name.to_sym)) || nil
|
69
|
+
end
|
70
|
+
|
71
|
+
SCRIPT_KEY_PREFIX = "ScriptCache::SHA::".freeze
|
72
|
+
|
73
|
+
def expire_all_script_shas(store_name=nil)
|
74
|
+
store_obj = store_name.is_a?(String) || store_name.is_a?(Symbol) ? store(store_name.to_sym) : store_name
|
75
|
+
store_obj.keys(script_sha_key("*")).each do |k|
|
76
|
+
store_obj.del k
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def script_sha_key(name)
|
81
|
+
"#{SCRIPT_KEY_PREFIX}#{name.to_s}"
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.included(base)
|
87
|
+
base.extend(ClassMethods)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Seabright
|
2
|
+
module Shardable
|
3
|
+
|
4
|
+
# Intention is to override any methods needed so that the underlying data can be safely sharded
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
# same here
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
RedisObject.send(:include,Shardable)
|
18
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Seabright
|
2
|
+
module Triggers
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
def trigger_on_set(fld,actn)
|
7
|
+
field_triggers[fld.to_sym] = actn.to_sym
|
8
|
+
intercept_sets_for_triggers!
|
9
|
+
end
|
10
|
+
|
11
|
+
def intercept_sets_for_triggers!
|
12
|
+
return if @intercepted_sets_for_triggers
|
13
|
+
self.class_eval do
|
14
|
+
alias_method :untriggered_set, :set unless method_defined?(:untriggered_set)
|
15
|
+
def set(k,v)
|
16
|
+
untriggered_set(k,v)
|
17
|
+
unless self.class.untriggerables.include?(k)
|
18
|
+
begin
|
19
|
+
self.class.untriggerables << k
|
20
|
+
if self.class.field_triggers[k.to_sym]
|
21
|
+
send(self.class.field_triggers[k.to_sym],k,v)
|
22
|
+
end
|
23
|
+
self.class.update_triggers.each do |actn|
|
24
|
+
send(actn.to_sym,k,v)
|
25
|
+
end
|
26
|
+
ensure
|
27
|
+
self.class.untriggerables.delete k
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
alias_method :untriggered_setnx, :setnx unless method_defined?(:untriggered_setnx)
|
32
|
+
def setnx(k,v)
|
33
|
+
ret = untriggered_setnx(k,v)
|
34
|
+
unless self.class.untriggerables.include?(k)
|
35
|
+
begin
|
36
|
+
self.class.untriggerables << k
|
37
|
+
if self.class.field_triggers[k.to_sym]
|
38
|
+
send(self.class.field_triggers[k.to_sym],k,v)
|
39
|
+
end
|
40
|
+
self.class.update_triggers.each do |actn|
|
41
|
+
send(actn.to_sym,k,v)
|
42
|
+
end
|
43
|
+
ensure
|
44
|
+
self.class.untriggerables.delete k
|
45
|
+
end
|
46
|
+
end
|
47
|
+
ret
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
@intercepted_sets_for_triggers = true
|
52
|
+
end
|
53
|
+
|
54
|
+
def intercept_reference_for_triggers!
|
55
|
+
return if @intercepted_reference_for_triggers
|
56
|
+
self.class_eval do
|
57
|
+
alias_method :untriggered_reference, :reference unless method_defined?(:untriggered_reference)
|
58
|
+
def reference(obj)
|
59
|
+
untriggered_reference(obj)
|
60
|
+
self.class.reference_triggers.each do |actn|
|
61
|
+
send(actn.to_sym,obj)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
@intercepted_reference_for_triggers = true
|
66
|
+
end
|
67
|
+
|
68
|
+
def untriggerables
|
69
|
+
@untriggerables ||= [:updated_at,:created_at]
|
70
|
+
end
|
71
|
+
|
72
|
+
def field_triggers
|
73
|
+
@field_triggers ||= {}
|
74
|
+
end
|
75
|
+
|
76
|
+
def trigger_on_update(actn)
|
77
|
+
update_triggers << actn.to_sym
|
78
|
+
intercept_sets_for_triggers!
|
79
|
+
end
|
80
|
+
|
81
|
+
def update_triggers
|
82
|
+
@update_triggers ||= Set.new
|
83
|
+
end
|
84
|
+
|
85
|
+
def trigger_on_reference(actn)
|
86
|
+
reference_triggers << actn.to_sym
|
87
|
+
intercept_reference_for_triggers!
|
88
|
+
end
|
89
|
+
|
90
|
+
def reference_triggers
|
91
|
+
@reference_triggers ||= Set.new
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.included(base)
|
97
|
+
base.extend(ClassMethods)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
# View Caching
|
2
|
+
#
|
3
|
+
# Cache a named view:
|
4
|
+
# cache_named_view :admin_view
|
5
|
+
#
|
6
|
+
# Invalidate all cached views when the I receive notification that something I reference or have been referenced by has changed:
|
7
|
+
# invalidate_caches_from_upstream_updates!
|
8
|
+
#
|
9
|
+
# Notify some objects that reference me when I am updated: (objects of these classes)
|
10
|
+
# invalidate_upstream Property, Application, PaymentRequest, PaymentResponse
|
11
|
+
#
|
12
|
+
# Notify some objects in my collections when I am updated: (objects in these collections)
|
13
|
+
# invalidate_downstream :properties, :applications, :payment_requests, :payment_responses
|
14
|
+
#
|
15
|
+
# You can also set up hooks for when this object is updated by certain types of objects by defining the following:
|
16
|
+
# def invalidated_by(obj,chain)
|
17
|
+
# invalidate_cached_view :blah
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def invalidated_by_user(obj,chain)
|
21
|
+
# invalidate_cached_view :users
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
|
25
|
+
module Seabright
|
26
|
+
module ViewCaching
|
27
|
+
|
28
|
+
CachedViewInvalidator = "
|
29
|
+
for i=1,#ARGV do
|
30
|
+
redis.call('HDEL', KEYS[1], ARGV[i])
|
31
|
+
end".gsub(/\t/,'').freeze
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
|
35
|
+
def cache_view(name,opts=true)
|
36
|
+
cached_views[name.to_sym] = opts
|
37
|
+
intercept_views_for_caching!
|
38
|
+
set_up_invalidation!
|
39
|
+
end
|
40
|
+
alias_method :cache_named_view, :cache_view
|
41
|
+
|
42
|
+
def intercept_views_for_caching!
|
43
|
+
return if @cached_views_intercepted
|
44
|
+
self.class_eval do
|
45
|
+
|
46
|
+
alias_method :uncached_view_as_hash, :view_as_hash unless method_defined?(:uncached_view_as_hash)
|
47
|
+
def view_as_hash(name)
|
48
|
+
return uncached_view_as_hash(name) unless self.class.view_should_be_cached?(name)
|
49
|
+
if v = view_from_cache(name)
|
50
|
+
puts " Got view from cache: #{name}" if DEBUG
|
51
|
+
Yajl::Parser.parse(v)
|
52
|
+
else
|
53
|
+
puts " View cache miss: #{name}" if DEBUG
|
54
|
+
cache_view_content(name)[0]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
alias_method :uncached_view_as_json, :view_as_json unless method_defined?(:uncached_view_as_json)
|
59
|
+
def view_as_json(name)
|
60
|
+
return uncached_view_as_json(name) unless self.class.view_should_be_cached?(name)
|
61
|
+
if v = view_from_cache(name)
|
62
|
+
puts " Got view from cache: #{name}" if DEBUG
|
63
|
+
v
|
64
|
+
else
|
65
|
+
puts " View cache miss: #{name}" if DEBUG
|
66
|
+
cache_view_content(name)[1]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def cache_view_content(name,content=nil)
|
71
|
+
content ||= uncached_view_as_hash(name)
|
72
|
+
json = Yajl::Encoder.encode(content)
|
73
|
+
store.hset(cached_view_key,name,json)
|
74
|
+
[content,json]
|
75
|
+
end
|
76
|
+
|
77
|
+
def view_from_cache(name)
|
78
|
+
if v = store.hget(cached_view_key,name)
|
79
|
+
v
|
80
|
+
else
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def view_is_cached?(name)
|
86
|
+
store.hexists(cached_view_key, name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def cached_view_key
|
90
|
+
"#{hkey}::ViewCache"
|
91
|
+
end
|
92
|
+
|
93
|
+
def regenerate_cached_views(*names)
|
94
|
+
names.each do |name|
|
95
|
+
cache_view_content name
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def regenerate_cached_views!
|
100
|
+
regenerate_cached_views(*self.class.cached_views.map {|name,opts| name })
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
@cached_views_intercepted = true
|
105
|
+
end
|
106
|
+
|
107
|
+
def set_up_invalidation!
|
108
|
+
return if @invalidation_set_up
|
109
|
+
self.class_eval do
|
110
|
+
|
111
|
+
def invalidate_cached_views(*names)
|
112
|
+
puts "Invalidating cached views: #{names.join(", ")}" if Debug.verbose?
|
113
|
+
run_script(:CachedViewInvalidator, [cached_view_key], names, CachedViewInvalidator)
|
114
|
+
self.class.cache_invalidation_hooks do |hook|
|
115
|
+
hook.call(names)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
alias_method :invalidate_cached_view, :invalidate_cached_views
|
119
|
+
|
120
|
+
def invalidate_cached_views!
|
121
|
+
invalidate_cached_views(*self.class.cached_views.map {|name,opts| name })
|
122
|
+
end
|
123
|
+
|
124
|
+
def invalidate_downstream!
|
125
|
+
return unless self.class.downstream_invalidations && (self.class.downstream_invalidations.size > 0)
|
126
|
+
puts "Invalidating downstream: #{self.class.downstream_invalidations.inspect}" if Debug.verbose?
|
127
|
+
self.class.downstream_invalidations.each do |col|
|
128
|
+
if has_collection?(col) && (colctn = get_collection(col))
|
129
|
+
colctn.each do |obj|
|
130
|
+
obj.invalidated_by_other(self,invalidation_chain + [self.hkey])
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def invalidate_upstream!
|
137
|
+
return unless self.class.upstream_invalidations && (self.class.upstream_invalidations.size > 0)
|
138
|
+
puts "Invalidating upstream: #{self.class.upstream_invalidations.inspect}" if Debug.verbose?
|
139
|
+
backreferences.each do |obj|
|
140
|
+
next unless self.class.upstream_invalidations.include?(obj.class.name.split("::").last.to_sym) #|| self.class.invalidate_everything_upstream?
|
141
|
+
obj.invalidated_by_other(self,invalidation_chain + [self.hkey]) if obj.respond_to?(:invalidated_by_other)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def invalidated_by_update!(*args)
|
146
|
+
Thread.new do
|
147
|
+
invalidate_cached_views!
|
148
|
+
invalidate_up_and_down!
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def invalidated_by_reference!(*args)
|
153
|
+
invalidated_by_update!
|
154
|
+
end
|
155
|
+
|
156
|
+
def invalidation_chain
|
157
|
+
@invalidation_chain ||= []
|
158
|
+
end
|
159
|
+
|
160
|
+
def invalidate_up_and_down!
|
161
|
+
unless invalidation_chain.include?(self)
|
162
|
+
invalidate_downstream!
|
163
|
+
invalidate_upstream!
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def invalidated_by_other(obj,chain)
|
168
|
+
unless chain.include?(self.hkey)
|
169
|
+
puts "#{self.class.name}:#{self.id}'s view caches were invalidated by upstream object: #{obj.class.name}:#{obj.id} (chain:#{chain.inspect})" if Debug.verbose?
|
170
|
+
@invalidation_chain = chain
|
171
|
+
[:invalidated_by,"invalidated_by_#{obj.class.name.underscore}".to_sym].each do |meth_sym|
|
172
|
+
send(meth_sym,obj,chain) if respond_to?(meth_sym)
|
173
|
+
end
|
174
|
+
invalidate_up_and_down!
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
trigger_on_update :invalidated_by_update!
|
179
|
+
trigger_on_reference :invalidated_by_reference!
|
180
|
+
|
181
|
+
end
|
182
|
+
@invalidation_set_up = true
|
183
|
+
end
|
184
|
+
|
185
|
+
def invalidate_caches_from_upstream_updates!
|
186
|
+
self.class_eval do
|
187
|
+
|
188
|
+
def invalidated_by(obj,chain)
|
189
|
+
invalidate_cached_views!
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def on_cache_invalidation(&block)
|
196
|
+
cache_invalidation_hooks << block
|
197
|
+
end
|
198
|
+
|
199
|
+
def cache_invalidation_hooks
|
200
|
+
@cache_invalidation_hooks ||= []
|
201
|
+
end
|
202
|
+
|
203
|
+
def invalidate_upstream(*args)
|
204
|
+
@upstream_invalidations = (@upstream_invalidations || []) + args
|
205
|
+
set_up_invalidation!
|
206
|
+
end
|
207
|
+
|
208
|
+
def upstream_invalidations
|
209
|
+
@upstream_invalidations ||= []
|
210
|
+
end
|
211
|
+
|
212
|
+
# def invalidate_everything_upstream!
|
213
|
+
# @invalidate_everything_upstream = true
|
214
|
+
# end
|
215
|
+
#
|
216
|
+
# def invalidate_everything_upstream?
|
217
|
+
# @invalidate_everything_upstream
|
218
|
+
# end
|
219
|
+
|
220
|
+
def invalidate_downstream(*args)
|
221
|
+
@downstream_invalidations = (@downstream_invalidations || []) + args
|
222
|
+
set_up_invalidation!
|
223
|
+
end
|
224
|
+
|
225
|
+
def downstream_invalidations
|
226
|
+
@downstream_invalidations ||= []
|
227
|
+
end
|
228
|
+
|
229
|
+
# def invalidate_everything_downstream!
|
230
|
+
# @invalidate_everything_downstream = true
|
231
|
+
# end
|
232
|
+
#
|
233
|
+
# def invalidate_everything_downstream?
|
234
|
+
# @invalidate_everything_downstream
|
235
|
+
# end
|
236
|
+
|
237
|
+
def view_should_be_cached?(name)
|
238
|
+
!!cached_views[name.to_sym]
|
239
|
+
end
|
240
|
+
|
241
|
+
def cached_views
|
242
|
+
@cached_view ||= {}
|
243
|
+
end
|
244
|
+
|
245
|
+
def cache_named_views!
|
246
|
+
named_views.each do |name,view|
|
247
|
+
cache_view name
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
def self.included(base)
|
254
|
+
base.extend(ClassMethods)
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
end
|