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,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
+ en:
15
+ riak:
16
+ client_type: "invalid argument {{client}} is not a Riak::Client"
17
+ string_type: "invalid_argument {{string}} is not a String"
18
+ loading_bucket: "while loading bucket '{{name}}'"
19
+ failed_request: "Expected {{expected}} from Riak but received {{code}}. {{body}}"
20
+ hash_type: "invalid argument {{hash}} is not a Hash"
21
+ path_and_body_required: "You must supply both a resource path and a body."
22
+ request_body_type: "Request body must be a string or IO."
23
+ resource_path_short: "Resource path too short"
24
+ missing_host_and_port: "You must specify a host and port, or use the defaults of 127.0.0.1:8098"
25
+ invalid_client_id: "Invalid client ID, must be a string or between 0 and {{max_id}}"
26
+ hostname_invalid: "host must be a valid hostname"
27
+ port_invalid: "port must be an integer between 0 and 65535"
28
+ install_curb: "curb library not found! Please `gem install curb` for better performance."
29
+ bucket_link_conversion: "Can't convert a bucket link to a walk spec"
30
+ invalid_phase_type: "type must be :map, :reduce, or :link"
31
+ module_function_pair_required: "function must have two elements when an array"
32
+ stored_function_invalid: "function must have :bucket and :key when a hash"
33
+ walk_spec_invalid_unless_link: "WalkSpec is only valid for a function when the type is :link"
34
+ invalid_function_value: "invalid value for function: {{value}}"
35
+ content_type_undefined: "content_type is not defined!"
36
+ too_few_arguments: "too few arguments: {{params}}"
37
+ wrong_argument_count_walk_spec: "wrong number of arguments (one Hash or bucket,tag,keep required)"
@@ -0,0 +1,240 @@
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 for invoking map-reduce jobs using the HTTP interface.
18
+ class MapReduce
19
+ include Util::Translation
20
+ # @return [Array<[bucket,key]>,String] The bucket/keys for input to the job, or the bucket (all keys).
21
+ # @see #add
22
+ attr_accessor :inputs
23
+
24
+ # @return [Array<Phase>] The map and reduce phases that will be executed
25
+ # @see #map
26
+ # @see #reduce
27
+ # @see #link
28
+ attr_accessor :query
29
+
30
+ # Creates a new map-reduce job.
31
+ # @param [Client] client the Riak::Client interface
32
+ # @yield [self] helpful for initializing the job
33
+ def initialize(client)
34
+ @client, @inputs, @query = client, [], []
35
+ yield self if block_given?
36
+ end
37
+
38
+ # Add or replace inputs for the job.
39
+ # @overload add(bucket)
40
+ # Run the job across all keys in the bucket. This will replace any other inputs previously added.
41
+ # @param [String, Bucket] bucket the bucket to run the job on
42
+ # @overload add(bucket,key)
43
+ # Add a bucket/key pair to the job.
44
+ # @param [String,Bucket] bucket the bucket of the object
45
+ # @param [String] key the key of the object
46
+ # @overload add(object)
47
+ # Add an object to the job (by its bucket/key)
48
+ # @param [RObject] object the object to add to the inputs
49
+ # @overload add(bucket, key, keydata)
50
+ # @param [String,Bucket] bucket the bucket of the object
51
+ # @param [String] key the key of the object
52
+ # @param [String] keydata extra data to pass along with the object to the job
53
+ # @return [MapReduce] self
54
+ def add(*params)
55
+ params = params.dup.flatten
56
+ case params.size
57
+ when 1
58
+ p = params.first
59
+ case p
60
+ when Bucket
61
+ @inputs = p.name
62
+ when RObject
63
+ @inputs << [p.bucket.name, p.key]
64
+ when String
65
+ @inputs = p
66
+ end
67
+ when 2..3
68
+ bucket = params.shift
69
+ bucket = bucket.name if Bucket === bucket
70
+ @inputs << params.unshift(bucket)
71
+ end
72
+ self
73
+ end
74
+ alias :<< :add
75
+ alias :include :add
76
+
77
+ # Add a map phase to the job.
78
+ # @overload map(function)
79
+ # @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module,function] pair
80
+ # @overload map(function?, options)
81
+ # @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module, function] pair
82
+ # @param [Hash] options extra options for the phase (see {Phase#initialize})
83
+ # @return [MapReduce] self
84
+ # @see Phase#initialize
85
+ def map(*params)
86
+ options = params.extract_options!
87
+ @query << Phase.new({:type => :map, :function => params.shift}.merge(options))
88
+ self
89
+ end
90
+
91
+ # Add a reduce phase to the job.
92
+ # @overload reduce(function)
93
+ # @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module,function] pair
94
+ # @overload reduce(function?, options)
95
+ # @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module, function] pair
96
+ # @param [Hash] options extra options for the phase (see {Phase#initialize})
97
+ # @return [MapReduce] self
98
+ # @see Phase#initialize
99
+ def reduce(*params)
100
+ options = params.extract_options!
101
+ @query << Phase.new({:type => :reduce, :function => params.shift}.merge(options))
102
+ self
103
+ end
104
+
105
+ # Add a link phase to the job. Link phases follow links attached to objects automatically (a special case of map).
106
+ # @overload link(walk_spec, options={})
107
+ # @param [WalkSpec] walk_spec a WalkSpec that represents the types of links to follow
108
+ # @param [Hash] options extra options for the phase (see {Phase#initialize})
109
+ # @overload link(bucket, tag, keep, options={})
110
+ # @param [String, nil] bucket the bucket to limit links to
111
+ # @param [String, nil] tag the tag to limit links to
112
+ # @param [Boolean] keep whether to keep results of this phase (overrides the phase options)
113
+ # @param [Hash] options extra options for the phase (see {Phase#initialize})
114
+ # @overload link(options)
115
+ # @param [Hash] options options for both the walk spec and link phase
116
+ # @see WalkSpec#initialize
117
+ # @return [MapReduce] self
118
+ # @see Phase#initialize
119
+ def link(*params)
120
+ options = params.extract_options!
121
+ walk_spec_options = options.slice!(:type, :function, :language, :arg) unless params.first
122
+ walk_spec = WalkSpec.normalize(params.shift || walk_spec_options).first
123
+ @query << Phase.new({:type => :link, :function => walk_spec}.merge(options))
124
+ self
125
+ end
126
+
127
+ # Convert the job to JSON for submission over the HTTP interface.
128
+ # @return [String] the JSON representation
129
+ def to_json(options={})
130
+ ActiveSupport::JSON.encode({"inputs" => inputs, "query" => query.map(&:as_json)}, options)
131
+ end
132
+
133
+ # Executes this map-reduce job.
134
+ # @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.
135
+ def run
136
+ response = @client.http.post(200, @client.mapred, to_json, {"Content-Type" => "application/json", "Accept" => "application/json"})
137
+ if response.try(:[], :headers).try(:[],'content-type').include?("application/json")
138
+ JSON.parse(response[:body])
139
+ else
140
+ response
141
+ end
142
+ rescue FailedRequest => fr
143
+ if fr.code == 500 && fr.headers['content-type'].include?("application/json")
144
+ raise MapReduceError.new(fr.body)
145
+ else
146
+ raise fr
147
+ end
148
+ end
149
+
150
+ # Represents an individual phase in a map-reduce pipeline. Generally you'll want to call
151
+ # methods of {MapReduce} instead of using this directly.
152
+ class Phase
153
+ include Util::Translation
154
+ # @return [Symbol] the type of phase - :map, :reduce, or :link
155
+ attr_accessor :type
156
+
157
+ # @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.
158
+ attr_accessor :function
159
+
160
+ # @return [String] the language of the phase's function - "javascript" or "erlang". Meaningless for :link type phases.
161
+ attr_accessor :language
162
+
163
+ # @return [Boolean] whether results of this phase will be returned
164
+ attr_accessor :keep
165
+
166
+ # @return [Array] any extra static arguments to pass to the phase
167
+ attr_accessor :arg
168
+
169
+ # Creates a phase in the map-reduce pipeline
170
+ # @param [Hash] options options for the phase
171
+ # @option options [Symbol] :type one of :map, :reduce, :link
172
+ # @option options [String] :language ("javascript") "erlang" or "javascript"
173
+ # @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.
174
+ # @option options [Boolean] :keep (false) whether to return the results of this phase
175
+ # @option options [Array] :arg (nil) any extra static arguments to pass to the phase
176
+ def initialize(options={})
177
+ self.type = options[:type]
178
+ self.language = options[:language] || "javascript"
179
+ self.function = options[:function]
180
+ self.keep = options[:keep] || false
181
+ self.arg = options[:arg]
182
+ end
183
+
184
+ def type=(value)
185
+ raise ArgumentError, t("invalid_phase_type") unless value.to_s =~ /^(map|reduce|link)$/i
186
+ @type = value.to_s.downcase.to_sym
187
+ end
188
+
189
+ def function=(value)
190
+ case value
191
+ when Array
192
+ raise ArgumentError, t("module_function_pair_required") unless value.size == 2
193
+ @language = "erlang"
194
+ when Hash
195
+ raise ArgumentError, t("stored_function_invalid") unless type == :link || value.has_key?(:bucket) && value.has_key?(:key)
196
+ @language = "javascript"
197
+ when String
198
+ @language = "javascript"
199
+ when WalkSpec
200
+ raise ArgumentError, t("walk_spec_invalid_unless_link") unless type == :link
201
+ else
202
+ raise ArgumentError, t("invalid_function_value", :value => value.inspect)
203
+ end
204
+ @function = value
205
+ end
206
+
207
+ # Converts the phase to JSON for use while invoking a job.
208
+ # @return [String] a JSON representation of the phase
209
+ def to_json(options=nil)
210
+ ActiveSupport::JSON.encode(as_json, options)
211
+ end
212
+
213
+ # Converts the phase to its JSON-compatible representation for job invocation.
214
+ # @return [Hash] a Hash-equivalent of the phase
215
+ def as_json(options=nil)
216
+ obj = case type
217
+ when :map, :reduce
218
+ defaults = {"language" => language, "keep" => keep}
219
+ case function
220
+ when Hash
221
+ defaults.merge(function)
222
+ when String
223
+ if function =~ /\s*function/
224
+ defaults.merge("source" => function)
225
+ else
226
+ defaults.merge("name" => function)
227
+ end
228
+ when Array
229
+ defaults.merge("module" => function[0], "function" => function[1])
230
+ end
231
+ when :link
232
+ spec = WalkSpec.normalize(function).first
233
+ {"bucket" => spec.bucket, "tag" => spec.tag, "keep" => spec.keep || keep}
234
+ end
235
+ obj["arg"] = arg if arg
236
+ { type => obj }
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,20 @@
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 an error occurred in the Javascript map-reduce chain.
18
+ # The message will be the body of the JSON error response.
19
+ class MapReduceError < StandardError; end
20
+ end
@@ -0,0 +1,234 @@
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
+ # Parent class of all object types supported by ripple. {Riak::RObject} represents
18
+ # the data and metadata stored in a bucket/key pair in the Riak database.
19
+ class RObject
20
+ include Util
21
+ include Util::Translation
22
+
23
+ # @return [Bucket] the bucket in which this object is contained
24
+ attr_accessor :bucket
25
+
26
+ # @return [String] the key of this object within its bucket
27
+ attr_accessor :key
28
+
29
+ # @return [String] the MIME content type of the object
30
+ attr_accessor :content_type
31
+
32
+ # @return [String] the Riak vector clock for the object
33
+ attr_accessor :vclock
34
+ alias_attribute :vector_clock, :vclock
35
+
36
+ # @return [Object] the data stored in Riak at this object's key. Varies in format by content-type, defaulting to String from the response body.
37
+ attr_accessor :data
38
+
39
+ # @return [Array<Link>] an array of {Riak::Link} objects for relationships between this object and other resources
40
+ attr_accessor :links
41
+
42
+ # @return [String] the ETag header from the most recent HTTP response, useful for caching and reloading
43
+ attr_accessor :etag
44
+
45
+ # @return [Time] the Last-Modified header from the most recent HTTP response, useful for caching and reloading
46
+ attr_accessor :last_modified
47
+
48
+ # @return [Hash] a hash of any X-Riak-Meta-* headers that were in the HTTP response, keyed on the trailing portion
49
+ attr_accessor :meta
50
+
51
+ # Create a new object manually
52
+ # @param [Bucket] bucket the bucket in which the object exists
53
+ # @param [String] key the key at which the object resides. If nil, a key will be assigned when the object is saved.
54
+ # @see Bucket#get
55
+ def initialize(bucket, key=nil)
56
+ @bucket, @key = bucket, key
57
+ @links, @meta = [], {}
58
+ end
59
+
60
+ # Load object data from an HTTP response
61
+ # @param [Hash] response a response from {Riak::Client::HTTPBackend}
62
+ def load(response)
63
+ extract_header(response, "location", :key) {|v| URI.unescape(v.split("/").last) }
64
+ extract_header(response, "content-type", :content_type)
65
+ extract_header(response, "x-riak-vclock", :vclock)
66
+ extract_header(response, "link", :links) {|v| Link.parse(v) }
67
+ extract_header(response, "etag", :etag)
68
+ extract_header(response, "last-modified", :last_modified) {|v| Time.httpdate(v) }
69
+ @meta = response[:headers].inject({}) do |h,(k,v)|
70
+ if k =~ /x-riak-meta-(.*)/
71
+ h[$1] = v
72
+ end
73
+ h
74
+ end
75
+ @data = deserialize(response[:body]) if response[:body].present?
76
+ self
77
+ end
78
+
79
+ # HTTP header hash that will be sent along when storing the object
80
+ # @return [Hash] hash of HTTP Headers
81
+ def store_headers
82
+ {}.tap do |hash|
83
+ hash["Content-Type"] = @content_type
84
+ hash["X-Riak-Vclock"] = @vclock if @vclock
85
+ unless @links.blank?
86
+ hash["Link"] = @links.reject {|l| l.rel == "up" }.map(&:to_s).join(", ")
87
+ end
88
+ unless @meta.blank?
89
+ @meta.each do |k,v|
90
+ hash["X-Riak-Meta-#{k}"] = v.to_s
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # HTTP header hash that will be sent along when reloading the object
97
+ # @return [Hash] hash of HTTP headers
98
+ def reload_headers
99
+ {}.tap do |h|
100
+ h['If-None-Match'] = @etag if @etag.present?
101
+ h['If-Modified-Since'] = @last_modified.httpdate if @last_modified.present?
102
+ end
103
+ end
104
+
105
+ # Store the object in Riak
106
+ # @param [Hash] options query parameters
107
+ # @option options [Fixnum] :r the "r" parameter (Read quorum for the implicit read performed when validating the store operation)
108
+ # @option options [Fixnum] :w the "w" parameter (Write quorum)
109
+ # @option options [Fixnum] :dw the "dw" parameter (Durable-write quorum)
110
+ # @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.
111
+ # @return [Riak::RObject] self
112
+ # @raise [ArgumentError] if the content_type is not defined
113
+ def store(options={})
114
+ raise ArgumentError, t("content_type_undefined") unless @content_type.present?
115
+ params = {:returnbody => true}.merge(options)
116
+ method, codes, path = @key.present? ? [:put, [200,204], "#{@bucket.name}/#{@key}"] : [:post, 201, @bucket.name]
117
+ response = @bucket.client.http.send(method, codes, @bucket.client.prefix, path, params, serialize(data), store_headers)
118
+ load(response)
119
+ end
120
+
121
+ # Reload the object from Riak. Will use conditional GETs when possible.
122
+ # @param [Hash] options query parameters
123
+ # @option options [Fixnum] :r the "r" parameter (Read quorum)
124
+ # @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)
125
+ # @return [Riak::RObject] self
126
+ def reload(options={})
127
+ force = options.delete(:force)
128
+ return self unless @key && (@vclock || force)
129
+ response = @bucket.client.http.get([200, 304], @bucket.client.prefix, @bucket.name, @key, options, reload_headers)
130
+ load(response) if response[:code] == 200
131
+ self
132
+ end
133
+
134
+ alias :fetch :reload
135
+
136
+ # Delete the object from Riak and freeze this instance. Will work whether or not the object actually
137
+ # exists in the Riak database.
138
+ def delete
139
+ return if key.blank?
140
+ @bucket.client.http.delete([204,404], @bucket.client.prefix, @bucket.name, key)
141
+ freeze
142
+ end
143
+
144
+ # Serializes the internal object data for sending to Riak. Differs based on the content-type.
145
+ # This method is called internally when storing the object.
146
+ # Automatically serialized formats:
147
+ # * JSON (application/json)
148
+ # * YAML (text/yaml)
149
+ # * Marshal (application/octet-stream if meta['ruby-serialization'] == "Marshal")
150
+ # @param [Object] payload the data to serialize
151
+ def serialize(payload)
152
+ return payload if IO === payload
153
+ case @content_type
154
+ when /json/
155
+ ActiveSupport::JSON.encode(payload)
156
+ when /yaml/
157
+ YAML.dump(payload)
158
+ when "application/octet-stream"
159
+ if @meta['ruby-serialization'] == "Marshal"
160
+ Marshal.dump(payload)
161
+ else
162
+ payload.to_s
163
+ end
164
+ else
165
+ payload.to_s
166
+ end
167
+ end
168
+
169
+ # Deserializes the internal object data from a Riak response. Differs based on the content-type.
170
+ # This method is called internally when loading the object.
171
+ # Automatically deserialized formats:
172
+ # * JSON (application/json)
173
+ # * YAML (text/yaml)
174
+ # * Marshal (application/octet-stream if meta['ruby-serialization'] == "Marshal")
175
+ # @param [String] body the serialized response body
176
+ def deserialize(body)
177
+ case @content_type
178
+ when /json/
179
+ ActiveSupport::JSON.decode(body)
180
+ when /yaml/
181
+ YAML.load(body)
182
+ when "application/octet-stream"
183
+ if @meta['ruby-serialization'] == "Marshal"
184
+ Marshal.load(body)
185
+ else
186
+ body
187
+ end
188
+ else
189
+ body
190
+ end
191
+ end
192
+
193
+ # @return [String] A representation suitable for IRB and debugging output
194
+ def inspect
195
+ "#<#{self.class.name} #{@bucket.client.http.path(@bucket.client.prefix, @bucket.name, @key).to_s} [#{@content_type}]:#{@data.inspect}>"
196
+ end
197
+
198
+ # Walks links from this object to other objects in Riak.
199
+ def walk(*params)
200
+ specs = WalkSpec.normalize(*params)
201
+ response = @bucket.client.http.get(200, @bucket.client.prefix, @bucket.name, @key, specs.join("/"))
202
+ if boundary = Multipart.extract_boundary(response[:headers]['content-type'].first)
203
+ Multipart.parse(response[:body], boundary).map do |group|
204
+ map_walk_group(group)
205
+ end
206
+ else
207
+ []
208
+ end
209
+ end
210
+
211
+ # Converts the object to a link suitable for linking other objects to it
212
+ def to_link(tag=nil)
213
+ Link.new(@bucket.client.http.path(@bucket.client.prefix, @bucket.name, @key).path, tag)
214
+ end
215
+
216
+ private
217
+ def extract_header(response, name, attribute=nil)
218
+ if response[:headers][name].present?
219
+ value = response[:headers][name].try(:first)
220
+ value = yield value if block_given?
221
+ send("#{attribute}=", value) if attribute
222
+ end
223
+ end
224
+
225
+ def map_walk_group(group)
226
+ group.map do |obj|
227
+ if obj[:headers] && obj[:body] && obj[:headers]['location']
228
+ bucket, key = $1, $2 if obj[:headers]['location'].first =~ %r{/.*/(.*)/(.*)$}
229
+ RObject.new(@bucket.client.bucket(bucket, :keys => false), key).load(obj)
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end