better-riak-client 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/LICENSE +16 -0
  2. data/README.markdown +198 -0
  3. data/RELEASE_NOTES.md +211 -0
  4. data/better-riak-client.gemspec +61 -0
  5. data/erl_src/riak_kv_test014_backend.beam +0 -0
  6. data/erl_src/riak_kv_test014_backend.erl +189 -0
  7. data/erl_src/riak_kv_test_backend.beam +0 -0
  8. data/erl_src/riak_kv_test_backend.erl +697 -0
  9. data/erl_src/riak_search_test_backend.beam +0 -0
  10. data/erl_src/riak_search_test_backend.erl +175 -0
  11. data/lib/riak/bucket.rb +221 -0
  12. data/lib/riak/client/beefcake/messages.rb +213 -0
  13. data/lib/riak/client/beefcake/object_methods.rb +111 -0
  14. data/lib/riak/client/beefcake_protobuffs_backend.rb +226 -0
  15. data/lib/riak/client/decaying.rb +36 -0
  16. data/lib/riak/client/excon_backend.rb +162 -0
  17. data/lib/riak/client/feature_detection.rb +88 -0
  18. data/lib/riak/client/http_backend/configuration.rb +211 -0
  19. data/lib/riak/client/http_backend/key_streamer.rb +43 -0
  20. data/lib/riak/client/http_backend/object_methods.rb +106 -0
  21. data/lib/riak/client/http_backend/request_headers.rb +34 -0
  22. data/lib/riak/client/http_backend/transport_methods.rb +201 -0
  23. data/lib/riak/client/http_backend.rb +340 -0
  24. data/lib/riak/client/net_http_backend.rb +82 -0
  25. data/lib/riak/client/node.rb +115 -0
  26. data/lib/riak/client/protobuffs_backend.rb +173 -0
  27. data/lib/riak/client/search.rb +91 -0
  28. data/lib/riak/client.rb +540 -0
  29. data/lib/riak/cluster.rb +151 -0
  30. data/lib/riak/core_ext/blank.rb +53 -0
  31. data/lib/riak/core_ext/deep_dup.rb +13 -0
  32. data/lib/riak/core_ext/extract_options.rb +7 -0
  33. data/lib/riak/core_ext/json.rb +15 -0
  34. data/lib/riak/core_ext/slice.rb +18 -0
  35. data/lib/riak/core_ext/stringify_keys.rb +10 -0
  36. data/lib/riak/core_ext/symbolize_keys.rb +10 -0
  37. data/lib/riak/core_ext/to_param.rb +31 -0
  38. data/lib/riak/core_ext.rb +7 -0
  39. data/lib/riak/encoding.rb +6 -0
  40. data/lib/riak/failed_request.rb +81 -0
  41. data/lib/riak/i18n.rb +5 -0
  42. data/lib/riak/json.rb +52 -0
  43. data/lib/riak/link.rb +94 -0
  44. data/lib/riak/locale/en.yml +53 -0
  45. data/lib/riak/locale/fr.yml +52 -0
  46. data/lib/riak/map_reduce/filter_builder.rb +103 -0
  47. data/lib/riak/map_reduce/phase.rb +98 -0
  48. data/lib/riak/map_reduce.rb +225 -0
  49. data/lib/riak/map_reduce_error.rb +7 -0
  50. data/lib/riak/node/configuration.rb +293 -0
  51. data/lib/riak/node/console.rb +133 -0
  52. data/lib/riak/node/control.rb +207 -0
  53. data/lib/riak/node/defaults.rb +83 -0
  54. data/lib/riak/node/generation.rb +106 -0
  55. data/lib/riak/node/log.rb +34 -0
  56. data/lib/riak/node/version.rb +43 -0
  57. data/lib/riak/node.rb +38 -0
  58. data/lib/riak/robject.rb +318 -0
  59. data/lib/riak/search.rb +3 -0
  60. data/lib/riak/serializers.rb +74 -0
  61. data/lib/riak/stamp.rb +77 -0
  62. data/lib/riak/test_server.rb +89 -0
  63. data/lib/riak/util/escape.rb +76 -0
  64. data/lib/riak/util/headers.rb +53 -0
  65. data/lib/riak/util/multipart/stream_parser.rb +62 -0
  66. data/lib/riak/util/multipart.rb +52 -0
  67. data/lib/riak/util/tcp_socket_extensions.rb +58 -0
  68. data/lib/riak/util/translation.rb +19 -0
  69. data/lib/riak/version.rb +3 -0
  70. data/lib/riak/walk_spec.rb +105 -0
  71. data/lib/riak.rb +21 -0
  72. metadata +348 -0
@@ -0,0 +1,83 @@
1
+ require 'riak/core_ext/deep_dup'
2
+
3
+ module Riak
4
+ class Node
5
+ # Settings based on Riak 1.1.
6
+ ENV_DEFAULTS = {
7
+ :riak_core => {
8
+ :ring_creation_size => 64
9
+ },
10
+ :riak_kv => {
11
+ :storage_backend => :riak_kv_bitcask_backend,
12
+ :map_js_vm_count => 8,
13
+ :reduce_js_vm_count => 6,
14
+ :hook_js_vm_count => 2,
15
+ :mapper_batch_size => 5,
16
+ :js_max_vm_mem => 8,
17
+ :js_thread_stack => 16,
18
+ :riak_kv_stat => true,
19
+ :legacy_stats => true,
20
+ :vnode_vclocks => true,
21
+ :http_url_encoding => :on,
22
+ :legacy_keylisting => false,
23
+ :mapred_system => :pipe,
24
+ :mapred_2i_pipe => true,
25
+ :listkeys_backpressure => true,
26
+ :add_paths => []
27
+ },
28
+ :riak_search => {
29
+ :enabled => true
30
+ },
31
+ :luwak => {
32
+ :enabled => true
33
+ },
34
+ :merge_index => {
35
+ :buffer_rollover_size => 1048576,
36
+ :max_compact_segments => 20
37
+ },
38
+ :eleveldb => {},
39
+ :bitcask => {},
40
+ :lager => {
41
+ :crash_log_size => 10485760,
42
+ :crash_log_msg_size => 65536,
43
+ :crash_log_date => "$D0",
44
+ :crash_log_count => 5,
45
+ :error_logger_redirect => true
46
+ },
47
+ :riak_sysmon => {
48
+ :process_limit => 30,
49
+ :port_limit => 30,
50
+ :gc_ms_limit => 100,
51
+ :heap_word_limit => 40111000,
52
+ :busy_port => true,
53
+ :busy_dist_port => true
54
+ },
55
+ :sasl => {
56
+ :sasl_error_logger => false
57
+ },
58
+ :riak_control => {
59
+ :enabled => false,
60
+ :auth => :userlist,
61
+ :userlist => {"user" => "pass"},
62
+ :admin => true
63
+ }
64
+ }.freeze
65
+
66
+ # Based on Riak 1.1.
67
+ VM_DEFAULTS = {
68
+ "+K" => true,
69
+ "+A" => 64,
70
+ "-smp" => "enable",
71
+ "+W" => "w",
72
+ "-env ERL_MAX_PORTS" => 4096,
73
+ "-env ERL_FULLSWEEP_AFTER" => 0
74
+ }.freeze
75
+
76
+ protected
77
+ # Populates the defaults
78
+ def set_defaults
79
+ @env = ENV_DEFAULTS.deep_dup
80
+ @vm = VM_DEFAULTS.deep_dup
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,106 @@
1
+ require 'yaml'
2
+
3
+ module Riak
4
+ class Node
5
+ # Does the node exist on disk?
6
+ def exist?
7
+ manifest.exist?
8
+ end
9
+
10
+ # Deletes the node and regenerates it.
11
+ def recreate
12
+ delete
13
+ create
14
+ end
15
+
16
+ # Generates the node.
17
+ def create
18
+ unless exist?
19
+ create_directories
20
+ write_scripts
21
+ write_vm_args
22
+ write_app_config
23
+ write_manifest
24
+ end
25
+ end
26
+
27
+ # Clears data from known data directories. Stops the node if it is
28
+ # running.
29
+ def drop
30
+ was_started = started?
31
+ stop if was_started
32
+ data.children.each do |item|
33
+ if item.directory?
34
+ item.children.each {|c| c.rmtree }
35
+ else
36
+ item.delete
37
+ end
38
+ end
39
+ start if was_started
40
+ end
41
+
42
+ # Removes the node from disk and freezes the object.
43
+ def destroy
44
+ delete
45
+ freeze
46
+ end
47
+
48
+ protected
49
+ def delete
50
+ stop unless stopped?
51
+ root.rmtree if root.exist?
52
+ end
53
+
54
+ def create_directories
55
+ root.mkpath
56
+ NODE_DIRECTORIES.each {|d| send(d).mkpath }
57
+ end
58
+
59
+ def write_vm_args
60
+ (etc + 'vm.args').open('w') do |f|
61
+ vm.each do |k,v|
62
+ f.puts "#{k} #{v}"
63
+ end
64
+ end
65
+ end
66
+
67
+ def write_app_config
68
+ (etc + 'app.config').open('w') do |f|
69
+ f.write to_erlang_config(env) + '.'
70
+ end
71
+ end
72
+
73
+ def write_scripts
74
+ [control_script, admin_script].each {|s| write_script(s.basename, s) }
75
+ end
76
+
77
+ def write_script(name, target)
78
+ source_script = source + name
79
+ target.open('wb') do |f|
80
+ source_script.readlines.each do |line|
81
+ line.sub!(/(RUNNER_SCRIPT_DIR=)(.*)/, '\1' + bin.to_s)
82
+ line.sub!(/(RUNNER_ETC_DIR=)(.*)/, '\1' + etc.to_s)
83
+ line.sub!(/(RUNNER_USER=)(.*)/, '\1')
84
+ line.sub!(/(RUNNER_LOG_DIR=)(.*)/, '\1' + log.to_s)
85
+ line.sub!(/(PIPE_DIR=)(.*)/, '\1' + pipe.to_s + "/") # PIPE_DIR must have a trailing slash
86
+ line.sub!(/(PLATFORM_DATA_DIR=)(.*)/, '\1' + data.to_s)
87
+ line.sub!('grep "$RUNNER_BASE_DIR/.*/[b]eam"', 'grep "$RUNNER_ETC_DIR/app.config"')
88
+ if line.strip == "RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*}"
89
+ line = "RUNNER_BASE_DIR=#{source.parent.to_s}\n"
90
+ end
91
+ f.write line
92
+ end
93
+ end
94
+ target.chmod 0755
95
+ end
96
+
97
+ def write_manifest
98
+ # TODO: For now this only saves the information that was used when
99
+ # configuring the node. Later we'll verify/warn if the settings
100
+ # used differ on subsequent generations.
101
+ @configuration[:env] = @env
102
+ @configuration[:vm] = @vm
103
+ manifest.open('w') {|f| YAML.dump(@configuration, f) }
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,34 @@
1
+ module Riak
2
+ class Node
3
+ LAGER_LEVELS = [
4
+ :debug,
5
+ :info,
6
+ :notice,
7
+ :warning,
8
+ :error,
9
+ :critical,
10
+ :alert,
11
+ :emergency
12
+ ]
13
+
14
+ def read_console_log(*levels)
15
+ console_log = log + 'console.log'
16
+ if console_log.exist?
17
+ levels = levels.map { |level| expand_log_level(level) }.compact.flatten
18
+ pattern = /(#{levels.map { |level| "\\[#{level}\\]" }.join("|")})/
19
+ console_log.readlines.grep(pattern)
20
+ end
21
+ end
22
+
23
+ def expand_log_level(level)
24
+ case level
25
+ when Range
26
+ first = LAGER_LEVELS.index(level.begin.to_sym) || 0
27
+ last = LAGER_LEVELS.index(level.end.to_sym) || -1
28
+ LAGER_LEVELS[first..last]
29
+ when Symbol
30
+ level
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+ require 'pathname'
2
+
3
+ module Riak
4
+ class Node
5
+ # @return [String] the version of the Riak node
6
+ def version
7
+ @version ||= configure_version
8
+ end
9
+
10
+ # @return [Pathname] the location of Riak installation, aka RUNNER_BASE_DIR
11
+ def base_dir
12
+ @base_dir ||= configure_base_dir
13
+ end
14
+
15
+ protected
16
+ # Detects the Riak version from the generated release
17
+ def configure_version
18
+ if base_dir
19
+ versions = (base_dir + 'releases' + 'start_erl.data').read
20
+ versions.split(" ")[1]
21
+ end
22
+ end
23
+
24
+ # Determines the base_dir from source control script
25
+ def configure_base_dir
26
+ # Use the script from the source directory so we don't require
27
+ # it to be generated first.
28
+ (source + control_script_name).each_line.find {|l| l =~ /^RUNNER_BASE_DIR=(.*)/ }
29
+
30
+ # There should only be one matching line, so the contents of $1
31
+ # will be the matched path. If there's nothing matched, we
32
+ # return nil.
33
+ case $1
34
+ when '${RUNNER_SCRIPT_DIR%/*}'
35
+ source.parent
36
+ when String
37
+ Pathname.new($1).expand_path
38
+ else
39
+ nil
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/riak/node.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'riak/util/translation'
2
+ require 'riak/node/defaults'
3
+ require 'riak/node/configuration'
4
+ require 'riak/node/generation'
5
+ require 'riak/node/control'
6
+ require 'riak/node/version'
7
+ require 'riak/node/log'
8
+
9
+ module Riak
10
+ # A Node encapsulates the generation and management of standalone
11
+ # Riak nodes. It is used by the {TestServer} to start and manage an
12
+ # in-memory node for supporting integration test suites.
13
+ class Node
14
+ include Util::Translation
15
+
16
+ # Creates a new Riak node. Unlike {#new}, this will also generate
17
+ # the node if it does not exist on disk. Equivalent to {::new}
18
+ # followed by {#create}.
19
+ # @see #new
20
+ def self.create(configuration={})
21
+ new(configuration).tap do |node|
22
+ node.create
23
+ end
24
+ end
25
+
26
+ # Creates the template for a Riak node. To generate the node after
27
+ # initialization, use {#create}.
28
+ def initialize(configuration={})
29
+ set_defaults
30
+ configure configuration
31
+ end
32
+
33
+ protected
34
+ def debug(msg)
35
+ $stderr.puts msg if ENV["DEBUG_RIAK_NODE"]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,318 @@
1
+ require 'set'
2
+ require 'time'
3
+ require 'yaml'
4
+ require 'riak/util/translation'
5
+ require 'riak/util/escape'
6
+ require 'riak/bucket'
7
+ require 'riak/link'
8
+ require 'riak/walk_spec'
9
+ require 'riak/serializers'
10
+
11
+ module Riak
12
+ # Represents the data and metadata stored in a bucket/key pair in
13
+ # the Riak database, the base unit of data manipulation.
14
+ class RObject
15
+ include Util::Translation
16
+ extend Util::Translation
17
+ include Util::Escape
18
+ extend Util::Escape
19
+
20
+ # @return [Bucket] the bucket in which this object is contained
21
+ attr_accessor :bucket
22
+
23
+ # @return [String] the key of this object within its bucket
24
+ attr_accessor :key
25
+
26
+ # @return [String] the MIME content type of the object
27
+ attr_accessor :content_type
28
+
29
+ # @return [String] the Riak vector clock for the object
30
+ attr_accessor :vclock
31
+
32
+ # @return [Set<Link>] a Set of {Riak::Link} objects for relationships between this object and other resources
33
+ attr_accessor :links
34
+
35
+ # @return [String] the ETag header from the most recent HTTP response, useful for caching and reloading
36
+ attr_accessor :etag
37
+
38
+ # @return [Time] the Last-Modified header from the most recent HTTP response, useful for caching and reloading
39
+ attr_accessor :last_modified
40
+
41
+ # @return [Hash] a hash of any X-Riak-Meta-* headers that were in the HTTP response, keyed on the trailing portion
42
+ attr_accessor :meta
43
+
44
+ # @return [Hash<Set>] a hash of secondary indexes, where the
45
+ # key is the index name and the value is a Set of index
46
+ # entries for that index
47
+ attr_accessor :indexes
48
+
49
+ # @return [Boolean] whether to attempt to prevent stale writes using conditional PUT semantics, If-None-Match: * or If-Match: {#etag}
50
+ # @see http://wiki.basho.com/display/RIAK/REST+API#RESTAPI-Storeaneworexistingobjectwithakey Riak Rest API Docs
51
+ attr_accessor :prevent_stale_writes
52
+
53
+ # Defines a callback to be invoked when there is conflict.
54
+ #
55
+ # @yield The conflict callback.
56
+ # @yieldparam [RObject] robject The conflicted RObject
57
+ # @yieldreturn [RObject, nil] Either the resolved RObject or nil if your
58
+ # callback cannot resolve it. The next registered
59
+ # callback will be given the chance to resolve it.
60
+ #
61
+ # @note Ripple registers its own document-level conflict handler, so if you're
62
+ # using ripple, you will probably want to use that instead.
63
+ def self.on_conflict(&conflict_hook)
64
+ on_conflict_hooks << conflict_hook
65
+ end
66
+
67
+ # @return [Array<Proc>] the list of registered conflict callbacks.
68
+ def self.on_conflict_hooks
69
+ @on_conflict_hooks ||= []
70
+ end
71
+
72
+ # Attempts to resolve conflict using the registered conflict callbacks.
73
+ #
74
+ # @return [RObject] the RObject
75
+ # @note There is no guarantee the returned RObject will have been resolved
76
+ def attempt_conflict_resolution
77
+ return self unless conflict?
78
+
79
+ self.class.on_conflict_hooks.each do |hook|
80
+ result = hook.call(self)
81
+ return result if result.is_a?(RObject)
82
+ end
83
+
84
+ self
85
+ end
86
+
87
+ # Loads a list of RObjects that were emitted from a MapReduce
88
+ # query.
89
+ # @param [Client] client A Riak::Client with which the results will be associated
90
+ # @param [Array<Hash>] response A list of results a MapReduce job. Each entry should contain these keys: bucket, key, vclock, values
91
+ # @return [Array<RObject>] An array of RObject instances
92
+ def self.load_from_mapreduce(client, response)
93
+ response.map do |item|
94
+ RObject.new(client[unescape(item['bucket'])], unescape(item['key'])).load_from_mapreduce(item)
95
+ end
96
+ end
97
+
98
+ # Create a new object manually
99
+ # @param [Bucket] bucket the bucket in which the object exists
100
+ # @param [String] key the key at which the object resides. If nil, a key will be assigned when the object is saved.
101
+ # @yield self the new RObject
102
+ # @see Bucket#get
103
+ def initialize(bucket, key=nil)
104
+ @bucket, @key = bucket, key
105
+ @links, @meta = Set.new, {}
106
+ @indexes = new_index_hash
107
+ yield self if block_given?
108
+ end
109
+
110
+ def indexes=(hash)
111
+ @indexes = hash.inject(new_index_hash) do |h, (k,v)|
112
+ h[k].merge([*v])
113
+ h
114
+ end
115
+ end
116
+
117
+ # Load object data from a map/reduce response item.
118
+ # This method is used by RObject::load_from_mapreduce to instantiate the necessary
119
+ # objects.
120
+ # @param [Hash] response a response from {Riak::MapReduce}
121
+ # @return [RObject] self
122
+ def load_from_mapreduce(response)
123
+ self.vclock = response['vclock']
124
+ if response['values'].size == 1
125
+ value = response['values'].first
126
+ load_map_reduce_value(value)
127
+ else
128
+ @conflict = true
129
+ @siblings = response['values'].map do |v|
130
+ RObject.new(self.bucket, self.key) do |robj|
131
+ robj.vclock = self.vclock
132
+ robj.load_map_reduce_value(v)
133
+ end
134
+ end
135
+ end
136
+ self
137
+ end
138
+
139
+ # @return [Object] the unmarshaled form of {#raw_data} stored in riak at this object's key
140
+ def data
141
+ if @raw_data && !@data
142
+ raw = @raw_data.respond_to?(:read) ? @raw_data.read : @raw_data
143
+ @data = deserialize(raw)
144
+ @raw_data = nil
145
+ end
146
+ @data
147
+ end
148
+
149
+ # @param [Object] unmarshaled form of the data to be stored in riak. Object will be serialized using {#serialize} if a known content_type is used. Setting this overrides values stored with {#raw_data=}
150
+ # @return [Object] the object stored
151
+ def data=(new_data)
152
+ if new_data.respond_to?(:read)
153
+ raise ArgumentError.new(t("invalid_io_object"))
154
+ end
155
+
156
+ @raw_data = nil
157
+ @data = new_data
158
+ end
159
+
160
+ # @return [String] raw data stored in riak for this object's key
161
+ def raw_data
162
+ if @data && !@raw_data
163
+ @raw_data = serialize(@data)
164
+ @data = nil
165
+ end
166
+ @raw_data
167
+ end
168
+
169
+ # @param [String, IO-like] the raw data to be stored in riak at this key, will not be marshaled or manipulated prior to storage. Overrides any data stored by {#data=}
170
+ # @return [String] the data stored
171
+ def raw_data=(new_raw_data)
172
+ @data = nil
173
+ @raw_data = new_raw_data
174
+ end
175
+
176
+ # Store the object in Riak
177
+ # @param [Hash] options query parameters
178
+ # @option options [Fixnum] :r the "r" parameter (Read quorum for the implicit read performed when validating the store operation)
179
+ # @option options [Fixnum] :w the "w" parameter (Write quorum)
180
+ # @option options [Fixnum] :dw the "dw" parameter (Durable-write quorum)
181
+ # @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.
182
+ # @return [Riak::RObject] self
183
+ # @raise [ArgumentError] if the content_type is not defined
184
+ def store(options={})
185
+ raise ArgumentError, t("content_type_undefined") unless @content_type.present?
186
+ @bucket.client.store_object(self, options)
187
+ self
188
+ end
189
+
190
+ # Reload the object from Riak. Will use conditional GETs when possible.
191
+ # @param [Hash] options query parameters
192
+ # @option options [Fixnum] :r the "r" parameter (Read quorum)
193
+ # @option options [Boolean] :force will force a reload request if
194
+ # the vclock is not present, useful for reloading the object after
195
+ # a store (not passed in the query params)
196
+ # @return [Riak::RObject] self
197
+ def reload(options={})
198
+ force = options.delete(:force)
199
+ return self unless @key && (@vclock || force)
200
+ self.etag = self.last_modified = nil if force
201
+ bucket.client.reload_object(self, options)
202
+ end
203
+
204
+ alias :fetch :reload
205
+
206
+ # Delete the object from Riak and freeze this instance. Will work whether or not the object actually
207
+ # exists in the Riak database.
208
+ # @see Bucket#delete
209
+ def delete(options={})
210
+ return if key.blank?
211
+ options[:vclock] = vclock if vclock
212
+ @bucket.delete(key, options)
213
+ freeze
214
+ end
215
+
216
+ attr_writer :siblings, :conflict
217
+
218
+ # Returns sibling objects when in conflict.
219
+ # @return [Array<RObject>] an array of conflicting sibling objects for this key
220
+ # @return [Array<self>] a single-element array containing object when not
221
+ # in conflict
222
+ def siblings
223
+ return [self] unless conflict?
224
+ @siblings
225
+ end
226
+
227
+ # @return [true,false] Whether this object has conflicting sibling objects (divergent vclocks)
228
+ def conflict?
229
+ @conflict.present?
230
+ end
231
+
232
+ # Serializes the internal object data for sending to Riak. Differs based on the content-type.
233
+ # This method is called internally when storing the object.
234
+ # Automatically serialized formats:
235
+ # * JSON (application/json)
236
+ # * YAML (text/yaml)
237
+ # * Marshal (application/x-ruby-marshal)
238
+ # When given an IO-like object (e.g. File), no serialization will
239
+ # be done.
240
+ # @param [Object] payload the data to serialize
241
+ def serialize(payload)
242
+ Serializers.serialize(@content_type, payload)
243
+ end
244
+
245
+ # Deserializes the internal object data from a Riak response. Differs based on the content-type.
246
+ # This method is called internally when loading the object.
247
+ # Automatically deserialized formats:
248
+ # * JSON (application/json)
249
+ # * YAML (text/yaml)
250
+ # * Marshal (application/x-ruby-marshal)
251
+ # @param [String] body the serialized response body
252
+ def deserialize(body)
253
+ Serializers.deserialize(@content_type, body)
254
+ end
255
+
256
+ # @return [String] A representation suitable for IRB and debugging output
257
+ def inspect
258
+ body = if @data || Serializers[content_type]
259
+ data.inspect
260
+ else
261
+ @raw_data && "(#{@raw_data.size} bytes)"
262
+ end
263
+ "#<#{self.class.name} {#{bucket.name}#{"," + @key if @key}} [#{@content_type}]:#{body}>"
264
+ end
265
+
266
+ # Walks links from this object to other objects in Riak.
267
+ # @param [Array<Hash,WalkSpec>] link specifications for the query
268
+ def walk(*params)
269
+ specs = WalkSpec.normalize(*params)
270
+ @bucket.client.link_walk(self, specs)
271
+ end
272
+
273
+ # Converts the object to a link suitable for linking other objects
274
+ # to it
275
+ # @param [String] tag the tag to apply to the link
276
+ def to_link(tag)
277
+ Link.new(@bucket.name, @key, tag)
278
+ end
279
+
280
+ alias :vector_clock :vclock
281
+ alias :vector_clock= :vclock=
282
+
283
+ protected
284
+ def load_map_reduce_value(hash)
285
+ metadata = hash['metadata']
286
+ extract_if_present(metadata, 'X-Riak-VTag', :etag)
287
+ extract_if_present(metadata, 'content-type', :content_type)
288
+ extract_if_present(metadata, 'X-Riak-Last-Modified', :last_modified) { |v| Time.httpdate( v ) }
289
+ extract_if_present(metadata, 'index', :indexes) do |entries|
290
+ Hash[ entries.map {|k,v| [k, Set.new(Array(v))] } ]
291
+ end
292
+ extract_if_present(metadata, 'Links', :links) do |links|
293
+ Set.new( links.map { |l| Link.new(*l) } )
294
+ end
295
+ extract_if_present(metadata, 'X-Riak-Meta', :meta) do |meta|
296
+ Hash[
297
+ meta.map do |k,v|
298
+ [k.sub(%r{^x-riak-meta-}i, ''), [v]]
299
+ end
300
+ ]
301
+ end
302
+ extract_if_present(hash, 'data', :data) { |v| deserialize(v) }
303
+ end
304
+
305
+ private
306
+ def extract_if_present(hash, key, attribute=nil)
307
+ if hash[key].present?
308
+ attribute ||= key
309
+ value = block_given? ? yield(hash[key]) : hash[key]
310
+ send("#{attribute}=", value)
311
+ end
312
+ end
313
+
314
+ def new_index_hash
315
+ Hash.new {|h,k| h[k] = Set.new }
316
+ end
317
+ end
318
+ end