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