better-riak-client 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +16 -0
- data/README.markdown +198 -0
- data/RELEASE_NOTES.md +211 -0
- data/better-riak-client.gemspec +61 -0
- data/erl_src/riak_kv_test014_backend.beam +0 -0
- data/erl_src/riak_kv_test014_backend.erl +189 -0
- data/erl_src/riak_kv_test_backend.beam +0 -0
- data/erl_src/riak_kv_test_backend.erl +697 -0
- data/erl_src/riak_search_test_backend.beam +0 -0
- data/erl_src/riak_search_test_backend.erl +175 -0
- data/lib/riak/bucket.rb +221 -0
- data/lib/riak/client/beefcake/messages.rb +213 -0
- data/lib/riak/client/beefcake/object_methods.rb +111 -0
- data/lib/riak/client/beefcake_protobuffs_backend.rb +226 -0
- data/lib/riak/client/decaying.rb +36 -0
- data/lib/riak/client/excon_backend.rb +162 -0
- data/lib/riak/client/feature_detection.rb +88 -0
- data/lib/riak/client/http_backend/configuration.rb +211 -0
- data/lib/riak/client/http_backend/key_streamer.rb +43 -0
- data/lib/riak/client/http_backend/object_methods.rb +106 -0
- data/lib/riak/client/http_backend/request_headers.rb +34 -0
- data/lib/riak/client/http_backend/transport_methods.rb +201 -0
- data/lib/riak/client/http_backend.rb +340 -0
- data/lib/riak/client/net_http_backend.rb +82 -0
- data/lib/riak/client/node.rb +115 -0
- data/lib/riak/client/protobuffs_backend.rb +173 -0
- data/lib/riak/client/search.rb +91 -0
- data/lib/riak/client.rb +540 -0
- data/lib/riak/cluster.rb +151 -0
- data/lib/riak/core_ext/blank.rb +53 -0
- data/lib/riak/core_ext/deep_dup.rb +13 -0
- data/lib/riak/core_ext/extract_options.rb +7 -0
- data/lib/riak/core_ext/json.rb +15 -0
- data/lib/riak/core_ext/slice.rb +18 -0
- data/lib/riak/core_ext/stringify_keys.rb +10 -0
- data/lib/riak/core_ext/symbolize_keys.rb +10 -0
- data/lib/riak/core_ext/to_param.rb +31 -0
- data/lib/riak/core_ext.rb +7 -0
- data/lib/riak/encoding.rb +6 -0
- data/lib/riak/failed_request.rb +81 -0
- data/lib/riak/i18n.rb +5 -0
- data/lib/riak/json.rb +52 -0
- data/lib/riak/link.rb +94 -0
- data/lib/riak/locale/en.yml +53 -0
- data/lib/riak/locale/fr.yml +52 -0
- data/lib/riak/map_reduce/filter_builder.rb +103 -0
- data/lib/riak/map_reduce/phase.rb +98 -0
- data/lib/riak/map_reduce.rb +225 -0
- data/lib/riak/map_reduce_error.rb +7 -0
- data/lib/riak/node/configuration.rb +293 -0
- data/lib/riak/node/console.rb +133 -0
- data/lib/riak/node/control.rb +207 -0
- data/lib/riak/node/defaults.rb +83 -0
- data/lib/riak/node/generation.rb +106 -0
- data/lib/riak/node/log.rb +34 -0
- data/lib/riak/node/version.rb +43 -0
- data/lib/riak/node.rb +38 -0
- data/lib/riak/robject.rb +318 -0
- data/lib/riak/search.rb +3 -0
- data/lib/riak/serializers.rb +74 -0
- data/lib/riak/stamp.rb +77 -0
- data/lib/riak/test_server.rb +89 -0
- data/lib/riak/util/escape.rb +76 -0
- data/lib/riak/util/headers.rb +53 -0
- data/lib/riak/util/multipart/stream_parser.rb +62 -0
- data/lib/riak/util/multipart.rb +52 -0
- data/lib/riak/util/tcp_socket_extensions.rb +58 -0
- data/lib/riak/util/translation.rb +19 -0
- data/lib/riak/version.rb +3 -0
- data/lib/riak/walk_spec.rb +105 -0
- data/lib/riak.rb +21 -0
- metadata +348 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
module Riak
|
2
|
+
class Client
|
3
|
+
# Methods that can be used to determine whether certain features
|
4
|
+
# are supported by the Riak node to which the client backend is
|
5
|
+
# connected.
|
6
|
+
#
|
7
|
+
# Backends must implement the "get_server_version" method,
|
8
|
+
# returning a string representing the Riak node's version. This is
|
9
|
+
# implemented on HTTP using the stats resource, and on Protocol
|
10
|
+
# Buffers using the RpbGetServerInfoReq message.
|
11
|
+
module FeatureDetection
|
12
|
+
# Constants representing Riak versions
|
13
|
+
VERSION = {
|
14
|
+
1 => Gem::Version.new("1.0.0"),
|
15
|
+
1.1 => Gem::Version.new("1.1.0"),
|
16
|
+
1.2 => Gem::Version.new("1.2.0")
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
# @return [String] the version of the Riak node
|
20
|
+
# @abstract
|
21
|
+
def get_server_version
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Gem::Version] the version of the Riak node to which
|
26
|
+
# this backend is connected
|
27
|
+
def server_version
|
28
|
+
@server_version ||= Gem::Version.new(get_server_version)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [true,false] whether MapReduce requests can be submitted without
|
32
|
+
# phases.
|
33
|
+
def mapred_phaseless?
|
34
|
+
at_least? VERSION[1.1]
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [true,false] whether secondary index queries are
|
38
|
+
# supported over Protocol Buffers
|
39
|
+
def pb_indexes?
|
40
|
+
at_least? VERSION[1.2]
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [true,false] whether search queries are supported over
|
44
|
+
# Protocol Buffers
|
45
|
+
def pb_search?
|
46
|
+
at_least? VERSION[1.2]
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [true,false] whether conditional fetch/store semantics
|
50
|
+
# are supported over Protocol Buffers
|
51
|
+
def pb_conditionals?
|
52
|
+
at_least? VERSION[1]
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [true,false] whether additional quorums and FSM
|
56
|
+
# controls are available, e.g. primary quorums, basic_quorum,
|
57
|
+
# notfound_ok
|
58
|
+
def quorum_controls?
|
59
|
+
at_least? VERSION[1]
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [true,false] whether "not found" responses might
|
63
|
+
# include vclocks
|
64
|
+
def tombstone_vclocks?
|
65
|
+
at_least? VERSION[1]
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [true,false] whether partial-fetches (vclock and
|
69
|
+
# metadata only) are supported over Protocol Buffers
|
70
|
+
def pb_head?
|
71
|
+
at_least? VERSION[1]
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
# @return [true,false] whether the server version is the same or
|
76
|
+
# newer than the requested version
|
77
|
+
def at_least?(version)
|
78
|
+
server_version >= version
|
79
|
+
end
|
80
|
+
|
81
|
+
# Backends should call this when their connection is interrupted
|
82
|
+
# or reset so as to facilitate rolling upgrades
|
83
|
+
def reset_server_version
|
84
|
+
@server_version = nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
|
2
|
+
require 'riak/failed_request'
|
3
|
+
require 'riak/client/http_backend'
|
4
|
+
require 'riak/link'
|
5
|
+
|
6
|
+
module Riak
|
7
|
+
class Client
|
8
|
+
class HTTPBackend
|
9
|
+
# Riak 0.14 provides a root URL that enumerates all of the
|
10
|
+
# HTTP endpoints and their paths. This module adds methods to
|
11
|
+
# auto-discover those endpoints via the root URL. It also adds
|
12
|
+
# methods for generating URL paths for specific resources.
|
13
|
+
module Configuration
|
14
|
+
# @return [URI] a URL path to the "ping" resource
|
15
|
+
def ping_path
|
16
|
+
path(riak_kv_wm_ping)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [URI] a URL path to the "stats" resource
|
20
|
+
def stats_path
|
21
|
+
path(riak_kv_wm_stats)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [URI] a URL path to the "mapred" resource
|
25
|
+
# @param options [Hash] query parameters, e.g. chunked=true
|
26
|
+
def mapred_path(options={})
|
27
|
+
path(riak_kv_wm_mapred, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [URI] a URL path for the "buckets list" resource
|
31
|
+
def bucket_list_path(options={})
|
32
|
+
if new_scheme?
|
33
|
+
path(riak_kv_wm_buckets, options.merge(:buckets => true))
|
34
|
+
else
|
35
|
+
path(riak_kv_wm_raw, options.merge(:buckets => true))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [URI] a URL path for the "bucket properties"
|
40
|
+
# resource
|
41
|
+
# @param [String] bucket the bucket name
|
42
|
+
def bucket_properties_path(bucket, options={})
|
43
|
+
if new_scheme?
|
44
|
+
path(riak_kv_wm_buckets, escape(bucket), "props", options)
|
45
|
+
else
|
46
|
+
path(riak_kv_wm_raw, escape(bucket), options.merge(:props => true, :keys => false))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [URI] a URL path for the "list keys" resource
|
51
|
+
# @param [String] bucket the bucket whose keys to list
|
52
|
+
# @param [Hash] options query parameters, e.g. keys=stream
|
53
|
+
def key_list_path(bucket, options={:keys => true})
|
54
|
+
if new_scheme?
|
55
|
+
path(riak_kv_wm_buckets, escape(bucket), "keys", options)
|
56
|
+
else
|
57
|
+
path(riak_kv_wm_raw, escape(bucket), options.merge(:props => false))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [URI] a URL path for the "object" resource
|
62
|
+
# @param [String] bucket the bucket of the object
|
63
|
+
# @param [String,nil] key the key of the object, or nil for a
|
64
|
+
# server-assigned key when using POST
|
65
|
+
def object_path(bucket, key, options={})
|
66
|
+
key = escape(key) if key
|
67
|
+
if new_scheme?
|
68
|
+
path([riak_kv_wm_buckets, escape(bucket), "keys", key, options].compact)
|
69
|
+
else
|
70
|
+
path([riak_kv_wm_raw, escape(bucket), key, options].compact)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [URI] a URL path for the "link-walking" resource
|
75
|
+
# @param [String] bucket the bucket of the origin object
|
76
|
+
# @param [String] key the key of the origin object
|
77
|
+
# @param [Array<WalkSpec>] specs a list of walk specifications
|
78
|
+
# to traverse
|
79
|
+
def link_walk_path(bucket, key, specs)
|
80
|
+
specs = specs.map {|s| s.to_s }
|
81
|
+
if new_scheme?
|
82
|
+
path(riak_kv_wm_buckets, escape(bucket), "keys", escape(key), *specs)
|
83
|
+
else
|
84
|
+
path(riak_kv_wm_link_walker, escape(bucket), escape(key), *specs)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [URI] a URL path for the "index range query"
|
89
|
+
# resource
|
90
|
+
# @param [String] bucket the bucket whose index to query
|
91
|
+
# @param [String] index the name of the index to query
|
92
|
+
# @param [String,Integer] start the start of the range
|
93
|
+
# @param [String,Integer] finish the end of the range
|
94
|
+
def index_range_path(bucket, index, start, finish, options={})
|
95
|
+
raise t('indexes_unsupported') unless new_scheme?
|
96
|
+
path(riak_kv_wm_buckets, escape(bucket), "index", escape(index), escape(start.to_s), escape(finish.to_s), options)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [URI] a URL path for the "index range query"
|
100
|
+
# resource
|
101
|
+
# @param [String] bucket the bucket whose index to query
|
102
|
+
# @param [String] index the name of the index to query
|
103
|
+
# @param [String,Integer] start the start of the range
|
104
|
+
# @param [String,Integer] finish the end of the range
|
105
|
+
def index_eq_path(bucket, index, value, options={})
|
106
|
+
raise t('indexes_unsupported') unless new_scheme?
|
107
|
+
path(riak_kv_wm_buckets, escape(bucket), "index", escape(index), escape(value.to_s), options)
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [URI] a URL path for a Solr query resource
|
111
|
+
# @param [String] index the index to query
|
112
|
+
# @param [String] query the Lucene-style query string
|
113
|
+
# @param [Hash] options additional query options
|
114
|
+
def solr_select_path(index, query, options={})
|
115
|
+
raise t('search_unsupported') unless riak_solr_searcher_wm
|
116
|
+
options = {"q" => query, "wt" => "json"}.merge(options)
|
117
|
+
if index
|
118
|
+
path(riak_solr_searcher_wm, index, 'select', options)
|
119
|
+
else
|
120
|
+
path(riak_solr_searcher_wm, 'select', options)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# @return [URI] a URL path for a Solr update resource
|
125
|
+
# @param [String] index the index to update
|
126
|
+
def solr_update_path(index)
|
127
|
+
raise t('search_unsupported') unless riak_solr_indexer_wm
|
128
|
+
if index
|
129
|
+
path(riak_solr_indexer_wm, index, 'update')
|
130
|
+
else
|
131
|
+
path(riak_solr_indexer_wm, 'update')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [URI] a URL path for the Luwak interface
|
136
|
+
def luwak_path(key)
|
137
|
+
raise t('luwak_unsupported') unless luwak_wm_file
|
138
|
+
if key
|
139
|
+
path(luwak_wm_file, escape(key))
|
140
|
+
else
|
141
|
+
path(luwak_wm_file)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
def server_config
|
147
|
+
@server_config ||= {}.tap do |hash|
|
148
|
+
begin
|
149
|
+
response = get(200, path("/"))
|
150
|
+
Link.parse(response[:headers]['link'].first).each {|l| hash[l.tag.intern] ||= l.url }
|
151
|
+
rescue Riak::FailedRequest
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def new_scheme?
|
157
|
+
!riak_kv_wm_buckets.nil?
|
158
|
+
end
|
159
|
+
|
160
|
+
def get_server_version
|
161
|
+
begin
|
162
|
+
# Attempt to grab the server version from the stats resource
|
163
|
+
stats = send(:stats)
|
164
|
+
stats['riak_kv_version']
|
165
|
+
rescue FailedRequest
|
166
|
+
# If stats is disabled, we can't assume the Riak version
|
167
|
+
# is >= 1.1. However, we can assume the new URL scheme is
|
168
|
+
# at least version 1.0.
|
169
|
+
new_scheme? ? "1.0.0" : "0.14.0"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def riak_kv_wm_buckets
|
174
|
+
server_config[:riak_kv_wm_buckets]
|
175
|
+
end
|
176
|
+
|
177
|
+
def riak_kv_wm_raw
|
178
|
+
server_config[:riak_kv_wm_raw] || node.http_paths[:prefix]
|
179
|
+
end
|
180
|
+
|
181
|
+
def riak_kv_wm_link_walker
|
182
|
+
server_config[:riak_kv_wm_link_walker] || node.http_paths[:prefix]
|
183
|
+
end
|
184
|
+
|
185
|
+
def riak_kv_wm_mapred
|
186
|
+
server_config[:riak_kv_wm_mapred] || node.http_paths[:mapred]
|
187
|
+
end
|
188
|
+
|
189
|
+
def riak_kv_wm_ping
|
190
|
+
server_config[:riak_kv_wm_ping] || "/ping"
|
191
|
+
end
|
192
|
+
|
193
|
+
def riak_kv_wm_stats
|
194
|
+
server_config[:riak_kv_wm_stats] || "/stats"
|
195
|
+
end
|
196
|
+
|
197
|
+
def riak_solr_searcher_wm
|
198
|
+
server_config[:riak_solr_searcher_wm]
|
199
|
+
end
|
200
|
+
|
201
|
+
def riak_solr_indexer_wm
|
202
|
+
server_config[:riak_solr_indexer_wm]
|
203
|
+
end
|
204
|
+
|
205
|
+
def luwak_wm_file
|
206
|
+
server_config[:luwak_wm_file]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'riak/util/escape'
|
2
|
+
require 'riak/json'
|
3
|
+
|
4
|
+
module Riak
|
5
|
+
class Client
|
6
|
+
class HTTPBackend
|
7
|
+
# @private
|
8
|
+
class KeyStreamer
|
9
|
+
include Util::Escape
|
10
|
+
|
11
|
+
def initialize(block)
|
12
|
+
@buffer = ""
|
13
|
+
@block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def accept(chunk)
|
17
|
+
@buffer << chunk
|
18
|
+
consume
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_proc
|
22
|
+
method(:accept).to_proc
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def consume
|
27
|
+
while @buffer =~ /\}\{/
|
28
|
+
stream($~.pre_match + '}')
|
29
|
+
@buffer = '{' + $~.post_match
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def stream(str)
|
34
|
+
obj = JSON.parse(str) rescue nil
|
35
|
+
if obj && obj['keys']
|
36
|
+
@block.call obj['keys'].map(&method(:maybe_unescape))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'set'
|
3
|
+
require 'time'
|
4
|
+
require 'riak/client/http_backend'
|
5
|
+
require 'riak/robject'
|
6
|
+
require 'riak/link'
|
7
|
+
require 'riak/util/multipart'
|
8
|
+
|
9
|
+
module Riak
|
10
|
+
class Client
|
11
|
+
class HTTPBackend
|
12
|
+
# Methods for assisting with the handling of {RObject}s present
|
13
|
+
# in HTTP requests and responses.
|
14
|
+
module ObjectMethods
|
15
|
+
# HTTP header hash that will be sent along when reloading the object
|
16
|
+
# @return [Hash] hash of HTTP headers
|
17
|
+
def reload_headers(robject)
|
18
|
+
{}.tap do |h|
|
19
|
+
h['If-None-Match'] = robject.etag if robject.etag.present?
|
20
|
+
h['If-Modified-Since'] = robject.last_modified.httpdate if robject.last_modified.present?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# HTTP header hash that will be sent along when storing the object
|
25
|
+
# @return [Hash] hash of HTTP Headers
|
26
|
+
def store_headers(robject)
|
27
|
+
{}.tap do |hash|
|
28
|
+
hash["Content-Type"] = robject.content_type
|
29
|
+
hash["X-Riak-Vclock"] = robject.vclock if robject.vclock
|
30
|
+
if robject.prevent_stale_writes && robject.etag.present?
|
31
|
+
hash["If-Match"] = robject.etag
|
32
|
+
elsif robject.prevent_stale_writes
|
33
|
+
hash["If-None-Match"] = "*"
|
34
|
+
end
|
35
|
+
unless robject.links.blank?
|
36
|
+
hash["Link"] = robject.links.reject {|l| l.rel == "up" }.map {|l| l.to_s(new_scheme?) }.join(", ")
|
37
|
+
end
|
38
|
+
unless robject.meta.blank?
|
39
|
+
robject.meta.each do |k,v|
|
40
|
+
hash["X-Riak-Meta-#{k}"] = v.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
unless robject.indexes.blank?
|
44
|
+
robject.indexes.each do |k,v|
|
45
|
+
hash["X-Riak-Index-#{k}"] = v.to_a.sort.map {|i| i.to_s }.join(", ")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Load object data from an HTTP response
|
52
|
+
# @param [Hash] response a response from {Riak::Client::HTTPBackend}
|
53
|
+
def load_object(robject, response)
|
54
|
+
extract_header(robject, response, "location", :key) {|v| URI.unescape(v.match(%r{.*/(.*?)(\?.*)?$})[1]) }
|
55
|
+
extract_header(robject, response, "content-type", :content_type)
|
56
|
+
extract_header(robject, response, "x-riak-vclock", :vclock)
|
57
|
+
extract_header(robject, response, "link", :links) {|v| Set.new(Link.parse(v)) }
|
58
|
+
extract_header(robject, response, "etag", :etag)
|
59
|
+
extract_header(robject, response, "last-modified", :last_modified) {|v| Time.httpdate(v) }
|
60
|
+
robject.meta = response[:headers].inject({}) do |h,(k,v)|
|
61
|
+
if k =~ /x-riak-meta-(.*)/i
|
62
|
+
h[$1] = v
|
63
|
+
end
|
64
|
+
h
|
65
|
+
end
|
66
|
+
robject.indexes = response[:headers].inject(Hash.new {|h,k| h[k] = Set.new }) do |h,(k,v)|
|
67
|
+
if k =~ /x-riak-index-((?:.*)_(?:int|bin))$/i
|
68
|
+
key = $1
|
69
|
+
h[key].merge Array(v).map {|vals| vals.split(/,\s*/).map {|i| key =~ /int$/ ? i.to_i : i } }.flatten
|
70
|
+
end
|
71
|
+
h
|
72
|
+
end
|
73
|
+
robject.conflict = (response[:code] && response[:code].to_i == 300 && robject.content_type =~ /multipart\/mixed/)
|
74
|
+
robject.siblings = robject.conflict? ? extract_siblings(robject, response[:body]) : nil
|
75
|
+
robject.raw_data = response[:body] if response[:body].present? && !robject.conflict?
|
76
|
+
|
77
|
+
robject.conflict? ? robject.attempt_conflict_resolution : robject
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def extract_header(robject, response, name, attribute=nil, &block)
|
82
|
+
extract_if_present(robject, response[:headers], name, attribute) do |value|
|
83
|
+
block ? block.call(value[0]) : value[0]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def extract_if_present(robject, hash, key, attribute=nil)
|
88
|
+
if hash[key].present?
|
89
|
+
attribute ||= key
|
90
|
+
value = block_given? ? yield(hash[key]) : hash[key]
|
91
|
+
robject.send("#{attribute}=", value)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def extract_siblings(robject, data)
|
96
|
+
Util::Multipart.parse(data, Util::Multipart.extract_boundary(robject.content_type)).map do |part|
|
97
|
+
RObject.new(robject.bucket, robject.key) do |sibling|
|
98
|
+
load_object(sibling, part)
|
99
|
+
sibling.vclock = robject.vclock
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|