ripple 0.5.0

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 (75) hide show
  1. data/.document +5 -0
  2. data/.gitignore +26 -0
  3. data/LICENSE +13 -0
  4. data/README.textile +126 -0
  5. data/RELEASE_NOTES.textile +24 -0
  6. data/Rakefile +61 -0
  7. data/VERSION +1 -0
  8. data/lib/riak.rb +45 -0
  9. data/lib/riak/bucket.rb +105 -0
  10. data/lib/riak/client.rb +138 -0
  11. data/lib/riak/client/curb_backend.rb +63 -0
  12. data/lib/riak/client/http_backend.rb +209 -0
  13. data/lib/riak/client/net_http_backend.rb +49 -0
  14. data/lib/riak/failed_request.rb +37 -0
  15. data/lib/riak/i18n.rb +15 -0
  16. data/lib/riak/invalid_response.rb +25 -0
  17. data/lib/riak/link.rb +54 -0
  18. data/lib/riak/locale/en.yml +37 -0
  19. data/lib/riak/map_reduce.rb +240 -0
  20. data/lib/riak/map_reduce_error.rb +20 -0
  21. data/lib/riak/robject.rb +234 -0
  22. data/lib/riak/util/headers.rb +44 -0
  23. data/lib/riak/util/multipart.rb +52 -0
  24. data/lib/riak/util/translation.rb +29 -0
  25. data/lib/riak/walk_spec.rb +113 -0
  26. data/lib/ripple.rb +48 -0
  27. data/lib/ripple/core_ext/casting.rb +96 -0
  28. data/lib/ripple/document.rb +60 -0
  29. data/lib/ripple/document/attribute_methods.rb +111 -0
  30. data/lib/ripple/document/attribute_methods/dirty.rb +52 -0
  31. data/lib/ripple/document/attribute_methods/query.rb +49 -0
  32. data/lib/ripple/document/attribute_methods/read.rb +38 -0
  33. data/lib/ripple/document/attribute_methods/write.rb +36 -0
  34. data/lib/ripple/document/bucket_access.rb +38 -0
  35. data/lib/ripple/document/finders.rb +84 -0
  36. data/lib/ripple/document/persistence.rb +93 -0
  37. data/lib/ripple/document/persistence/callbacks.rb +48 -0
  38. data/lib/ripple/document/properties.rb +85 -0
  39. data/lib/ripple/document/validations.rb +44 -0
  40. data/lib/ripple/embedded_document.rb +38 -0
  41. data/lib/ripple/embedded_document/persistence.rb +46 -0
  42. data/lib/ripple/i18n.rb +15 -0
  43. data/lib/ripple/locale/en.yml +16 -0
  44. data/lib/ripple/property_type_mismatch.rb +23 -0
  45. data/lib/ripple/translation.rb +24 -0
  46. data/ripple.gemspec +159 -0
  47. data/spec/fixtures/cat.jpg +0 -0
  48. data/spec/fixtures/multipart-blank.txt +7 -0
  49. data/spec/fixtures/multipart-with-body.txt +16 -0
  50. data/spec/riak/bucket_spec.rb +141 -0
  51. data/spec/riak/client_spec.rb +169 -0
  52. data/spec/riak/curb_backend_spec.rb +50 -0
  53. data/spec/riak/headers_spec.rb +34 -0
  54. data/spec/riak/http_backend_spec.rb +136 -0
  55. data/spec/riak/link_spec.rb +50 -0
  56. data/spec/riak/map_reduce_spec.rb +347 -0
  57. data/spec/riak/multipart_spec.rb +36 -0
  58. data/spec/riak/net_http_backend_spec.rb +28 -0
  59. data/spec/riak/object_spec.rb +444 -0
  60. data/spec/riak/walk_spec_spec.rb +208 -0
  61. data/spec/ripple/attribute_methods_spec.rb +149 -0
  62. data/spec/ripple/bucket_access_spec.rb +48 -0
  63. data/spec/ripple/callbacks_spec.rb +86 -0
  64. data/spec/ripple/document_spec.rb +35 -0
  65. data/spec/ripple/embedded_document_spec.rb +52 -0
  66. data/spec/ripple/finders_spec.rb +146 -0
  67. data/spec/ripple/persistence_spec.rb +89 -0
  68. data/spec/ripple/properties_spec.rb +195 -0
  69. data/spec/ripple/ripple_spec.rb +43 -0
  70. data/spec/ripple/validations_spec.rb +64 -0
  71. data/spec/spec.opts +1 -0
  72. data/spec/spec_helper.rb +32 -0
  73. data/spec/support/http_backend_implementation_examples.rb +215 -0
  74. data/spec/support/mock_server.rb +58 -0
  75. metadata +221 -0
@@ -0,0 +1,63 @@
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 Client
18
+ # An HTTP backend for Riak::Client that uses the 'curb' library/gem.
19
+ # If the 'curb' library is present, this backend will be preferred to
20
+ # the backend based on Net::HTTP.
21
+ # Conforms to the Riak::Client::HTTPBackend interface.
22
+ class CurbBackend < HTTPBackend
23
+ # @private
24
+ def initialize(client)
25
+ super
26
+ @curl = Curl::Easy.new
27
+ @curl.follow_location = false
28
+ @curl.on_header do |header_line|
29
+ @response_headers.parse(header_line)
30
+ header_line.size
31
+ end
32
+ end
33
+
34
+ private
35
+ def perform(method, uri, headers, expect, data=nil)
36
+ # Setup
37
+ @curl.headers = headers
38
+ @curl.url = uri.to_s
39
+ @response_headers = Riak::Util::Headers.new
40
+ @curl.on_body {|chunk| yield chunk; chunk.size } if block_given?
41
+
42
+ # Perform
43
+ case method
44
+ when :put, :post
45
+ @curl.send("http_#{method}", data)
46
+ else
47
+ @curl.send("http_#{method}")
48
+ end
49
+
50
+ # Verify
51
+ if valid_response?(expect, @curl.response_code)
52
+ result = { :headers => @response_headers.to_hash, :code => @curl.response_code.to_i }
53
+ if return_body?(method, @curl.response_code, block_given?)
54
+ result[:body] = @curl.body_str
55
+ end
56
+ result
57
+ else
58
+ raise FailedRequest.new(method, expect, @curl.response_code, @response_headers.to_hash, @curl.body_str)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,209 @@
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 Client
18
+ class HTTPBackend
19
+ include Util::Translation
20
+ # The Riak::Client that uses this backend
21
+ attr_reader :client
22
+
23
+ # Create an HTTPBackend for the Riak::Client.
24
+ # @param [Client] client the client
25
+ def initialize(client)
26
+ raise ArgumentError, t("client_type", :client => client) unless Client === client
27
+ @client = client
28
+ end
29
+
30
+ # Default header hash sent with every request, based on settings in the client
31
+ # @return [Hash] headers that will be merged with user-specified headers on every request
32
+ def default_headers
33
+ {
34
+ "Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5",
35
+ "X-Riak-ClientId" => @client.client_id
36
+ }
37
+ end
38
+
39
+ # Performs a HEAD request to the specified resource on the Riak server.
40
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
41
+ # @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
42
+ # @overload head(expect, *resource)
43
+ # @overload head(expect, *resource, headers)
44
+ # Send the request with custom headers
45
+ # @param [Hash] headers custom headers to send with the request
46
+ # @return [Hash] response data, containing only the :headers and :code keys
47
+ # @raise [FailedRequest] if the response code doesn't match the expected response
48
+ def head(expect, *resource)
49
+ headers = default_headers.merge(resource.extract_options!)
50
+ verify_path!(resource)
51
+ perform(:head, path(*resource), headers, expect)
52
+ end
53
+
54
+ # Performs a GET request to the specified resource on the Riak server.
55
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
56
+ # @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
57
+ # @overload get(expect, *resource)
58
+ # @overload get(expect, *resource, headers)
59
+ # Send the request with custom headers
60
+ # @param [Hash] headers custom headers to send with the request
61
+ # @overload get(expect, *resource, headers={})
62
+ # Stream the response body through the supplied block
63
+ # @param [Hash] headers custom headers to send with the request
64
+ # @yield [chunk] yields successive chunks of the response body as strings
65
+ # @return [Hash] response data, containing only the :headers and :code keys
66
+ # @return [Hash] response data, containing :headers, :body, and :code keys
67
+ # @raise [FailedRequest] if the response code doesn't match the expected response
68
+ def get(expect, *resource, &block)
69
+ headers = default_headers.merge(resource.extract_options!)
70
+ verify_path!(resource)
71
+ perform(:get, path(*resource), headers, expect, &block)
72
+ end
73
+
74
+ # Performs a PUT request to the specified resource on the Riak server.
75
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
76
+ # @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
77
+ # @param [String] body the request body to send to the server
78
+ # @overload put(expect, *resource, body)
79
+ # @overload put(expect, *resource, body, headers)
80
+ # Send the request with custom headers
81
+ # @param [Hash] headers custom headers to send with the request
82
+ # @overload put(expect, *resource, body, headers={})
83
+ # Stream the response body through the supplied block
84
+ # @param [Hash] headers custom headers to send with the request
85
+ # @yield [chunk] yields successive chunks of the response body as strings
86
+ # @return [Hash] response data, containing only the :headers and :code keys
87
+ # @return [Hash] response data, containing :headers, :code, and :body keys
88
+ # @raise [FailedRequest] if the response code doesn't match the expected response
89
+ def put(expect, *resource, &block)
90
+ headers = default_headers.merge(resource.extract_options!)
91
+ uri, data = verify_path_and_body!(resource)
92
+ perform(:put, path(*uri), headers, expect, data, &block)
93
+ end
94
+
95
+ # Performs a POST request to the specified resource on the Riak server.
96
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
97
+ # @param [String, Array<String>] resource a relative path or array of path segments that will be joined to the root URI
98
+ # @param [String] body the request body to send to the server
99
+ # @overload post(expect, *resource, body)
100
+ # @overload post(expect, *resource, body, headers)
101
+ # Send the request with custom headers
102
+ # @param [Hash] headers custom headers to send with the request
103
+ # @overload post(expect, *resource, body, headers={})
104
+ # Stream the response body through the supplied block
105
+ # @param [Hash] headers custom headers to send with the request
106
+ # @yield [chunk] yields successive chunks of the response body as strings
107
+ # @return [Hash] response data, containing only the :headers and :code keys
108
+ # @return [Hash] response data, containing :headers, :code and :body keys
109
+ # @raise [FailedRequest] if the response code doesn't match the expected response
110
+ def post(expect, *resource, &block)
111
+ headers = default_headers.merge(resource.extract_options!)
112
+ uri, data = verify_path_and_body!(resource)
113
+ perform(:post, path(*uri), headers, expect, data, &block)
114
+ end
115
+
116
+ # Performs a DELETE request to the specified resource on the Riak server.
117
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
118
+ # @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
119
+ # @overload delete(expect, *resource)
120
+ # @overload delete(expect, *resource, headers)
121
+ # Send the request with custom headers
122
+ # @param [Hash] headers custom headers to send with the request
123
+ # @overload delete(expect, *resource, headers={})
124
+ # Stream the response body through the supplied block
125
+ # @param [Hash] headers custom headers to send with the request
126
+ # @yield [chunk] yields successive chunks of the response body as strings
127
+ # @return [Hash] response data, containing only the :headers and :code keys
128
+ # @return [Hash] response data, containing :headers, :code and :body keys
129
+ # @raise [FailedRequest] if the response code doesn't match the expected response
130
+ def delete(expect, *resource, &block)
131
+ headers = default_headers.merge(resource.extract_options!)
132
+ verify_path!(resource)
133
+ perform(:delete, path(*resource), headers, expect, &block)
134
+ end
135
+
136
+ # @return [URI] The calculated root URI for the Riak HTTP endpoint
137
+ def root_uri
138
+ URI.parse("http://#{@client.host}:#{@client.port}")
139
+ end
140
+
141
+ # Calculates an absolute URI from a relative path specification
142
+ # @param [Array<String,Hash>] segments a relative path or sequence of path segments and optional query params Hash that will be joined to the root URI
143
+ # @return [URI] an absolute URI for the resource
144
+ def path(*segments)
145
+ query = segments.extract_options!.to_param
146
+ root_uri.merge(URI.escape(segments.join("/").gsub(/\/+/, "/").sub(/^\//, ''))).tap do |uri|
147
+ uri.query = query if query.present?
148
+ end
149
+ end
150
+
151
+ # Verifies that both a resource path and body are present in the arguments
152
+ # @param [Array] args the arguments to verify
153
+ # @raise [ArgumentError] if the body or resource is missing, or if the body is not a String
154
+ def verify_path_and_body!(args)
155
+ body = args.pop
156
+ begin
157
+ verify_path!(args)
158
+ rescue ArgumentError
159
+ raise ArgumentError, t("path_and_body_required")
160
+ end
161
+
162
+ raise ArgumentError, t("request_body_type") unless String === body || IO === body
163
+ [args, body]
164
+ end
165
+
166
+ # Verifies that the specified resource is valid
167
+ # @param [String, Array] resource the resource specification
168
+ # @raise [ArgumentError] if the resource path is too short
169
+ def verify_path!(resource)
170
+ resource = Array(resource).flatten
171
+ raise ArgumentError, t("resource_path_short") unless resource.length > 1 || resource.include?(@client.mapred)
172
+ end
173
+
174
+ # Checks the expected response codes against the actual response code. Use internally when
175
+ # implementing {#perform}.
176
+ # @param [String, Fixnum, Array<String,Fixnum>] expected the expected response code(s)
177
+ # @param [String, Fixnum] actual the received response code
178
+ # @return [Boolean] whether the actual response code is acceptable given the expectations
179
+ def valid_response?(expected, actual)
180
+ Array(expected).map(&:to_i).include?(actual.to_i)
181
+ end
182
+
183
+ # Checks whether a combination of the HTTP method, response code, and block should
184
+ # result in returning the :body in the response hash. Use internally when implementing {#perform}.
185
+ # @param [Symbol] method the HTTP method
186
+ # @param [String, Fixnum] code the received response code
187
+ # @param [Boolean] has_block whether a streaming block was passed to {#perform}. Pass block_given? to this parameter.
188
+ # @return [Boolean] whether to return the body in the response hash
189
+ def return_body?(method, code, has_block)
190
+ method != :head && !valid_response?([204,205,304], code) && !has_block
191
+ end
192
+
193
+ # Executes requests according to the underlying HTTP client library semantics.
194
+ # @abstract Subclasses must implement this internal method to perform HTTP requests
195
+ # according to the API of their HTTP libraries.
196
+ # @param [Symbol] method one of :head, :get, :post, :put, :delete
197
+ # @param [URI] uri the HTTP URI to request
198
+ # @param [Hash] headers headers to send along with the request
199
+ # @param [Fixnum, Array] expect the expected response code(s)
200
+ # @param [String, IO] body the PUT or POST request body
201
+ # @return [Hash] response data, containing :headers, :code and :body keys. Only :headers and :code should be present when the body is streamed or the method is :head.
202
+ # @yield [chunk] if the method is not :head, successive chunks of the response body will be yielded as strings
203
+ # @raise [NotImplementedError] if a subclass does not implement this method
204
+ def perform(method, uri, headers, expect, body=nil)
205
+ raise NotImplementedError
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,49 @@
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 Client
18
+ # Uses the Ruby standard library Net::HTTP to connect to Riak.
19
+ # We recommend using the CurbBackend, which will
20
+ # be preferred when the 'curb' library is available.
21
+ # Conforms to the Riak::Client::HTTPBackend interface.
22
+ class NetHTTPBackend < HTTPBackend
23
+ private
24
+ def perform(method, uri, headers, expect, data=nil) #:nodoc:
25
+ Net::HTTP.start(uri.host, uri.port) do |http|
26
+ request = Net::HTTP.const_get(method.to_s.camelize).new(uri.request_uri, headers)
27
+ case data
28
+ when String
29
+ request.body = data
30
+ when IO
31
+ request.body_stream = data
32
+ end
33
+ response = http.request(request)
34
+
35
+ if valid_response?(expect, response.code)
36
+ result = {:headers => response.to_hash, :code => response.code.to_i}
37
+ response.read_body {|chunk| yield chunk } if block_given?
38
+ if return_body?(method, response.code, block_given?)
39
+ result[:body] = response.body
40
+ end
41
+ result
42
+ else
43
+ raise FailedRequest.new(method, expect, response.code, response.to_hash, response.body)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
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
+ # Exception raised when the expected response code from Riak
18
+ # fails to match the actual response code.
19
+ class FailedRequest < StandardError
20
+ include Util::Translation
21
+ # @return [Symbol] the HTTP method, one of :head, :get, :post, :put, :delete
22
+ attr_reader :method
23
+ # @return [Fixnum] the expected response code
24
+ attr_reader :expected
25
+ # @return [Fixnum] the received response code
26
+ attr_reader :code
27
+ # @return [Hash] the response headers
28
+ attr_reader :headers
29
+ # @return [String] the response body, if present
30
+ attr_reader :body
31
+
32
+ def initialize(method, expected_code, received_code, headers, body)
33
+ @method, @expected, @code, @headers, @body = method, expected_code, received_code, headers, body
34
+ super t("failed_request", :expected => @expected.inspect, :code => @code, :body => @body)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
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 'active_support/i18n'
15
+ I18n.load_path << File.expand_path("../locale/en.yml", __FILE__)
@@ -0,0 +1,25 @@
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
+ # Raised when Riak returns a response that is in an unexpected format
18
+ class InvalidResponse < StandardError
19
+ def initialize(expected, received, extra="")
20
+ expected = expected.inspect if Hash === expected
21
+ received = received.inspect if Hash === received
22
+ super "Expected #{expected} but received #{received} from Riak #{extra}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,54 @@
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
+ # Represents a link from one object to another in Riak
18
+ class Link
19
+ include Util::Translation
20
+ # @return [String] the URL (relative or absolute) of the related resource
21
+ attr_accessor :url
22
+
23
+ # @return [String] the relationship ("rel") of the other resource to this one
24
+ attr_accessor :rel
25
+
26
+ # @param [String] header_string the string value of the Link: HTTP header from a Riak response
27
+ # @return [Array<Link>] an array of Riak::Link structs parsed from the header
28
+ def self.parse(header_string)
29
+ header_string.scan(%r{<([^>]+)>\s*;\s*(?:rel|riaktag)=\"([^\"]+)\"}).map do |match|
30
+ new(match[0], match[1])
31
+ end
32
+ end
33
+
34
+ def initialize(url, rel)
35
+ @url, @rel = url, rel
36
+ end
37
+
38
+ def inspect; to_s; end
39
+
40
+ def to_s
41
+ %Q[<#{@url}>; riaktag="#{@rel}"]
42
+ end
43
+
44
+ def ==(other)
45
+ other.is_a?(Link) && url == other.url && rel == other.rel
46
+ end
47
+
48
+ def to_walk_spec
49
+ bucket, object = $1, $2 if @url =~ %r{/raw/([^/]+)/([^/]+)/?}
50
+ raise t("bucket_link_conversion") if @rel == "up" || object.nil?
51
+ WalkSpec.new(:bucket => bucket, :tag => @rel)
52
+ end
53
+ end
54
+ end