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.
Files changed (49) hide show
  1. data/.coveralls.yml +1 -0
  2. data/.gitignore +6 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +8 -0
  5. data/README.markdown +179 -0
  6. data/Rakefile +10 -0
  7. data/lib/redis_object.rb +47 -0
  8. data/lib/redis_object/base.rb +408 -0
  9. data/lib/redis_object/collection.rb +388 -0
  10. data/lib/redis_object/defaults.rb +42 -0
  11. data/lib/redis_object/experimental/history.rb +49 -0
  12. data/lib/redis_object/ext/benchmark.rb +34 -0
  13. data/lib/redis_object/ext/cleaner.rb +14 -0
  14. data/lib/redis_object/ext/filters.rb +68 -0
  15. data/lib/redis_object/ext/script_cache.rb +92 -0
  16. data/lib/redis_object/ext/shardable.rb +18 -0
  17. data/lib/redis_object/ext/triggers.rb +101 -0
  18. data/lib/redis_object/ext/view_caching.rb +258 -0
  19. data/lib/redis_object/ext/views.rb +102 -0
  20. data/lib/redis_object/external_index.rb +25 -0
  21. data/lib/redis_object/indices.rb +97 -0
  22. data/lib/redis_object/inheritance_tracking.rb +23 -0
  23. data/lib/redis_object/keys.rb +37 -0
  24. data/lib/redis_object/storage.rb +93 -0
  25. data/lib/redis_object/storage/adapter.rb +46 -0
  26. data/lib/redis_object/storage/aws.rb +71 -0
  27. data/lib/redis_object/storage/mysql.rb +47 -0
  28. data/lib/redis_object/storage/redis.rb +119 -0
  29. data/lib/redis_object/timestamps.rb +74 -0
  30. data/lib/redis_object/tpl.rb +17 -0
  31. data/lib/redis_object/types.rb +276 -0
  32. data/lib/redis_object/validation.rb +89 -0
  33. data/lib/redis_object/version.rb +5 -0
  34. data/redis_object.gemspec +26 -0
  35. data/spec/adapter_spec.rb +43 -0
  36. data/spec/base_spec.rb +90 -0
  37. data/spec/benchmark_spec.rb +46 -0
  38. data/spec/collections_spec.rb +144 -0
  39. data/spec/defaults_spec.rb +56 -0
  40. data/spec/filters_spec.rb +29 -0
  41. data/spec/indices_spec.rb +45 -0
  42. data/spec/rename_class_spec.rb +96 -0
  43. data/spec/spec_helper.rb +38 -0
  44. data/spec/timestamp_spec.rb +28 -0
  45. data/spec/trigger_spec.rb +51 -0
  46. data/spec/types_spec.rb +103 -0
  47. data/spec/view_caching_spec.rb +130 -0
  48. data/spec/views_spec.rb +72 -0
  49. metadata +172 -0
@@ -0,0 +1,47 @@
1
+ module Seabright
2
+ module Storage
3
+ class MySQL
4
+
5
+ def set
6
+
7
+ end
8
+
9
+ def sadd
10
+
11
+ end
12
+
13
+ def del
14
+
15
+ end
16
+
17
+ def srem
18
+
19
+ end
20
+
21
+ def smembers
22
+
23
+ end
24
+
25
+ def exists
26
+
27
+ end
28
+
29
+ def hget
30
+
31
+ end
32
+
33
+ def hset
34
+
35
+ end
36
+
37
+ private
38
+
39
+ def connection(num=0)
40
+ require 'mysql'
41
+ @connections ||= []
42
+ @connections[num] ||= MySQL.new
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,119 @@
1
+ module Seabright
2
+ module Storage
3
+ class Redis < Adapter
4
+
5
+ def method_missing(sym, *args, &block)
6
+ return super unless connection.respond_to?(sym)
7
+ puts "[Storage::Redis] #{sym}(#{args.inspect.gsub(/\[|\]/m,'')})" if Debug.verbose?
8
+ begin
9
+ connection.send(sym,*args, &block)
10
+ rescue ::Redis::InheritedError => err
11
+ puts "Rescued: #{err.inspect}" if DEBUG
12
+ reset
13
+ connection.send(sym,*args, &block)
14
+ rescue ::Redis::TimeoutError => err
15
+ puts "Rescued connection timeout: #{err.inspect}" if DEBUG
16
+ reset
17
+ connection.send(sym,*args, &block)
18
+ end
19
+ end
20
+
21
+ def new_connection
22
+ require 'redis'
23
+ puts "Connecting to Redis with: #{config_opts(:path, :db, :password, :host, :port, :timeout, :tcp_keepalive).inspect}" if DEBUG
24
+ ::Redis.new(config_opts(:path, :db, :password, :host, :port, :timeout, :tcp_keepalive))
25
+ end
26
+
27
+ DUMP_SEPARATOR = "---:::RedisObject::DUMP_SEPARATOR:::---"
28
+ REC_SEPARATOR = "---:::RedisObject::REC_SEPARATOR:::---"
29
+
30
+ def dump_to_file(file)
31
+ File.open(file,'wb') do |f|
32
+ keys = connection.send(:keys,"*")
33
+ f.write keys.map {|k|
34
+ v = connection.dump(k)
35
+ v.force_encoding(Encoding::BINARY)
36
+ [k,v].join(DUMP_SEPARATOR)
37
+ }.join(REC_SEPARATOR)
38
+ end
39
+ end
40
+
41
+ def restore_from_file(file)
42
+ str = File.read(file)
43
+ str.force_encoding(Encoding::BINARY)
44
+ str.split(REC_SEPARATOR).each do |line|
45
+ line.force_encoding(Encoding::BINARY)
46
+ key, val = line.split(DUMP_SEPARATOR)
47
+ connection.multi do
48
+ connection.del key
49
+ connection.restore key, 0, val
50
+ end
51
+ end
52
+ end
53
+
54
+ def rename_class old_name, new_name
55
+ old_name = old_name.to_s#.split('::').last
56
+ new_name = new_name.to_s#.split('::').last
57
+ old_collection_name = old_name.split('::').last.underscore.pluralize
58
+ new_collection_name = new_name.split('::').last.underscore.pluralize
59
+
60
+ # references to type in collection data
61
+ keys("#{old_name}:*:backreferences").each do |backref_key|
62
+ smembers(backref_key).each do |hashref|
63
+ # there are two referenes we need to fix: individual references to items
64
+ # and lists of collection names.
65
+ #
66
+ # this updates the item references in collections
67
+ backref = hashref.sub(/_h$/,'');
68
+ old_collection = "#{backref}:COLLECTION:#{old_collection_name}"
69
+ new_collection = "#{backref}:COLLECTION:#{new_collection_name}"
70
+ zrange(old_collection, 0, 99999, withscores:true).each do |key, score|
71
+ zadd(new_collection, score, key.sub(/^#{old_name}/, new_name))
72
+ end
73
+ del(old_collection)
74
+
75
+ # this updates the lists of collection names
76
+ collection_names = "#{hashref}:collections"
77
+ smembers(collection_names).each do |collection_name|
78
+ if collection_name == old_collection_name
79
+ sadd(collection_names, new_collection_name)
80
+ srem(collection_names, old_collection_name)
81
+ end
82
+ end
83
+ end
84
+ rename(backref_key, backref_key.sub(/^#{old_name}/, new_name))
85
+ end
86
+
87
+ # type-wide id index
88
+ smembers(old_name.pluralize).each do |key|
89
+ sadd(new_name.pluralize, key.sub(/^#{old_name}/, new_name))
90
+ old_class = hget("#{key}_h", :class)
91
+ old_key = hget("#{key}_h", :key)
92
+ hset("#{key}_h", :class, old_class.sub(/#{old_name}$/, new_name))
93
+ hset("#{key}_h", :key, old_key.sub(/^#{old_name}/, new_name))
94
+ hset("#{key}_h", "#{new_name.downcase}_id", key.sub(/^#{old_name}:/,''))
95
+ hdel("#{key}_h", "#{old_name.downcase}_id")
96
+ end
97
+ del(old_name.pluralize)
98
+
99
+ # column indexes
100
+ keys("#{old_name.pluralize}::*").each do |old_index|
101
+ new_index = old_index.sub(/^#{old_name.pluralize}/, new_name.pluralize)
102
+ zrange(old_index, 0, 99999, withscores:true).each do |key, score|
103
+ zadd(new_index, score, key.sub(/^#{old_name}/, new_name))
104
+ end
105
+ del(old_index)
106
+ end
107
+
108
+ # top-level keys
109
+ keys("#{old_name}:*").each do |key|
110
+ rename(key, key.sub(/^#{old_name}/, new_name))
111
+ end
112
+ keys("#{old_name.pluralize}:*").each do |key|
113
+ rename(key, key.sub(/^#{old_name.pluralize}/, new_name.pluralize))
114
+ end
115
+ end
116
+
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,74 @@
1
+ module Seabright
2
+ module Timestamps
3
+
4
+ def update_timestamps
5
+ # return unless self.class.time_matters?
6
+ set(:created_at, Time.now) if !is_set?(:created_at)
7
+ set(:updated_at, Time.now)
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def intercept_sets_for_timestamps!
13
+ return if @intercepted_sets_for_timestamps
14
+ self.class_eval do
15
+ alias_method :untimestamped_set, :set unless method_defined?(:untimestamped_set)
16
+ def set(k,v)
17
+ ret = untimestamped_set(k,v)
18
+ set(:updated_at, Time.now) unless k.to_sym == :updated_at
19
+ ret
20
+ end
21
+ alias_method :untimestamped_mset, :mset unless method_defined?(:untimestamped_mset)
22
+ def mset(dat)
23
+ ret = untimestamped_mset(dat)
24
+ set(:updated_at, Time.now)
25
+ ret
26
+ end
27
+ alias_method :untimestamped_setnx, :setnx unless method_defined?(:untimestamped_setnx)
28
+ def setnx(k,v)
29
+ ret = untimestamped_setnx(k,v)
30
+ set(:updated_at, Time.now) unless k.to_sym == :updated_at
31
+ ret
32
+ end
33
+ alias_method :untimestamped_save, :save unless method_defined?(:untimestamped_save)
34
+ def save
35
+ ret = untimestamped_save()
36
+ update_timestamps
37
+ ret
38
+ end
39
+ end
40
+ @intercepted_sets_for_timestamps = true
41
+ end
42
+
43
+ # def time_matters?
44
+ # @time_irrelevant != true
45
+ # end
46
+ #
47
+ # def time_matters_not!
48
+ # @time_irrelevant = true
49
+ # sort_indices.delete(:created_at)
50
+ # sort_indices.delete(:updated_at)
51
+ # end
52
+ #
53
+ def recently_created(num=5)
54
+ self.indexed(:created_at,num,true)
55
+ end
56
+
57
+ def recently_updated(num=5)
58
+ self.indexed(:updated_at,num,true)
59
+ end
60
+
61
+ end
62
+
63
+ def self.included(base)
64
+ # @time_irrelevant = false
65
+ base.send(:sort_by,:created_at)
66
+ base.send(:sort_by,:updated_at)
67
+ base.send(:register_format,:created_at, :date)
68
+ base.send(:register_format,:updated_at, :date)
69
+ base.extend(ClassMethods)
70
+ base.intercept_sets_for_timestamps!
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,17 @@
1
+ module Seabright
2
+ module Template
3
+
4
+
5
+
6
+ module ClassMethods
7
+
8
+
9
+
10
+ end
11
+
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,276 @@
1
+ module Seabright
2
+ module Types
3
+
4
+ def enforce_format(k,v)
5
+ if v && fmt = self.class.field_formats[k.to_sym]
6
+ send(fmt,v)
7
+ else
8
+ v
9
+ end
10
+ end
11
+
12
+ def score_format(k,v)
13
+ if v && fmt = self.class.score_formats[k.to_sym]
14
+ send(fmt,v)
15
+ else
16
+ 0
17
+ end
18
+ end
19
+
20
+ def save_format(k,v)
21
+ v && (fmt = self.class.save_formats[k.to_s.gsub(/\=$/,'').to_sym]) ? send(fmt,v) : v
22
+ end
23
+
24
+ def format_date(val)
25
+ begin
26
+ val.is_a?(DateTime) || val.is_a?(Date) || val.is_a?(Time) ? val : ( val.is_a?(String) ? DateTime.parse(val) : nil )
27
+ rescue StandardError => e
28
+ puts "Could not parse value as date using Date.parse. Returning nil instead. Value: #{val.inspect}\nError: #{e.inspect}" if DEBUG
29
+ nil
30
+ end
31
+ end
32
+
33
+ def score_date(val)
34
+ val.to_time.to_i
35
+ end
36
+
37
+ def format_array(val)
38
+ Yajl::Parser.new(:symbolize_keys => true).parse(val)
39
+ end
40
+
41
+ def save_array(val)
42
+ Yajl::Encoder.encode(val)
43
+ end
44
+
45
+ def format_number(val)
46
+ val.to_i
47
+ end
48
+
49
+ def score_number(val)
50
+ Float(val)
51
+ end
52
+
53
+ def format_float(val)
54
+ Float(val)
55
+ end
56
+ alias_method :score_float, :format_float
57
+
58
+ def format_json(val)
59
+ Yajl::Parser.new(:symbolize_keys => true).parse(val)
60
+ end
61
+
62
+ def save_json(val)
63
+ Yajl::Encoder.encode(val)
64
+ end
65
+
66
+ def format_boolean(val)
67
+ val=="true"
68
+ end
69
+
70
+ def save_boolean(val)
71
+ val === true ? "true" : "false"
72
+ end
73
+
74
+ def score_boolean(val)
75
+ val ? 1 : 0
76
+ end
77
+
78
+ module ClassMethods
79
+
80
+ def date(k)
81
+ set_field_format(k, :format_date)
82
+ set_score_format(k, :score_date)
83
+ end
84
+
85
+ def number(k)
86
+ set_field_format(k, :format_number)
87
+ set_score_format(k, :score_number)
88
+ end
89
+ alias_method :int, :number
90
+
91
+ def float(k)
92
+ set_field_format(k, :format_float)
93
+ set_score_format(k, :score_float)
94
+ end
95
+
96
+ def bool(k)
97
+ set_field_format(k, :format_boolean)
98
+ set_score_format(k, :score_boolean)
99
+ set_save_format(k, :save_boolean)
100
+ end
101
+ alias_method :boolean, :bool
102
+
103
+ def array(k)
104
+ set_field_format(k, :format_array)
105
+ set_save_format(k, :save_array)
106
+ end
107
+
108
+ def json(k)
109
+ set_field_format(k, :format_json)
110
+ set_save_format(k, :save_json)
111
+ end
112
+
113
+ def field_formats
114
+ @field_formats_hash ||= (defined?(superclass.field_formats) ? superclass.field_formats.clone : {})
115
+ end
116
+
117
+ def score_formats
118
+ @score_formats_hash ||= (defined?(superclass.score_formats) ? superclass.score_formats.clone : {})
119
+ end
120
+
121
+ def save_formats
122
+ @save_formats_hash ||= (defined?(superclass.save_formats) ? superclass.save_formats.clone : {})
123
+ end
124
+
125
+ def set_field_format(k, v)
126
+ field_formats_set_locally.add(k)
127
+ field_formats[k] = v
128
+ update_child_class_field_formats(k, v)
129
+ intercept_for_typing!
130
+ end
131
+
132
+ def field_formats_set_locally
133
+ @field_formats_set_locally_set ||= Set.new
134
+ end
135
+
136
+ def inherit_field_format(k, v)
137
+ unless fields_formats_set_locally.include? k
138
+ field_formats[k] = v
139
+ update_child_class_field_formats(k, v)
140
+ end
141
+ intercept_for_typing!
142
+ end
143
+
144
+ def update_child_class_field_formats(k, v)
145
+ child_classes.each do |child_class|
146
+ child_class.inherit_field_format(k, v)
147
+ end
148
+ end
149
+
150
+ def set_score_format(k, v)
151
+ score_formats_set_locally.add(k)
152
+ score_formats[k] = v
153
+ update_child_class_score_formats(k, v)
154
+ intercept_for_typing!
155
+ end
156
+
157
+ def score_formats_set_locally
158
+ @score_formats_set_locally_set ||= Set.new
159
+ end
160
+
161
+ def inherit_score_format(k, v)
162
+ unless scores_formats_set_locally.include? k
163
+ score_formats[k] = v
164
+ update_child_class_score_formats(k, v)
165
+ end
166
+ intercept_for_typing!
167
+ end
168
+
169
+ def update_child_class_score_formats(k, v)
170
+ child_classes.each do |child_class|
171
+ child_class.inherit_score_format(k, v)
172
+ end
173
+ end
174
+
175
+ def set_save_format(k, v)
176
+ save_formats_set_locally.add(k)
177
+ save_formats[k] = v
178
+ update_child_class_save_formats(k, v)
179
+ intercept_for_typing!
180
+ end
181
+
182
+ def save_formats_set_locally
183
+ @save_formats_set_locally_set ||= Set.new
184
+ end
185
+
186
+ def inherit_save_format(k, v)
187
+ unless save_formats_set_locally.include? k
188
+ save_formats[k] = v
189
+ update_child_class_save_formats(k, v)
190
+ end
191
+ intercept_for_typing!
192
+ end
193
+
194
+ def update_child_class_save_formats(k, v)
195
+ child_classes.each do |child_class|
196
+ child_class.inherit_save_format(k, v)
197
+ end
198
+ end
199
+
200
+ def register_format(k,fmt)
201
+ send(fmt, k)
202
+ end
203
+
204
+ def describe
205
+ all_keys.inject({}) do |acc,(k,v)|
206
+ if field_formats[k.to_sym]
207
+ acc[k.to_sym] ||= [field_formats[k.to_sym].to_s.gsub(/^format_/,'').to_sym, 0]
208
+ else
209
+ acc[k.to_sym] ||= [:string, 0]
210
+ end
211
+ acc[k.to_sym][1] += 1
212
+ acc
213
+ end
214
+ end
215
+
216
+ def dump_schema(file)
217
+ child_classes_set.sort {|a,b| a.name <=> b.name}.each do |child|
218
+ file.puts "# #{child.name}"
219
+ # sort fields by number of instances found
220
+ child.describe.sort {|a,b| b[1][1] <=> a[1][1]}.each do |field,(type, count)|
221
+ file.puts "#{field}: #{type} (#{count})"
222
+ end
223
+ file.puts
224
+ end
225
+ end
226
+
227
+ def intercept_for_typing!
228
+ return if @intercepted_for_typing
229
+ self.class_eval do
230
+
231
+ alias_method :untyped_get, :get unless method_defined?(:untyped_get)
232
+ def get(k)
233
+ enforce_format(k,untyped_get(k))
234
+ end
235
+
236
+ alias_method :untyped_mset, :mset unless method_defined?(:untyped_mset)
237
+ def mset(dat)
238
+ dat.merge!(dat) {|k,v1,v2| save_format(k,v1) }
239
+ untyped_mset(dat)
240
+ end
241
+
242
+ alias_method :untyped_set, :set unless method_defined?(:untyped_set)
243
+ def set(k,v)
244
+ untyped_set(k,save_format(k,v))
245
+ end
246
+
247
+ alias_method :untyped_setnx, :setnx unless method_defined?(:untyped_setnx)
248
+ def setnx(k,v)
249
+ untyped_setnx(k,save_format(k,v))
250
+ end
251
+
252
+ end
253
+ @intercepted_for_typing = true
254
+ end
255
+
256
+ private
257
+
258
+ def all_keys(limit=100)
259
+ steps = 0
260
+ all.inject([]) do |acc,obj|
261
+ store.hkeys(obj.hkey).each do |k|
262
+ acc << k.to_sym
263
+ end
264
+ steps += 1
265
+ return acc if steps >= limit
266
+ acc
267
+ end
268
+ end
269
+ end
270
+
271
+ def self.included(base)
272
+ base.extend(ClassMethods)
273
+ end
274
+
275
+ end
276
+ end