riak-client 0.8.3 → 0.9.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,7 +22,7 @@ module Riak
22
22
  # @return [String] the relationship tag (or "rel") of the other resource to this one
23
23
  attr_accessor :tag
24
24
  alias_method :rel, :tag
25
- alias_method :rel=, :tag=
25
+ alias_method :'rel=', :'tag='
26
26
 
27
27
  # @return [String] the bucket of the related resource
28
28
  attr_accessor :bucket
@@ -30,6 +30,10 @@ module Riak
30
30
  # @return [String] the key of the related resource
31
31
  attr_accessor :key
32
32
 
33
+ %w{bucket key}.each do |m|
34
+ class_eval %{ def #{m}=(value); @url = nil; @#{m} = value; end }
35
+ end
36
+
33
37
  # @param [String] header_string the string value of the Link: HTTP header from a Riak response
34
38
  # @return [Array<Link>] an array of Riak::Link structs parsed from the header
35
39
  def self.parse(header_string)
@@ -56,12 +60,13 @@ module Riak
56
60
 
57
61
  # @return [String] the URL (relative or absolute) of the related resource
58
62
  def url
59
- "/riak/#{escape(bucket)}" + (key.blank? ? "" : "/#{escape(key)}")
63
+ @url ||= "/riak/#{escape(bucket)}" + (key.blank? ? "" : "/#{escape(key)}")
60
64
  end
61
65
 
62
66
  def url=(value)
63
- @bucket = CGI.unescape($1) if value =~ %r{^/[^/]+/([^/]+)/?}
64
- @key = CGI.unescape($1) if value =~ %r{^/[^/]+/[^/]+/([^/]+)/?}
67
+ @url = value
68
+ @bucket = unescape($1) if value =~ %r{^/[^/]+/([^/]+)/?}
69
+ @key = unescape($1) if value =~ %r{^/[^/]+/[^/]+/([^/]+)/?}
65
70
  end
66
71
 
67
72
  def inspect; to_s; end
@@ -18,6 +18,8 @@ en:
18
18
  content_type_undefined: "content_type is not defined!"
19
19
  empty_map_reduce_query: "Specify one or more query phases to your MapReduce."
20
20
  failed_request: "Expected %{expected} from Riak but received %{code}. %{body}"
21
+ filter_needs_block: "Filter %{filter} expects a block."
22
+ filter_arity_mismatch: "Filter %{filter} expects %{expected} arguments but %{received} were given."
21
23
  hash_type: "invalid argument %{hash} is not a Hash"
22
24
  http_configuration: "The %{backend} HTTP backend cannot be used. Please check its requirements."
23
25
  hostname_invalid: "host must be a valid hostname"
@@ -19,7 +19,12 @@ module Riak
19
19
  include Util::Translation
20
20
  include Util::Escape
21
21
 
22
- # @return [Array<[bucket,key]>,String] The bucket/keys for input to the job, or the bucket (all keys).
22
+ autoload :Phase, "riak/map_reduce/phase"
23
+ autoload :FilterBuilder, "riak/map_reduce/filter_builder"
24
+
25
+ # @return [Array<[bucket,key]>,String,Hash<:bucket,:filters>] The
26
+ # bucket/keys for input to the job, or the bucket (all
27
+ # keys), or a hash containing the bucket and key-filters.
23
28
  # @see #add
24
29
  attr_accessor :inputs
25
30
 
@@ -52,9 +57,17 @@ module Riak
52
57
  # @param [String,Bucket] bucket the bucket of the object
53
58
  # @param [String] key the key of the object
54
59
  # @param [String] keydata extra data to pass along with the object to the job
60
+ # @overload add(bucket, filters)
61
+ # Run the job across all keys in the bucket, with the given
62
+ # key-filters. This will replace any other inputs previously
63
+ # added. (Requires Riak 0.14)
64
+ # @param [String,Bucket] bucket the bucket to filter keys from
65
+ # @param [Array<Array>] filters a list of key-filters to apply
66
+ # to the key list
55
67
  # @return [MapReduce] self
56
68
  def add(*params)
57
- params = params.dup.flatten
69
+ params = params.dup
70
+ params = params.first if Array === params.first
58
71
  case params.size
59
72
  when 1
60
73
  p = params.first
@@ -69,14 +82,28 @@ module Riak
69
82
  when 2..3
70
83
  bucket = params.shift
71
84
  bucket = bucket.name if Bucket === bucket
72
- key = params.shift
73
- @inputs << params.unshift(escape(key)).unshift(escape(bucket))
85
+ if Array === params.first
86
+ @inputs = {:bucket => escape(bucket), :filters => params.first }
87
+ else
88
+ key = params.shift
89
+ @inputs << params.unshift(escape(key)).unshift(escape(bucket))
90
+ end
74
91
  end
75
92
  self
76
93
  end
77
94
  alias :<< :add
78
95
  alias :include :add
79
96
 
97
+ # Adds a bucket and key-filters built by the given
98
+ # block. Equivalent to #add with a list of filters.
99
+ # @param [String] bucket the bucket to apply key-filters to
100
+ # @yield [] builder block - instance_eval'ed into a FilterBuilder
101
+ # @return [MapReduce] self
102
+ # @see MapReduce#add
103
+ def filter(bucket, &block)
104
+ add(bucket, FilterBuilder.new(&block).to_a)
105
+ end
106
+
80
107
  # Add a map phase to the job.
81
108
  # @overload map(function)
82
109
  # @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module,function] pair
@@ -144,16 +171,13 @@ module Riak
144
171
  end
145
172
 
146
173
  # Executes this map-reduce job.
147
- # @return [Array<Array>] similar to link-walking, each element is an array of results from a phase where "keep" is true. If there is only one "keep" phase, only the results from that phase will be returned.
148
- def run
174
+ # @return [Array<Array>] similar to link-walking, each element is
175
+ # an array of results from a phase where "keep" is true. If there
176
+ # is only one "keep" phase, only the results from that phase will
177
+ # be returned.
178
+ def run(&block)
149
179
  raise MapReduceError.new(t("empty_map_reduce_query")) if @query.empty?
150
- response = @client.http.post(200, @client.mapred, to_json, {"Content-Type" => "application/json", "Accept" => "application/json"})
151
- begin
152
- raise unless response[:headers]['content-type'].include?('application/json')
153
- JSON.parse(response[:body])
154
- rescue
155
- response
156
- end
180
+ @client.backend.mapred(self, &block)
157
181
  rescue FailedRequest => fr
158
182
  if fr.code == 500 && fr.headers['content-type'].include?("application/json")
159
183
  raise MapReduceError.new(fr.body)
@@ -161,95 +185,5 @@ module Riak
161
185
  raise fr
162
186
  end
163
187
  end
164
-
165
- # Represents an individual phase in a map-reduce pipeline. Generally you'll want to call
166
- # methods of MapReduce instead of using this directly.
167
- class Phase
168
- include Util::Translation
169
- # @return [Symbol] the type of phase - :map, :reduce, or :link
170
- attr_accessor :type
171
-
172
- # @return [String, Array<String, String>, Hash, WalkSpec] For :map and :reduce types, the Javascript function to run (as a string or hash with bucket/key), or the module + function in Erlang to run. For a :link type, a {Riak::WalkSpec} or an equivalent hash.
173
- attr_accessor :function
174
-
175
- # @return [String] the language of the phase's function - "javascript" or "erlang". Meaningless for :link type phases.
176
- attr_accessor :language
177
-
178
- # @return [Boolean] whether results of this phase will be returned
179
- attr_accessor :keep
180
-
181
- # @return [Array] any extra static arguments to pass to the phase
182
- attr_accessor :arg
183
-
184
- # Creates a phase in the map-reduce pipeline
185
- # @param [Hash] options options for the phase
186
- # @option options [Symbol] :type one of :map, :reduce, :link
187
- # @option options [String] :language ("javascript") "erlang" or "javascript"
188
- # @option options [String, Array, Hash] :function In the case of Javascript, a literal function in a string, or a hash with :bucket and :key. In the case of Erlang, an Array of [module, function]. For a :link phase, a hash including any of :bucket, :tag or a WalkSpec.
189
- # @option options [Boolean] :keep (false) whether to return the results of this phase
190
- # @option options [Array] :arg (nil) any extra static arguments to pass to the phase
191
- def initialize(options={})
192
- self.type = options[:type]
193
- self.language = options[:language] || "javascript"
194
- self.function = options[:function]
195
- self.keep = options[:keep] || false
196
- self.arg = options[:arg]
197
- end
198
-
199
- def type=(value)
200
- raise ArgumentError, t("invalid_phase_type") unless value.to_s =~ /^(map|reduce|link)$/i
201
- @type = value.to_s.downcase.to_sym
202
- end
203
-
204
- def function=(value)
205
- case value
206
- when Array
207
- raise ArgumentError, t("module_function_pair_required") unless value.size == 2
208
- @language = "erlang"
209
- when Hash
210
- raise ArgumentError, t("stored_function_invalid") unless type == :link || value.has_key?(:bucket) && value.has_key?(:key)
211
- @language = "javascript"
212
- when String
213
- @language = "javascript"
214
- when WalkSpec
215
- raise ArgumentError, t("walk_spec_invalid_unless_link") unless type == :link
216
- else
217
- raise ArgumentError, t("invalid_function_value", :value => value.inspect)
218
- end
219
- @function = value
220
- end
221
-
222
- # Converts the phase to JSON for use while invoking a job.
223
- # @return [String] a JSON representation of the phase
224
- def to_json(*a)
225
- as_json.to_json(*a)
226
- end
227
-
228
- # Converts the phase to its JSON-compatible representation for job invocation.
229
- # @return [Hash] a Hash-equivalent of the phase
230
- def as_json(options=nil)
231
- obj = case type
232
- when :map, :reduce
233
- defaults = {"language" => language, "keep" => keep}
234
- case function
235
- when Hash
236
- defaults.merge(function)
237
- when String
238
- if function =~ /\s*function/
239
- defaults.merge("source" => function)
240
- else
241
- defaults.merge("name" => function)
242
- end
243
- when Array
244
- defaults.merge("module" => function[0], "function" => function[1])
245
- end
246
- when :link
247
- spec = WalkSpec.normalize(function).first
248
- {"bucket" => spec.bucket, "tag" => spec.tag, "keep" => spec.keep || keep}
249
- end
250
- obj["arg"] = arg if arg
251
- { type => obj }
252
- end
253
- end
254
188
  end
255
189
  end
@@ -0,0 +1,106 @@
1
+ # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'riak'
15
+
16
+ module Riak
17
+ class MapReduce
18
+ # Builds key-filter lists for MapReduce inputs in a DSL-like fashion.
19
+ class FilterBuilder
20
+ include Util::Translation
21
+
22
+ # Known filters available in riak_kv_mapred_filters, mapped to
23
+ # their arities. These are turned into instance methods.
24
+ # Example:
25
+ #
26
+ # FilterBuilder.new do
27
+ # string_to_int
28
+ # less_than 50
29
+ # end
30
+ FILTERS = {
31
+ :int_to_string => 0,
32
+ :string_to_int => 0,
33
+ :float_to_string => 0,
34
+ :string_to_float => 0,
35
+ :to_upper => 0,
36
+ :to_lower => 0,
37
+ :tokenize => 2,
38
+ :urldecode => 0,
39
+ :greater_than => 1,
40
+ :less_than => 1,
41
+ :greater_than_eq => 1,
42
+ :less_than_eq => 1,
43
+ :between => [2,3],
44
+ :matches => 1,
45
+ :neq => 1,
46
+ :eq => 1,
47
+ :set_member => 1,
48
+ :similar_to => 2,
49
+ :starts_with => 1,
50
+ :ends_with => 1
51
+ }
52
+
53
+ # Available logical operations for joining filter chains. These
54
+ # are turned into instance methods with leading underscores,
55
+ # with aliases to uppercase versions.
56
+ # Example:
57
+ #
58
+ # FilterBuilder.new do
59
+ # string_to_int
60
+ # AND do
61
+ # greater_than_eq 50
62
+ # neq 100
63
+ # end
64
+ # end
65
+ LOGICAL_OPERATIONS = %w{and or not}
66
+
67
+ FILTERS.each do |f,arity|
68
+ class_eval <<-CODE
69
+ def #{f}(*args)
70
+ raise ArgumentError.new(t("filter_arity_mismatch", :filter => :#{f}, :expected => #{arity.inspect}, :received => args.size)) unless Array(#{arity.inspect}).include?(args.size)
71
+ @filters << ([:#{f}] + args)
72
+ end
73
+ CODE
74
+ end
75
+
76
+ LOGICAL_OPERATIONS.each do |op|
77
+ class_eval <<-CODE
78
+ def _#{op}(&block)
79
+ raise ArgumentError.new(t('filter_needs_block', :filter => '#{op}')) unless block_given?
80
+ @filters << [:#{op}, self.class.new(&block).to_a]
81
+ end
82
+ alias :#{op.to_s.upcase} :_#{op}
83
+ CODE
84
+ end
85
+
86
+ # Creates a new FilterBuilder. Pass a block that will be
87
+ # instance_eval'ed to construct the sequence of filters.
88
+ def initialize(&block)
89
+ @filters = []
90
+ instance_eval(&block) if block_given?
91
+ end
92
+
93
+ # Wraps multi-step filters for use inside logical
94
+ # operations. Does not correspond to an actual filter.
95
+ def sequence(&block)
96
+ @filters << self.class.new(&block).to_a
97
+ end
98
+ alias :seq :sequence
99
+
100
+ # @return A list of filters for handing to the MapReduce inputs.
101
+ def to_a
102
+ @filters
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,108 @@
1
+ # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'riak'
15
+
16
+ module Riak
17
+ class MapReduce
18
+ # Represents an individual phase in a map-reduce pipeline. Generally you'll want to call
19
+ # methods of MapReduce instead of using this directly.
20
+ class Phase
21
+ include Util::Translation
22
+ # @return [Symbol] the type of phase - :map, :reduce, or :link
23
+ attr_accessor :type
24
+
25
+ # @return [String, Array<String, String>, Hash, WalkSpec] For :map and :reduce types, the Javascript function to run (as a string or hash with bucket/key), or the module + function in Erlang to run. For a :link type, a {Riak::WalkSpec} or an equivalent hash.
26
+ attr_accessor :function
27
+
28
+ # @return [String] the language of the phase's function - "javascript" or "erlang". Meaningless for :link type phases.
29
+ attr_accessor :language
30
+
31
+ # @return [Boolean] whether results of this phase will be returned
32
+ attr_accessor :keep
33
+
34
+ # @return [Array] any extra static arguments to pass to the phase
35
+ attr_accessor :arg
36
+
37
+ # Creates a phase in the map-reduce pipeline
38
+ # @param [Hash] options options for the phase
39
+ # @option options [Symbol] :type one of :map, :reduce, :link
40
+ # @option options [String] :language ("javascript") "erlang" or "javascript"
41
+ # @option options [String, Array, Hash] :function In the case of Javascript, a literal function in a string, or a hash with :bucket and :key. In the case of Erlang, an Array of [module, function]. For a :link phase, a hash including any of :bucket, :tag or a WalkSpec.
42
+ # @option options [Boolean] :keep (false) whether to return the results of this phase
43
+ # @option options [Array] :arg (nil) any extra static arguments to pass to the phase
44
+ def initialize(options={})
45
+ self.type = options[:type]
46
+ self.language = options[:language] || "javascript"
47
+ self.function = options[:function]
48
+ self.keep = options[:keep] || false
49
+ self.arg = options[:arg]
50
+ end
51
+
52
+ def type=(value)
53
+ raise ArgumentError, t("invalid_phase_type") unless value.to_s =~ /^(map|reduce|link)$/i
54
+ @type = value.to_s.downcase.to_sym
55
+ end
56
+
57
+ def function=(value)
58
+ case value
59
+ when Array
60
+ raise ArgumentError, t("module_function_pair_required") unless value.size == 2
61
+ @language = "erlang"
62
+ when Hash
63
+ raise ArgumentError, t("stored_function_invalid") unless type == :link || value.has_key?(:bucket) && value.has_key?(:key)
64
+ @language = "javascript"
65
+ when String
66
+ @language = "javascript"
67
+ when WalkSpec
68
+ raise ArgumentError, t("walk_spec_invalid_unless_link") unless type == :link
69
+ else
70
+ raise ArgumentError, t("invalid_function_value", :value => value.inspect)
71
+ end
72
+ @function = value
73
+ end
74
+
75
+ # Converts the phase to JSON for use while invoking a job.
76
+ # @return [String] a JSON representation of the phase
77
+ def to_json(*a)
78
+ as_json.to_json(*a)
79
+ end
80
+
81
+ # Converts the phase to its JSON-compatible representation for job invocation.
82
+ # @return [Hash] a Hash-equivalent of the phase
83
+ def as_json(options=nil)
84
+ obj = case type
85
+ when :map, :reduce
86
+ defaults = {"language" => language, "keep" => keep}
87
+ case function
88
+ when Hash
89
+ defaults.merge(function)
90
+ when String
91
+ if function =~ /\s*function/
92
+ defaults.merge("source" => function)
93
+ else
94
+ defaults.merge("name" => function)
95
+ end
96
+ when Array
97
+ defaults.merge("module" => function[0], "function" => function[1])
98
+ end
99
+ when :link
100
+ spec = WalkSpec.normalize(function).first
101
+ {"bucket" => spec.bucket, "tag" => spec.tag, "keep" => spec.keep || keep}
102
+ end
103
+ obj["arg"] = arg if arg
104
+ { type => obj }
105
+ end
106
+ end
107
+ end
108
+ end
@@ -13,14 +13,15 @@
13
13
  # limitations under the License.
14
14
  require 'riak'
15
15
  require 'set'
16
+ require 'time'
16
17
 
17
18
  module Riak
18
19
  # Represents the data and metadata stored in a bucket/key pair in
19
- # the Riak database, the base unit of data manipulation.
20
+ # the Riak database, the base unit of data manipulation.
20
21
  class RObject
21
- include Util
22
22
  include Util::Translation
23
23
  include Util::Escape
24
+ extend Util::Escape
24
25
 
25
26
  # @return [Bucket] the bucket in which this object is contained
26
27
  attr_accessor :bucket
@@ -57,7 +58,7 @@ module Riak
57
58
  # @return [Array<RObject>] An array of RObject instances
58
59
  def self.load_from_mapreduce(client, response)
59
60
  response.map do |item|
60
- RObject.new(client[CGI.unescape(item['bucket'])], CGI.unescape(item['key'])).load_from_mapreduce(item)
61
+ RObject.new(client[unescape(item['bucket'])], unescape(item['key'])).load_from_mapreduce(item)
61
62
  end
62
63
  end
63
64
 
@@ -72,27 +73,6 @@ module Riak
72
73
  yield self if block_given?
73
74
  end
74
75
 
75
- # Load object data from an HTTP response
76
- # @param [Hash] response a response from {Riak::Client::HTTPBackend}
77
- def load(response)
78
- extract_header(response, "location", :key) {|v| URI.unescape(v.split("/").last) }
79
- extract_header(response, "content-type", :content_type)
80
- extract_header(response, "x-riak-vclock", :vclock)
81
- extract_header(response, "link", :links) {|v| Set.new(Link.parse(v)) }
82
- extract_header(response, "etag", :etag)
83
- extract_header(response, "last-modified", :last_modified) {|v| Time.httpdate(v) }
84
- @meta = response[:headers].inject({}) do |h,(k,v)|
85
- if k =~ /x-riak-meta-(.*)/
86
- h[$1] = v
87
- end
88
- h
89
- end
90
- @conflict = (response[:code].to_i == 300 && content_type =~ /multipart\/mixed/) rescue false
91
- @siblings = nil
92
- self.raw_data = response[:body] if response[:body].present?
93
- self
94
- end
95
-
96
76
  # Load object data from a map/reduce response item.
97
77
  # This method is used by RObject::load_from_mapreduce to instantiate the necessary
98
78
  # objects.
@@ -115,7 +95,7 @@ module Riak
115
95
  self
116
96
  end
117
97
 
118
- # @return [Object] the unmarshaled form of {#raw_data} stored in riak at this object's key
98
+ # @return [Object] the unmarshaled form of {#raw_data} stored in riak at this object's key
119
99
  def data
120
100
  if @raw_data && !@data
121
101
  @data = deserialize(@raw_data)
@@ -147,37 +127,6 @@ module Riak
147
127
  @raw_data = new_raw_data
148
128
  end
149
129
 
150
- # HTTP header hash that will be sent along when storing the object
151
- # @return [Hash] hash of HTTP Headers
152
- def store_headers
153
- {}.tap do |hash|
154
- hash["Content-Type"] = @content_type
155
- hash["X-Riak-Vclock"] = @vclock if @vclock
156
- if @prevent_stale_writes && @etag.present?
157
- hash["If-Match"] = @etag
158
- elsif @prevent_stale_writes
159
- hash["If-None-Match"] = "*"
160
- end
161
- unless @links.blank?
162
- hash["Link"] = @links.reject {|l| l.rel == "up" }.map(&:to_s).join(", ")
163
- end
164
- unless @meta.blank?
165
- @meta.each do |k,v|
166
- hash["X-Riak-Meta-#{k}"] = v.to_s
167
- end
168
- end
169
- end
170
- end
171
-
172
- # HTTP header hash that will be sent along when reloading the object
173
- # @return [Hash] hash of HTTP headers
174
- def reload_headers
175
- {}.tap do |h|
176
- h['If-None-Match'] = @etag if @etag.present?
177
- h['If-Modified-Since'] = @last_modified.httpdate if @last_modified.present?
178
- end
179
- end
180
-
181
130
  # Store the object in Riak
182
131
  # @param [Hash] options query parameters
183
132
  # @option options [Fixnum] :r the "r" parameter (Read quorum for the implicit read performed when validating the store operation)
@@ -189,23 +138,22 @@ module Riak
189
138
  def store(options={})
190
139
  raise ArgumentError, t("content_type_undefined") unless @content_type.present?
191
140
  params = {:returnbody => true}.merge(options)
192
- method, codes, path = @key.present? ? [:put, [200,204,300], "#{escape(@bucket.name)}/#{escape(@key)}"] : [:post, 201, escape(@bucket.name)]
193
- response = @bucket.client.http.send(method, codes, @bucket.client.prefix, path, params, raw_data, store_headers)
194
- load(response)
141
+ @bucket.client.backend.store_object(self, params[:returnbody], params[:w], params[:dw])
142
+ self
195
143
  end
196
144
 
197
145
  # Reload the object from Riak. Will use conditional GETs when possible.
198
146
  # @param [Hash] options query parameters
199
147
  # @option options [Fixnum] :r the "r" parameter (Read quorum)
200
- # @option options [Boolean] :force will force a reload request if the vclock is not present, useful for reloading the object after a store (not passed in the query params)
148
+ # @option options [Boolean] :force will force a reload request if
149
+ # the vclock is not present, useful for reloading the object after
150
+ # a store (not passed in the query params)
201
151
  # @return [Riak::RObject] self
202
152
  def reload(options={})
203
153
  force = options.delete(:force)
204
154
  return self unless @key && (@vclock || force)
205
- codes = @bucket.allow_mult ? [200,300,304] : [200,304]
206
- response = @bucket.client.http.get(codes, @bucket.client.prefix, escape(@bucket.name), escape(@key), options, reload_headers)
207
- load(response) unless response[:code] == 304
208
- self
155
+ self.etag = self.last_modified = nil if force
156
+ bucket.client.backend.reload_object(self, options[:r])
209
157
  end
210
158
 
211
159
  alias :fetch :reload
@@ -218,17 +166,14 @@ module Riak
218
166
  freeze
219
167
  end
220
168
 
169
+ attr_writer :siblings, :conflict
170
+
221
171
  # Returns sibling objects when in conflict.
222
172
  # @return [Array<RObject>] an array of conflicting sibling objects for this key
223
173
  # @return [self] this object when not in conflict
224
174
  def siblings
225
175
  return self unless conflict?
226
- @siblings ||= Multipart.parse(data, Multipart.extract_boundary(content_type)).map do |part|
227
- RObject.new(self.bucket, self.key) do |sibling|
228
- sibling.load(part)
229
- sibling.vclock = vclock
230
- end
231
- end
176
+ @siblings
232
177
  end
233
178
 
234
179
  # @return [true,false] Whether this object has conflicting sibling objects (divergent vclocks)
@@ -286,21 +231,14 @@ module Riak
286
231
  else
287
232
  @raw_data && "(#{@raw_data.size} bytes)"
288
233
  end
289
- "#<#{self.class.name} #{url} [#{@content_type}]:#{body}>"
234
+ "#<#{self.class.name} {#{bucket.name}#{"," + @key if @key}} [#{@content_type}]:#{body}>"
290
235
  end
291
236
 
292
237
  # Walks links from this object to other objects in Riak.
293
238
  # @param [Array<Hash,WalkSpec>] link specifications for the query
294
239
  def walk(*params)
295
240
  specs = WalkSpec.normalize(*params)
296
- response = @bucket.client.http.get(200, @bucket.client.prefix, escape(@bucket.name), escape(@key), specs.join("/"))
297
- if boundary = Multipart.extract_boundary(response[:headers]['content-type'].first)
298
- Multipart.parse(response[:body], boundary).map do |group|
299
- map_walk_group(group)
300
- end
301
- else
302
- []
303
- end
241
+ @bucket.client.http.link_walk(self, specs)
304
242
  end
305
243
 
306
244
  # Converts the object to a link suitable for linking other objects
@@ -322,14 +260,14 @@ module Riak
322
260
  alias :vector_clock :vclock
323
261
  alias :vector_clock= :vclock=
324
262
 
325
- protected
263
+ protected
326
264
  def load_map_reduce_value(hash)
327
265
  metadata = hash['metadata']
328
266
  extract_if_present(metadata, 'X-Riak-VTag', :etag)
329
267
  extract_if_present(metadata, 'content-type', :content_type)
330
268
  extract_if_present(metadata, 'X-Riak-Last-Modified', :last_modified) { |v| Time.httpdate( v ) }
331
269
  extract_if_present(metadata, 'Links', :links) do |links|
332
- Set.new( links.map { |l| Link.new("#{@bucket.client.prefix}#{l[0]}/#{l[1]}", l[2]) } )
270
+ Set.new( links.map { |l| Link.new(*l) } )
333
271
  end
334
272
  extract_if_present(metadata, 'X-Riak-Meta', :meta) do |meta|
335
273
  Hash[
@@ -349,20 +287,5 @@ module Riak
349
287
  send("#{attribute}=", value)
350
288
  end
351
289
  end
352
-
353
- def extract_header(response, name, attribute=nil, &block)
354
- extract_if_present(response[:headers], name, attribute) do |value|
355
- block ? block.call(value[0]) : value[0]
356
- end
357
- end
358
-
359
- def map_walk_group(group)
360
- group.map do |obj|
361
- if obj[:headers] && obj[:body] && obj[:headers]['location']
362
- bucket, key = $1, $2 if obj[:headers]['location'].first =~ %r{/.*/(.*)/(.*)$}
363
- RObject.new(@bucket.client.bucket(bucket, :keys => false), key).load(obj)
364
- end
365
- end
366
- end
367
290
  end
368
291
  end