ripple 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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