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,102 @@
1
+ module Seabright
2
+ module Views
3
+
4
+ ViewFieldGetter = "local out = {}
5
+ local key
6
+ local val
7
+ for i=1,#ARGV do
8
+ key = ARGV[i]
9
+ val = redis.call('HGET',KEYS[1],key)
10
+ if val then
11
+ table.insert(out,key)
12
+ table.insert(out,val)
13
+ end
14
+ end
15
+ return out".gsub(/\t/,'').freeze
16
+
17
+ def view_as_hash(name)
18
+ out = {}
19
+ if requested_set = self.class.named_views[name]
20
+ if requested_set.is_a?(Symbol) and self.respond_to?(requested_set)
21
+ out = send(requested_set)
22
+ else
23
+ methods = requested_set[:fields].select {|f| self.respond_to?(f.to_sym) }
24
+ if methods.count > 0
25
+ methods.each do |m|
26
+ out[m.to_s] = send(m.to_sym)
27
+ end
28
+ end
29
+ if requested_set[:fields] && (flds = requested_set[:fields].select {|f| !out.keys.include?(f.to_s) }.map {|f| f.to_s }) && flds.count > 0
30
+ res = Hash[*store.eval(ViewFieldGetter, [hkey], flds)]
31
+ out.merge!(res)
32
+ end
33
+ if requested_set[:procs]
34
+ requested_set[:procs].each do |k,proc|
35
+ out[k.to_s] = proc.call(self)
36
+ end
37
+ end
38
+ if requested_set[:hashes]
39
+ requested_set[:hashes].each do |k,v|
40
+ case v
41
+ when String, Symbol
42
+ out[k.to_s] = get(v)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ out
49
+ end
50
+
51
+ def view_as_json(name)
52
+ Yajl::Encoder.encode(view_as_hash(name))
53
+ end
54
+
55
+ module ClassMethods
56
+
57
+ def named_view(name,*fields)
58
+ named_views[name] = normalize_field_options(fields)
59
+ end
60
+
61
+ def named_views
62
+ @named_views ||= {}
63
+ end
64
+
65
+ def normalize_field_options(fields)
66
+ fields.flatten!
67
+ fields.uniq!
68
+
69
+ options = {}
70
+ if fields.last.is_a?(Hash) # assume an option hash
71
+ options.merge!(fields.slice!(fields.size - 1, 1)[0])
72
+ end
73
+
74
+ # assign a the method as a symbol to be exclusively invoked on view
75
+ # so instead of returning a hash on view, it will return only what was
76
+ # produced by calling the method.
77
+ if options.keys.size > 0 and options[:method]
78
+ out = options[:method].to_sym
79
+ else
80
+ hash = fields.select {|f| f.is_a?(Hash) }.inject({},:merge)
81
+ out = {}
82
+ if (h = hash.select {|k,v| !v.is_a?(Proc) }) && h.count > 0
83
+ out[:hashes] = h
84
+ end
85
+ if (h = hash.select {|k,v| v.is_a?(Proc) }) && h.count > 0
86
+ out[:procs] = h
87
+ end
88
+ if (h = fields.select {|o| o.is_a?(String) || o.is_a?(Symbol) }) && h.count > 0
89
+ out[:fields] = h
90
+ end
91
+ end
92
+ out
93
+ end
94
+
95
+ end
96
+
97
+ def self.included(base)
98
+ base.extend(ClassMethods)
99
+ end
100
+
101
+ end
102
+ end
@@ -1,45 +1,50 @@
1
1
  module Seabright
2
2
  module Indices
3
3
 
4
- # def save_indices
5
- # # self.class.indices.each do |indx|
6
- # # indx.each do |key,idx|
7
- # #
8
- # # end
9
- # # end
10
- # self.class.sort_indices.each do |idx|
11
- # store.zadd(index_key(idx), send(idx).to_i, hkey)
12
- # end
13
- # end
14
-
15
4
  def index_key(idx)
16
5
  self.class.index_key(idx)
17
6
  end
18
7
 
19
- # def save
20
- # super
21
- # save_indices
22
- # end
23
-
24
- def mset(dat)
25
- super(dat)
26
- dat.select {|k,v| self.class.has_sort_index?(k) }.each do |k,v|
27
- store.zadd(index_key(k), score_format(k,v), hkey)
28
- end
29
- end
30
-
31
- def set(k,v)
32
- super(k,v)
33
- if self.class.has_sort_index?(k)
34
- store.zadd(index_key(k), score_format(k,v), hkey)
35
- end
36
- end
37
-
38
8
  module ClassMethods
39
9
 
10
+ def intercept_sets_for_indices!
11
+ return if @intercepted_sets_for_indices
12
+ self.class_eval do
13
+ alias_method :unindexed_set, :set unless method_defined?(:unindexed_set)
14
+ def set(k,v)
15
+ ret = unindexed_set(k,v)
16
+ if self.class.has_sort_index?(k)
17
+ store.zrem(index_key(k), hkey)
18
+ store.zadd(index_key(k), score_format(k,v), hkey)
19
+ end
20
+ ret
21
+ end
22
+ alias_method :unindexed_mset, :mset unless method_defined?(:unindexed_mset)
23
+ def mset(dat)
24
+ ret = unindexed_mset(dat)
25
+ dat.select {|k,v| self.class.has_sort_index?(k) }.each do |k,v|
26
+ store.zrem(index_key(k), hkey)
27
+ store.zadd(index_key(k), score_format(k,v), hkey)
28
+ end
29
+ ret
30
+ end
31
+ alias_method :unindexed_setnx, :setnx unless method_defined?(:unindexed_setnx)
32
+ def setnx(k,v)
33
+ ret = unindexed_setnx(k,v)
34
+ if self.class.has_sort_index?(k)
35
+ store.zrem(index_key(k), hkey)
36
+ store.zadd(index_key(k), score_format(k,v), hkey)
37
+ end
38
+ ret
39
+ end
40
+
41
+ end
42
+ @intercepted_sets_for_indices = true
43
+ end
44
+
40
45
  def indexed(idx,num=-1,reverse=false)
41
46
  out = Enumerator.new do |yielder|
42
- store.send(reverse ? :zrevrange : :zrange, index_key(idx), 0, num).each do |member|
47
+ store.send(reverse ? :zrevrange : :zrange, index_key(idx), 0, num-1).each do |member|
43
48
  if a = self.find_by_key(member)
44
49
  yielder << a
45
50
  end
@@ -58,20 +63,20 @@ module Seabright
58
63
  "#{self.plname}::#{idx}"
59
64
  end
60
65
 
61
- # def index(opts)
62
- # indices << opts
63
- # end
64
- #
65
- # def indices
66
- # @@indices ||= []
67
- # end
68
-
69
66
  def sort_indices
70
67
  @@sort_indices ||= []
71
68
  end
72
69
 
73
70
  def sort_by(k)
74
71
  sort_indices << k.to_sym
72
+ intercept_sets_for_indices!
73
+ end
74
+
75
+ def reindex(k)
76
+ store.del index_key(k)
77
+ all.each do |obj|
78
+ obj.set(k,obj.get(k))
79
+ end
75
80
  end
76
81
 
77
82
  def has_sort_index?(k)
@@ -0,0 +1,23 @@
1
+ module Seabright
2
+ module InheritanceTracking
3
+
4
+ module ClassMethods
5
+ def inherited(child_class)
6
+ child_classes_set.add(child_class)
7
+ end
8
+
9
+ def child_classes_set
10
+ @child_classes_set ||= Set.new
11
+ end
12
+
13
+ def child_classes
14
+ child_classes_set.to_a
15
+ end
16
+ end
17
+
18
+ def self.included(base)
19
+ base.extend(ClassMethods)
20
+ end
21
+
22
+ end
23
+ end
@@ -15,15 +15,15 @@ module Seabright
15
15
 
16
16
  module ClassMethods
17
17
 
18
- def key(ident)
19
- "#{cname}:#{ident.gsub(/^.*:/,'')}"
18
+ def key(ident=nil)
19
+ "#{cname}#{ident ? ":#{ident.gsub(/^.*:/,'')}" : ""}"
20
20
  end
21
21
 
22
- def reserve_key(ident)
22
+ def reserve_key(ident=nil)
23
23
  "#{key(ident)}_reserve"
24
24
  end
25
25
 
26
- def hkey(ident = id)
26
+ def hkey(ident = nil)
27
27
  "#{key(ident)}_h"
28
28
  end
29
29
 
@@ -19,8 +19,12 @@ module Seabright
19
19
  adapters[id] ||= const_get(adapter).new(config(id))
20
20
  end
21
21
 
22
- def configure_store(conf,id=store_name)
22
+ def configure_store(conf,id=store_name,*ids)
23
23
  configs[id] = conf
24
+ ids.each do |i|
25
+ configs[i] = conf
26
+ end
27
+ store(id)
24
28
  end
25
29
 
26
30
  def use_store(id)
@@ -54,6 +58,31 @@ module Seabright
54
58
  configs[id]
55
59
  end
56
60
 
61
+ def stores
62
+ adapters
63
+ end
64
+
65
+ def dump_stores_to_files(path)
66
+ raise "Directory does not exist!" unless Dir.exists?(File.dirname(path))
67
+ adapters.each do |name,adptr|
68
+ if adptr.respond_to? :dump_to_file
69
+ puts "Dumping #{name} into #{path}/#{name.to_s}.dump"
70
+ adptr.dump_to_file("#{path}/#{name.to_s}.dump")
71
+ end
72
+ end
73
+ end
74
+
75
+ def restore_stores_from_files(path)
76
+ raise "Directory does not exist!" unless Dir.exists?(File.dirname(path))
77
+ Dir.glob(path + "/*.dump").each do |file|
78
+ name = file.gsub(/\.[^\.]+$/,'').gsub(/.*\//,'').to_sym
79
+ if (stor = store(name)) && stor.respond_to?(:restore_from_file)
80
+ puts "Restoring #{name} from #{file}"
81
+ stor.restore_from_file(file)
82
+ end
83
+ end
84
+ end
85
+
57
86
  end
58
87
 
59
88
  def self.included(base)
@@ -23,15 +23,18 @@ module Seabright
23
23
  end
24
24
 
25
25
  def reset
26
- @connections.each_index do |i|
27
- @connections[i] = nil
26
+ connections.each_index do |i|
27
+ connections[i] = nil
28
28
  end
29
29
  end
30
30
  alias_method :reconnect!, :reset
31
31
 
32
32
  def connection(num=0)
33
+ connections[num] ||= new_connection
34
+ end
35
+
36
+ def connections
33
37
  @connections ||= []
34
- @connections[num] ||= new_connection
35
38
  end
36
39
 
37
40
  def new_connection
@@ -3,6 +3,7 @@ module Seabright
3
3
  class Redis < Adapter
4
4
 
5
5
  def method_missing(sym, *args, &block)
6
+ return super unless connection.respond_to?(sym)
6
7
  puts "[Storage::Redis] #{sym}(#{args.inspect.gsub(/\[|\]/m,'')})" if Debug.verbose?
7
8
  begin
8
9
  connection.send(sym,*args, &block)
@@ -10,15 +11,109 @@ module Seabright
10
11
  puts "Rescued: #{err.inspect}" if DEBUG
11
12
  reset
12
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)
13
18
  end
14
19
  end
15
20
 
16
21
  def new_connection
17
22
  require 'redis'
18
- # puts "Connecting to Redis with: #{config_opts(:path, :db, :password).inspect}" if DEBUG
19
- ::Redis.new(config_opts(:path, :db, :password))
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
20
115
  end
21
116
 
22
117
  end
23
118
  end
24
- end
119
+ end