redis_object 1.0 → 1.1

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 (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