redis_object 1.0 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/.coveralls.yml +1 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +4 -0
  5. data/README.markdown +24 -15
  6. data/Rakefile +8 -0
  7. data/lib/redis_object.rb +11 -1
  8. data/lib/redis_object/base.rb +210 -60
  9. data/lib/redis_object/collection.rb +130 -100
  10. data/lib/redis_object/defaults.rb +21 -8
  11. data/lib/redis_object/{history.rb → experimental/history.rb} +0 -0
  12. data/lib/redis_object/ext/filters.rb +34 -16
  13. data/lib/redis_object/ext/script_cache.rb +92 -0
  14. data/lib/redis_object/ext/shardable.rb +18 -0
  15. data/lib/redis_object/ext/triggers.rb +75 -13
  16. data/lib/redis_object/ext/view_caching.rb +258 -0
  17. data/lib/redis_object/ext/views.rb +102 -0
  18. data/lib/redis_object/indices.rb +44 -39
  19. data/lib/redis_object/inheritance_tracking.rb +23 -0
  20. data/lib/redis_object/keys.rb +4 -4
  21. data/lib/redis_object/storage.rb +30 -1
  22. data/lib/redis_object/storage/adapter.rb +6 -3
  23. data/lib/redis_object/storage/redis.rb +98 -3
  24. data/lib/redis_object/timestamps.rb +42 -21
  25. data/lib/redis_object/types.rb +172 -30
  26. data/lib/redis_object/version.rb +1 -1
  27. data/redis_object.gemspec +1 -0
  28. data/spec/adapter_spec.rb +43 -0
  29. data/spec/base_spec.rb +41 -6
  30. data/spec/benchmark_spec.rb +46 -0
  31. data/spec/collections_spec.rb +144 -0
  32. data/spec/defaults_spec.rb +56 -0
  33. data/spec/filters_spec.rb +29 -0
  34. data/spec/indices_spec.rb +45 -0
  35. data/spec/rename_class_spec.rb +96 -0
  36. data/spec/spec_helper.rb +32 -1
  37. data/spec/timestamp_spec.rb +28 -0
  38. data/spec/trigger_spec.rb +51 -0
  39. data/spec/types_spec.rb +103 -0
  40. data/spec/view_caching_spec.rb +130 -0
  41. data/spec/views_spec.rb +72 -0
  42. metadata +111 -116
  43. data/doc/Object.html +0 -185
  44. data/doc/Seabright.html +0 -181
  45. data/doc/Seabright/Adapter.html +0 -442
  46. data/doc/Seabright/Collection.html +0 -797
  47. data/doc/Seabright/Collections.html +0 -635
  48. data/doc/Seabright/Collections/ClassMethods.html +0 -212
  49. data/doc/Seabright/ExternalIndex.html +0 -217
  50. data/doc/Seabright/History.html +0 -382
  51. data/doc/Seabright/History/ClassMethods.html +0 -276
  52. data/doc/Seabright/Indices.html +0 -324
  53. data/doc/Seabright/Indices/ClassMethods.html +0 -348
  54. data/doc/Seabright/Keys.html +0 -314
  55. data/doc/Seabright/Keys/ClassMethods.html +0 -276
  56. data/doc/Seabright/ObjectBase.html +0 -852
  57. data/doc/Seabright/ObjectBase/ClassMethods.html +0 -677
  58. data/doc/Seabright/RedisObject.html +0 -230
  59. data/doc/Seabright/References.html +0 -280
  60. data/doc/Seabright/Storage.html +0 -252
  61. data/doc/Seabright/Storage/ClassMethods.html +0 -276
  62. data/doc/Seabright/Storage/MySQL.html +0 -442
  63. data/doc/Seabright/Storage/Redis.html +0 -218
  64. data/doc/Seabright/Template.html +0 -212
  65. data/doc/Seabright/Template/ClassMethods.html +0 -166
  66. data/doc/Seabright/Timestamps.html +0 -292
  67. data/doc/Seabright/Timestamps/ClassMethods.html +0 -214
  68. data/doc/Seabright/Types.html +0 -410
  69. data/doc/Seabright/Types/ClassMethods.html +0 -308
  70. data/doc/created.rid +0 -17
  71. data/doc/images/add.png +0 -0
  72. data/doc/images/brick.png +0 -0
  73. data/doc/images/brick_link.png +0 -0
  74. data/doc/images/bug.png +0 -0
  75. data/doc/images/bullet_black.png +0 -0
  76. data/doc/images/bullet_toggle_minus.png +0 -0
  77. data/doc/images/bullet_toggle_plus.png +0 -0
  78. data/doc/images/date.png +0 -0
  79. data/doc/images/delete.png +0 -0
  80. data/doc/images/find.png +0 -0
  81. data/doc/images/loadingAnimation.gif +0 -0
  82. data/doc/images/macFFBgHack.png +0 -0
  83. data/doc/images/package.png +0 -0
  84. data/doc/images/page_green.png +0 -0
  85. data/doc/images/page_white_text.png +0 -0
  86. data/doc/images/page_white_width.png +0 -0
  87. data/doc/images/plugin.png +0 -0
  88. data/doc/images/ruby.png +0 -0
  89. data/doc/images/tag_blue.png +0 -0
  90. data/doc/images/tag_green.png +0 -0
  91. data/doc/images/transparent.png +0 -0
  92. data/doc/images/wrench.png +0 -0
  93. data/doc/images/wrench_orange.png +0 -0
  94. data/doc/images/zoom.png +0 -0
  95. data/doc/index.html +0 -125
  96. data/doc/js/darkfish.js +0 -153
  97. data/doc/js/jquery.js +0 -18
  98. data/doc/js/navigation.js +0 -142
  99. data/doc/js/search.js +0 -94
  100. data/doc/js/search_index.js +0 -1
  101. data/doc/js/searcher.js +0 -228
  102. data/doc/rdoc.css +0 -543
  103. data/doc/table_of_contents.html +0 -394
@@ -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
@@ -1,20 +1,72 @@
1
1
  module Seabright
2
2
  module Triggers
3
3
 
4
- def set(k,v)
5
- super(k,v)
6
- if self.class.field_triggers[k.to_sym]
7
- send(self.class.field_triggers[k.to_sym],k,v)
8
- end
9
- self.class.update_triggers.each do |actn|
10
- send(actn.to_sym,k,v)
11
- end
12
- end
13
-
14
4
  module ClassMethods
15
5
 
16
6
  def trigger_on_set(fld,actn)
17
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]
18
70
  end
19
71
 
20
72
  def field_triggers
@@ -22,11 +74,21 @@ module Seabright
22
74
  end
23
75
 
24
76
  def trigger_on_update(actn)
25
- update_triggers.push actn.to_sym
77
+ update_triggers << actn.to_sym
78
+ intercept_sets_for_triggers!
26
79
  end
27
80
 
28
81
  def update_triggers
29
- @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
30
92
  end
31
93
 
32
94
  end
@@ -36,4 +98,4 @@ module Seabright
36
98
  end
37
99
 
38
100
  end
39
- 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