riak-client 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.document +5 -0
  2. data/.gitignore +4 -3
  3. data/.rspec +1 -0
  4. data/Gemfile +1 -0
  5. data/RELEASE_NOTES.md +47 -0
  6. data/Rakefile +0 -1
  7. data/erl_src/riak_kv_test_backend.erl +34 -0
  8. data/lib/riak/client.rb +3 -1
  9. data/lib/riak/client/beefcake/messages.rb +49 -1
  10. data/lib/riak/client/beefcake/object_methods.rb +14 -21
  11. data/lib/riak/client/beefcake_protobuffs_backend.rb +58 -12
  12. data/lib/riak/client/decaying.rb +31 -23
  13. data/lib/riak/client/feature_detection.rb +88 -0
  14. data/lib/riak/client/http_backend.rb +27 -6
  15. data/lib/riak/client/http_backend/configuration.rb +13 -0
  16. data/lib/riak/client/http_backend/object_methods.rb +33 -25
  17. data/lib/riak/client/node.rb +7 -2
  18. data/lib/riak/client/protobuffs_backend.rb +54 -3
  19. data/lib/riak/client/search.rb +2 -2
  20. data/lib/riak/conflict.rb +13 -0
  21. data/lib/riak/locale/en.yml +2 -0
  22. data/lib/riak/map_reduce.rb +1 -1
  23. data/lib/riak/map_reduce/filter_builder.rb +2 -2
  24. data/lib/riak/map_reduce/results.rb +49 -0
  25. data/lib/riak/node/console.rb +17 -16
  26. data/lib/riak/node/generation.rb +9 -0
  27. data/lib/riak/rcontent.rb +168 -0
  28. data/lib/riak/robject.rb +37 -157
  29. data/lib/riak/util/escape.rb +5 -1
  30. data/lib/riak/version.rb +1 -1
  31. data/riak-client.gemspec +37 -5
  32. data/spec/fixtures/multipart-basic-conflict.txt +15 -0
  33. data/spec/fixtures/munchausen.txt +1033 -0
  34. data/spec/integration/riak/cluster_spec.rb +1 -1
  35. data/spec/integration/riak/http_backends_spec.rb +23 -2
  36. data/spec/integration/riak/node_spec.rb +2 -2
  37. data/spec/integration/riak/protobuffs_backends_spec.rb +17 -2
  38. data/spec/integration/riak/test_server_spec.rb +1 -1
  39. data/spec/integration/riak/threading_spec.rb +3 -3
  40. data/spec/riak/beefcake_protobuffs_backend_spec.rb +58 -25
  41. data/spec/riak/escape_spec.rb +3 -0
  42. data/spec/riak/feature_detection_spec.rb +61 -0
  43. data/spec/riak/http_backend/object_methods_spec.rb +4 -13
  44. data/spec/riak/http_backend_spec.rb +6 -5
  45. data/spec/riak/map_reduce_spec.rb +0 -5
  46. data/spec/riak/robject_spec.rb +12 -11
  47. data/spec/spec_helper.rb +3 -1
  48. data/spec/support/riak_test.rb +77 -0
  49. data/spec/support/search_corpus_setup.rb +18 -0
  50. data/spec/support/sometimes.rb +1 -1
  51. data/spec/support/test_server.rb +1 -1
  52. data/spec/support/unified_backend_examples.rb +53 -7
  53. data/spec/support/version_filter.rb +4 -11
  54. metadata +56 -22
  55. data/lib/riak/client/pool.rb +0 -180
  56. data/spec/riak/pool_spec.rb +0 -306
@@ -0,0 +1,49 @@
1
+ module Riak
2
+ class MapReduce
3
+ # @api private
4
+ # Collects and normalizes results from MapReduce requests
5
+ class Results
6
+ # Creates a new result collector
7
+ # @param [MapReduce] mr the MapReduce query
8
+ def initialize(mr)
9
+ @keep_count = mr.query.select {|p| p.keep }.size
10
+ @hash = create_results_hash(mr.query)
11
+ end
12
+
13
+ # Adds a new result to the collector
14
+ # @param [Fixnum] phase the phase index
15
+ # @param [Array] data the phase result
16
+ def add(phase, result)
17
+ @hash[phase] += result
18
+ end
19
+
20
+ # Coalesces the query results
21
+ # @return [Array] the query results, coalesced according to the
22
+ # phase configuration
23
+ def report
24
+ if @keep_count > 1
25
+ @hash.to_a.sort.map {|(num, results)| results }
26
+ else
27
+ @hash[@hash.keys.first]
28
+ end
29
+ end
30
+
31
+ private
32
+ def create_results_hash(query)
33
+ # When the query is empty, only bucket/key pairs are returned,
34
+ # but implicitly in phase 0.
35
+ return { 0 => [] } if query.empty?
36
+
37
+ # Pre-populate the hash with empty results for kept phases.
38
+ # Additionally, the last phase is always implictly kept, even
39
+ # when keep is false.
40
+ query.inject({}) do |hash, phase|
41
+ if phase.keep || query[-1] == phase
42
+ hash[query.index(phase)] = []
43
+ end
44
+ hash
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -46,15 +46,25 @@ module Riak
46
46
  end
47
47
  end
48
48
  raise ArgumentError, t('no_pipes', :path => pipedir.to_s) if [@rfile, @wfile].any? {|p| p.nil? }
49
- # We have to open the read pipe AFTER we have sent some data
50
- # to the write pipe, otherwise JRuby hangs.
49
+
51
50
  begin
52
51
  debug "Opening write pipe."
53
52
  @w = open_write_pipe
54
53
  @w.sync = true
55
- debug "Opening read pipe."
56
- @r = open_read_pipe
57
- command 'ok.'
54
+ read_ready = false
55
+ # Using threads, we get around JRuby's blocking-open
56
+ # behavior. The main thread pumps the write pipe full of
57
+ # data so that run_erl will start echoing it into the read
58
+ # pipe once we have started the open. On non-JVM rubies,
59
+ # O_NONBLOCK works and we proceed as expected.
60
+ Thread.new do
61
+ debug "Opening read pipe."
62
+ @r = open_read_pipe
63
+ read_ready = true
64
+ end
65
+ Thread.pass
66
+ @w.print "\n" until read_ready
67
+ command "ok."
58
68
  debug "Initialized console: #{@r.inspect} #{@w.inspect}"
59
69
  rescue => e
60
70
  debug e.message
@@ -102,7 +112,6 @@ module Riak
102
112
  def close
103
113
  @r.close if @r && !@r.closed?
104
114
  @w.close if @w && !@w.closed?
105
- Signal.trap("WINCH", @winch)
106
115
  freeze
107
116
  end
108
117
 
@@ -113,19 +122,11 @@ module Riak
113
122
  end
114
123
 
115
124
  def open_write_pipe
116
- if defined?(::Java)
117
- java.io.FileOutputStream.new(@wfile.to_s).to_io
118
- else
119
- @wfile.open(File::WRONLY|File::NONBLOCK)
120
- end
125
+ @wfile.open(File::WRONLY|File::NONBLOCK)
121
126
  end
122
127
 
123
128
  def open_read_pipe
124
- if defined?(::Java)
125
- IO.popen("cat #{@rfile}", "rb")
126
- else
127
- @rfile.open(File::RDONLY|File::NONBLOCK)
128
- end
129
+ @rfile.open(File::RDONLY|File::NONBLOCK)
129
130
  end
130
131
  end
131
132
  end
@@ -16,6 +16,7 @@ module Riak
16
16
  # Generates the node.
17
17
  def create
18
18
  unless exist?
19
+ touch_ssl_distribution_args
19
20
  create_directories
20
21
  write_scripts
21
22
  write_vm_args
@@ -102,5 +103,13 @@ module Riak
102
103
  @configuration[:vm] = @vm
103
104
  manifest.open('w') {|f| YAML.dump(@configuration, f) }
104
105
  end
106
+
107
+ def touch_ssl_distribution_args
108
+ # To make sure that the ssl_distribution.args file is present,
109
+ # the control script in the source node has to have been run at
110
+ # least once. Running the `chkconfig` command is innocuous
111
+ # enough to accomplish this without other side-effects.
112
+ `#{(source + control_script.basename).to_s} chkconfig`
113
+ end
105
114
  end
106
115
  end
@@ -0,0 +1,168 @@
1
+ require 'set'
2
+ require 'time'
3
+ require 'yaml'
4
+ require 'forwardable'
5
+ require 'riak/util/translation'
6
+ require 'riak/serializers'
7
+
8
+ module Riak
9
+ # Represents single (potentially-conflicted) value stored against a
10
+ # key in the Riak database. This includes the raw value as well as
11
+ # metadata.
12
+ # @since 1.1.0
13
+ class RContent
14
+ extend Forwardable
15
+ include Util::Translation
16
+
17
+ # @return [String] the MIME content type of the value
18
+ attr_accessor :content_type
19
+
20
+ # @return [Set<Link>] a Set of {Riak::Link} objects for relationships between this object and other resources
21
+ attr_accessor :links
22
+
23
+ # @return [String] the ETag header from the most recent HTTP response, useful for caching and reloading
24
+ attr_accessor :etag
25
+
26
+ # @return [Time] the Last-Modified header from the most recent HTTP response, useful for caching and reloading
27
+ attr_accessor :last_modified
28
+
29
+ # @return [Hash] a hash of any X-Riak-Meta-* headers that were in the HTTP response, keyed on the trailing portion
30
+ attr_accessor :meta
31
+
32
+ # @return [Hash<Set>] a hash of secondary indexes, where the
33
+ # key is the index name and the value is a Set of index
34
+ # entries for that index
35
+ attr_accessor :indexes
36
+
37
+ # @return [Riak::RObject] the RObject to which this sibling belongs
38
+ attr_accessor :robject
39
+
40
+ def_delegators :robject, :bucket, :key, :vclock
41
+
42
+ # Creates a new object value. This should not normally need to be
43
+ # called by users of the client. Normal, single-value use can rely
44
+ # on the delegating accessors on {Riak::RObject}.
45
+ # @param [RObject] robject the object that this value belongs to
46
+ # @yield self the new RContent
47
+ def initialize(robject)
48
+ @robject = robject
49
+ @links, @meta = Set.new, {}
50
+ @indexes = new_index_hash
51
+ yield self if block_given?
52
+ end
53
+
54
+ def indexes=(hash)
55
+ @indexes = hash.inject(new_index_hash) do |h, (k,v)|
56
+ h[k].merge([*v])
57
+ h
58
+ end
59
+ end
60
+
61
+ # @return [Object] the unmarshaled form of {#raw_data} stored in riak at this object's key
62
+ def data
63
+ if @raw_data && !@data
64
+ raw = @raw_data.respond_to?(:read) ? @raw_data.read : @raw_data
65
+ @data = deserialize(raw)
66
+ @raw_data = nil
67
+ end
68
+ @data
69
+ end
70
+
71
+ # @param [Object] unmarshaled form of the data to be stored in riak. Object will be serialized using {#serialize} if a known content_type is used. Setting this overrides values stored with {#raw_data=}
72
+ # @return [Object] the object stored
73
+ def data=(new_data)
74
+ if new_data.respond_to?(:read)
75
+ raise ArgumentError.new(t("invalid_io_object"))
76
+ end
77
+
78
+ @raw_data = nil
79
+ @data = new_data
80
+ end
81
+
82
+
83
+ # @return [String] raw data stored in riak for this object's key
84
+ def raw_data
85
+ if @data && !@raw_data
86
+ @raw_data = serialize(@data)
87
+ @data = nil
88
+ end
89
+ @raw_data
90
+ end
91
+
92
+ # @param [String, IO-like] the raw data to be stored in riak at this key, will not be marshaled or manipulated prior to storage. Overrides any data stored by {#data=}
93
+ # @return [String] the data stored
94
+ def raw_data=(new_raw_data)
95
+ @data = nil
96
+ @raw_data = new_raw_data
97
+ end
98
+
99
+ # Serializes the internal object data for sending to Riak. Differs based on the content-type.
100
+ # This method is called internally when storing the object.
101
+ # Automatically serialized formats:
102
+ # * JSON (application/json)
103
+ # * YAML (text/yaml)
104
+ # * Marshal (application/x-ruby-marshal)
105
+ # When given an IO-like object (e.g. File), no serialization will
106
+ # be done.
107
+ # @param [Object] payload the data to serialize
108
+ def serialize(payload)
109
+ Serializers.serialize(@content_type, payload)
110
+ end
111
+
112
+ # Deserializes the internal object data from a Riak response. Differs based on the content-type.
113
+ # This method is called internally when loading the object.
114
+ # Automatically deserialized formats:
115
+ # * JSON (application/json)
116
+ # * YAML (text/yaml)
117
+ # * Marshal (application/x-ruby-marshal)
118
+ # @param [String] body the serialized response body
119
+ def deserialize(body)
120
+ Serializers.deserialize(@content_type, body)
121
+ end
122
+
123
+ # @return [String] A representation suitable for IRB and debugging output
124
+ def inspect
125
+ body = if @data || Serializers[content_type]
126
+ data.inspect
127
+ else
128
+ @raw_data && "(#{@raw_data.size} bytes)"
129
+ end
130
+ "#<#{self.class.name} [#{@content_type}]:#{body}>"
131
+ end
132
+
133
+ # @hidden
134
+ def load_map_reduce_value(hash)
135
+ metadata = hash['metadata']
136
+ extract_if_present(metadata, 'X-Riak-VTag', :etag)
137
+ extract_if_present(metadata, 'content-type', :content_type)
138
+ extract_if_present(metadata, 'X-Riak-Last-Modified', :last_modified) { |v| Time.httpdate( v ) }
139
+ extract_if_present(metadata, 'index', :indexes) do |entries|
140
+ Hash[ entries.map {|k,v| [k, Set.new(Array(v))] } ]
141
+ end
142
+ extract_if_present(metadata, 'Links', :links) do |links|
143
+ Set.new( links.map { |l| Link.new(*l) } )
144
+ end
145
+ extract_if_present(metadata, 'X-Riak-Meta', :meta) do |meta|
146
+ Hash[
147
+ meta.map do |k,v|
148
+ [k.sub(%r{^x-riak-meta-}i, ''), [v]]
149
+ end
150
+ ]
151
+ end
152
+ extract_if_present(hash, 'data', :data) { |v| deserialize(v) }
153
+ end
154
+
155
+ private
156
+ def extract_if_present(hash, key, attribute=nil)
157
+ if hash[key].present?
158
+ attribute ||= key
159
+ value = block_given? ? yield(hash[key]) : hash[key]
160
+ send("#{attribute}=", value)
161
+ end
162
+ end
163
+
164
+ def new_index_hash
165
+ Hash.new {|h,k| h[k] = Set.new }
166
+ end
167
+ end
168
+ end
data/lib/riak/robject.rb CHANGED
@@ -1,12 +1,11 @@
1
- require 'set'
2
- require 'time'
3
- require 'yaml'
1
+ require 'forwardable'
2
+ require 'riak/rcontent'
3
+ require 'riak/conflict'
4
4
  require 'riak/util/translation'
5
5
  require 'riak/util/escape'
6
6
  require 'riak/bucket'
7
7
  require 'riak/link'
8
8
  require 'riak/walk_spec'
9
- require 'riak/serializers'
10
9
 
11
10
  module Riak
12
11
  # Represents the data and metadata stored in a bucket/key pair in
@@ -16,6 +15,7 @@ module Riak
16
15
  extend Util::Translation
17
16
  include Util::Escape
18
17
  extend Util::Escape
18
+ extend Forwardable
19
19
 
20
20
  # @return [Bucket] the bucket in which this object is contained
21
21
  attr_accessor :bucket
@@ -23,28 +23,11 @@ module Riak
23
23
  # @return [String] the key of this object within its bucket
24
24
  attr_accessor :key
25
25
 
26
- # @return [String] the MIME content type of the object
27
- attr_accessor :content_type
28
-
29
26
  # @return [String] the Riak vector clock for the object
30
27
  attr_accessor :vclock
31
28
 
32
- # @return [Set<Link>] a Set of {Riak::Link} objects for relationships between this object and other resources
33
- attr_accessor :links
34
-
35
- # @return [String] the ETag header from the most recent HTTP response, useful for caching and reloading
36
- attr_accessor :etag
37
-
38
- # @return [Time] the Last-Modified header from the most recent HTTP response, useful for caching and reloading
39
- attr_accessor :last_modified
40
-
41
- # @return [Hash] a hash of any X-Riak-Meta-* headers that were in the HTTP response, keyed on the trailing portion
42
- attr_accessor :meta
43
-
44
- # @return [Hash<Set>] a hash of secondary indexes, where the
45
- # key is the index name and the value is a Set of index
46
- # entries for that index
47
- attr_accessor :indexes
29
+ alias :vector_clock :vclock
30
+ alias :vector_clock= :vclock=
48
31
 
49
32
  # @return [Boolean] whether to attempt to prevent stale writes using conditional PUT semantics, If-None-Match: * or If-Match: {#etag}
50
33
  # @see http://wiki.basho.com/display/RIAK/REST+API#RESTAPI-Storeaneworexistingobjectwithakey Riak Rest API Docs
@@ -69,6 +52,16 @@ module Riak
69
52
  @on_conflict_hooks ||= []
70
53
  end
71
54
 
55
+ def_delegators :content, :content_type, :content_type=,
56
+ :links, :links=,
57
+ :etag, :etag=,
58
+ :last_modified, :last_modified=,
59
+ :meta, :meta=,
60
+ :indexes, :indexes=,
61
+ :data, :data=,
62
+ :raw_data, :raw_data=,
63
+ :deserialize, :serialize
64
+
72
65
  # Attempts to resolve conflict using the registered conflict callbacks.
73
66
  #
74
67
  # @return [RObject] the RObject
@@ -102,18 +95,10 @@ module Riak
102
95
  # @see Bucket#get
103
96
  def initialize(bucket, key=nil)
104
97
  @bucket, @key = bucket, key
105
- @links, @meta = Set.new, {}
106
- @indexes = new_index_hash
98
+ @siblings = [ RContent.new(self) ]
107
99
  yield self if block_given?
108
100
  end
109
101
 
110
- def indexes=(hash)
111
- @indexes = hash.inject(new_index_hash) do |h, (k,v)|
112
- h[k].merge([*v])
113
- h
114
- end
115
- end
116
-
117
102
  # Load object data from a map/reduce response item.
118
103
  # This method is used by RObject::load_from_mapreduce to instantiate the necessary
119
104
  # objects.
@@ -121,58 +106,14 @@ module Riak
121
106
  # @return [RObject] self
122
107
  def load_from_mapreduce(response)
123
108
  self.vclock = response['vclock']
124
- if response['values'].size == 1
125
- value = response['values'].first
126
- load_map_reduce_value(value)
127
- else
128
- @conflict = true
129
- @siblings = response['values'].map do |v|
130
- RObject.new(self.bucket, self.key) do |robj|
131
- robj.vclock = self.vclock
132
- robj.load_map_reduce_value(v)
133
- end
109
+ @siblings = response['values'].map do |v|
110
+ RContent.new(self) do |rcontent|
111
+ rcontent.load_map_reduce_value(v)
134
112
  end
135
113
  end
136
114
  self
137
115
  end
138
116
 
139
- # @return [Object] the unmarshaled form of {#raw_data} stored in riak at this object's key
140
- def data
141
- if @raw_data && !@data
142
- raw = @raw_data.respond_to?(:read) ? @raw_data.read : @raw_data
143
- @data = deserialize(raw)
144
- @raw_data = nil
145
- end
146
- @data
147
- end
148
-
149
- # @param [Object] unmarshaled form of the data to be stored in riak. Object will be serialized using {#serialize} if a known content_type is used. Setting this overrides values stored with {#raw_data=}
150
- # @return [Object] the object stored
151
- def data=(new_data)
152
- if new_data.respond_to?(:read)
153
- raise ArgumentError.new(t("invalid_io_object"))
154
- end
155
-
156
- @raw_data = nil
157
- @data = new_data
158
- end
159
-
160
- # @return [String] raw data stored in riak for this object's key
161
- def raw_data
162
- if @data && !@raw_data
163
- @raw_data = serialize(@data)
164
- @data = nil
165
- end
166
- @raw_data
167
- end
168
-
169
- # @param [String, IO-like] the raw data to be stored in riak at this key, will not be marshaled or manipulated prior to storage. Overrides any data stored by {#data=}
170
- # @return [String] the data stored
171
- def raw_data=(new_raw_data)
172
- @data = nil
173
- @raw_data = new_raw_data
174
- end
175
-
176
117
  # Store the object in Riak
177
118
  # @param [Hash] options query parameters
178
119
  # @option options [Fixnum] :r the "r" parameter (Read quorum for the implicit read performed when validating the store operation)
@@ -181,8 +122,10 @@ module Riak
181
122
  # @option options [Boolean] :returnbody (true) whether to return the result of a successful write in the body of the response. Set to false for fire-and-forget updates, set to true to immediately have access to the object's stored representation.
182
123
  # @return [Riak::RObject] self
183
124
  # @raise [ArgumentError] if the content_type is not defined
125
+ # @raise [Conflict] if the object has siblings
184
126
  def store(options={})
185
- raise ArgumentError, t("content_type_undefined") unless @content_type.present?
127
+ raise Conflict, self if conflict?
128
+ raise ArgumentError, t("content_type_undefined") unless content_type.present?
186
129
  @bucket.client.store_object(self, options)
187
130
  self
188
131
  end
@@ -213,54 +156,29 @@ module Riak
213
156
  freeze
214
157
  end
215
158
 
216
- attr_writer :siblings, :conflict
159
+ # Returns sibling values. If the object is not in conflict, then
160
+ # only one value will be present in the array.
161
+ # @return [Array<RContent>] an array of conflicting sibling values
162
+ # for this key, possibly containing only one
163
+ attr_accessor :siblings
217
164
 
218
- # Returns sibling objects when in conflict.
219
- # @return [Array<RObject>] an array of conflicting sibling objects for this key
220
- # @return [Array<self>] a single-element array containing object when not
221
- # in conflict
222
- def siblings
223
- return [self] unless conflict?
224
- @siblings
165
+ # Returns the solitary sibling when not in conflict.
166
+ # @return [RContent] the sole value/sibling on this object
167
+ # @raise [Conflict] when multiple siblings are present
168
+ def content
169
+ raise Conflict, self if conflict?
170
+ @siblings.first
225
171
  end
226
172
 
227
173
  # @return [true,false] Whether this object has conflicting sibling objects (divergent vclocks)
228
174
  def conflict?
229
- @conflict.present?
230
- end
231
-
232
- # Serializes the internal object data for sending to Riak. Differs based on the content-type.
233
- # This method is called internally when storing the object.
234
- # Automatically serialized formats:
235
- # * JSON (application/json)
236
- # * YAML (text/yaml)
237
- # * Marshal (application/x-ruby-marshal)
238
- # When given an IO-like object (e.g. File), no serialization will
239
- # be done.
240
- # @param [Object] payload the data to serialize
241
- def serialize(payload)
242
- Serializers.serialize(@content_type, payload)
243
- end
244
-
245
- # Deserializes the internal object data from a Riak response. Differs based on the content-type.
246
- # This method is called internally when loading the object.
247
- # Automatically deserialized formats:
248
- # * JSON (application/json)
249
- # * YAML (text/yaml)
250
- # * Marshal (application/x-ruby-marshal)
251
- # @param [String] body the serialized response body
252
- def deserialize(body)
253
- Serializers.deserialize(@content_type, body)
175
+ @siblings.size != 1
254
176
  end
255
177
 
256
178
  # @return [String] A representation suitable for IRB and debugging output
257
179
  def inspect
258
- body = if @data || Serializers[content_type]
259
- data.inspect
260
- else
261
- @raw_data && "(#{@raw_data.size} bytes)"
262
- end
263
- "#<#{self.class.name} {#{bucket.name}#{"," + @key if @key}} [#{@content_type}]:#{body}>"
180
+ body = @siblings.map {|s| s.inspect }.join(", ")
181
+ "#<#{self.class.name} {#{bucket.name}#{"," + @key if @key}} [#{body}]>"
264
182
  end
265
183
 
266
184
  # Walks links from this object to other objects in Riak.
@@ -276,43 +194,5 @@ module Riak
276
194
  def to_link(tag)
277
195
  Link.new(@bucket.name, @key, tag)
278
196
  end
279
-
280
- alias :vector_clock :vclock
281
- alias :vector_clock= :vclock=
282
-
283
- protected
284
- def load_map_reduce_value(hash)
285
- metadata = hash['metadata']
286
- extract_if_present(metadata, 'X-Riak-VTag', :etag)
287
- extract_if_present(metadata, 'content-type', :content_type)
288
- extract_if_present(metadata, 'X-Riak-Last-Modified', :last_modified) { |v| Time.httpdate( v ) }
289
- extract_if_present(metadata, 'index', :indexes) do |entries|
290
- Hash[ entries.map {|k,v| [k, Set.new(Array(v))] } ]
291
- end
292
- extract_if_present(metadata, 'Links', :links) do |links|
293
- Set.new( links.map { |l| Link.new(*l) } )
294
- end
295
- extract_if_present(metadata, 'X-Riak-Meta', :meta) do |meta|
296
- Hash[
297
- meta.map do |k,v|
298
- [k.sub(%r{^x-riak-meta-}i, ''), [v]]
299
- end
300
- ]
301
- end
302
- extract_if_present(hash, 'data', :data) { |v| deserialize(v) }
303
- end
304
-
305
- private
306
- def extract_if_present(hash, key, attribute=nil)
307
- if hash[key].present?
308
- attribute ||= key
309
- value = block_given? ? yield(hash[key]) : hash[key]
310
- send("#{attribute}=", value)
311
- end
312
- end
313
-
314
- def new_index_hash
315
- Hash.new {|h,k| h[k] = Set.new }
316
- end
317
197
  end
318
198
  end