ripple 0.6.0 → 0.6.1

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.
@@ -1,5 +1,18 @@
1
1
  h1. Ripple Release Notes
2
2
 
3
+ h2. 0.6.1 Patch Release - 2010-03-17
4
+
5
+ This is a minor release with fixes for a few issues:
6
+
7
+ * Riak::Link objects will now be unique when added to RObject#links
8
+ Set. [John Lynch]
9
+ * Attributes on Ripple::Document classes are no longer clone, which had
10
+ prevented non-scalar properties from being modified directly (e.g. Array).
11
+ * Buckets, keys, and walk specs are properly escaped in URLs
12
+ (including slashes).
13
+ * Time-related properties properly convert to string formats in JSON that
14
+ they will work in the context of MapReduce jobs.
15
+
3
16
  h2. 0.6.0 Feature Release - 2010-03-05
4
17
 
5
18
  This release contains enhancements and bugfixes in preparation for the
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.6.1
data/lib/riak.rb CHANGED
@@ -38,6 +38,7 @@ module Riak
38
38
  autoload :MapReduceError, "riak/map_reduce_error"
39
39
 
40
40
  module Util
41
+ autoload :Escape, "riak/util/escape"
41
42
  autoload :Headers, "riak/util/headers"
42
43
  autoload :Multipart, "riak/util/multipart"
43
44
  autoload :Translation, "riak/util/translation"
data/lib/riak/bucket.rb CHANGED
@@ -18,6 +18,8 @@ module Riak
18
18
  # using {Client#bucket}, or create it manually and retrieve its meta-information later.
19
19
  class Bucket
20
20
  include Util::Translation
21
+ include Util::Escape
22
+
21
23
  # @return [Riak::Client] the associated client
22
24
  attr_reader :client
23
25
 
@@ -62,12 +64,12 @@ module Riak
62
64
  # @return [Array<String>] Keys in this bucket
63
65
  def keys(options={})
64
66
  if block_given?
65
- @client.http.get(200, @client.prefix, name, {:props => false}, {}) do |chunk|
67
+ @client.http.get(200, @client.prefix, escape(name), {:props => false}, {}) do |chunk|
66
68
  obj = ActiveSupport::JSON.decode(chunk) rescue {}
67
69
  yield obj['keys'].map {|k| URI.unescape(k) } if obj['keys']
68
70
  end
69
71
  elsif @keys.nil? || options[:reload]
70
- response = @client.http.get(200, @client.prefix, name, {:props => false}, {})
72
+ response = @client.http.get(200, @client.prefix, escape(name), {:props => false}, {})
71
73
  load(response)
72
74
  end
73
75
  @keys
@@ -81,7 +83,7 @@ module Riak
81
83
  def props=(properties)
82
84
  raise ArgumentError, t("hash_type", :hash => properties.inspect) unless Hash === properties
83
85
  body = {'props' => properties}.to_json
84
- @client.http.put(204, @client.prefix, name, body, {"Content-Type" => "application/json"})
86
+ @client.http.put(204, @client.prefix, escape(name), body, {"Content-Type" => "application/json"})
85
87
  @props = properties
86
88
  end
87
89
 
@@ -93,7 +95,7 @@ module Riak
93
95
  # @raise [FailedRequest] if the object is not found or some other error occurs
94
96
  def get(key, options={})
95
97
  code = allow_mult ? [200,300] : 200
96
- response = @client.http.get(code, @client.prefix, name, key, options, {})
98
+ response = @client.http.get(code, @client.prefix, escape(name), escape(key), options, {})
97
99
  RObject.new(self, key).load(response)
98
100
  end
99
101
  alias :[] :get
@@ -126,7 +128,7 @@ module Riak
126
128
  def allow_mult
127
129
  props['allow_mult']
128
130
  end
129
-
131
+
130
132
  # Set the allow_mult property. *NOTE* This will result in a PUT request to Riak.
131
133
  # @param [true, false] value whether the bucket should allow siblings
132
134
  def allow_mult=(value)
@@ -146,10 +148,10 @@ module Riak
146
148
  self.props = props.merge('n_val' => value)
147
149
  value
148
150
  end
149
-
151
+
150
152
  # @return [String] a representation suitable for IRB and debugging output
151
153
  def inspect
152
- "#<Riak::Bucket #{client.http.path(client.prefix, name).to_s}#{" keys=[#{keys.join(',')}]" if defined?(@keys)}>"
154
+ "#<Riak::Bucket #{client.http.path(client.prefix, escape(name)).to_s}#{" keys=[#{keys.join(',')}]" if defined?(@keys)}>"
153
155
  end
154
156
  end
155
157
  end
data/lib/riak/client.rb CHANGED
@@ -17,6 +17,7 @@ module Riak
17
17
  # A client connection to Riak.
18
18
  class Client
19
19
  include Util::Translation
20
+ include Util::Escape
20
21
 
21
22
  autoload :HTTPBackend, "riak/client/http_backend"
22
23
  autoload :NetHTTPBackend, "riak/client/net_http_backend"
@@ -116,7 +117,7 @@ module Riak
116
117
  # @return [Bucket] the requested bucket
117
118
  def bucket(name, options={})
118
119
  options.assert_valid_keys(:keys, :props)
119
- response = http.get(200, prefix, name, options, {})
120
+ response = http.get(200, prefix, escape(name), options, {})
120
121
  Bucket.new(self, name).load(response)
121
122
  end
122
123
  alias :[] :bucket
@@ -143,7 +143,7 @@ module Riak
143
143
  # @return [URI] an absolute URI for the resource
144
144
  def path(*segments)
145
145
  query = segments.extract_options!.to_param
146
- root_uri.merge(URI.escape(segments.join("/").gsub(/\/+/, "/").sub(/^\//, ''))).tap do |uri|
146
+ root_uri.merge(segments.join("/").gsub(/\/+/, "/").sub(/^\//, '')).tap do |uri|
147
147
  uri.query = query if query.present?
148
148
  end
149
149
  end
data/lib/riak/link.rb CHANGED
@@ -22,6 +22,8 @@ module Riak
22
22
 
23
23
  # @return [String] the relationship ("rel") of the other resource to this one
24
24
  attr_accessor :rel
25
+ alias :tag :rel
26
+ alias :tag= :rel=
25
27
 
26
28
  # @param [String] header_string the string value of the Link: HTTP header from a Riak response
27
29
  # @return [Array<Link>] an array of Riak::Link structs parsed from the header
@@ -37,12 +39,12 @@ module Riak
37
39
 
38
40
  # @return [String] bucket_name, if the Link url is a known Riak link ("/riak/<bucket>/<key>")
39
41
  def bucket
40
- $1 if url =~ %r{/riak/([^/]+)/?}
42
+ URI.unescape($1) if url =~ %r{^/[^/]+/([^/]+)/?}
41
43
  end
42
44
 
43
45
  # @return [String] key, if the Link url is a known Riak link ("/riak/<bucket>/<key>")
44
46
  def key
45
- $1 if url =~ %r{/riak/[^/]+/([^/]+)/?}
47
+ URI.unescape($1) if url =~ %r{^/[^/]+/[^/]+/([^/]+)/?}
46
48
  end
47
49
 
48
50
  def inspect; to_s; end
@@ -51,6 +53,14 @@ module Riak
51
53
  %Q[<#{@url}>; riaktag="#{@rel}"]
52
54
  end
53
55
 
56
+ def hash
57
+ self.to_s.hash
58
+ end
59
+
60
+ def eql?(other)
61
+ self == other
62
+ end
63
+
54
64
  def ==(other)
55
65
  other.is_a?(Link) && url == other.url && rel == other.rel
56
66
  end
data/lib/riak/robject.rb CHANGED
@@ -20,6 +20,7 @@ module Riak
20
20
  class RObject
21
21
  include Util
22
22
  include Util::Translation
23
+ include Util::Escape
23
24
 
24
25
  # @return [Bucket] the bucket in which this object is contained
25
26
  attr_accessor :bucket
@@ -117,7 +118,7 @@ module Riak
117
118
  def store(options={})
118
119
  raise ArgumentError, t("content_type_undefined") unless @content_type.present?
119
120
  params = {:returnbody => true}.merge(options)
120
- method, codes, path = @key.present? ? [:put, [200,204], "#{@bucket.name}/#{@key}"] : [:post, 201, @bucket.name]
121
+ method, codes, path = @key.present? ? [:put, [200,204,300], "#{escape(@bucket.name)}/#{escape(@key)}"] : [:post, 201, escape(@bucket.name)]
121
122
  response = @bucket.client.http.send(method, codes, @bucket.client.prefix, path, params, serialize(data), store_headers)
122
123
  load(response)
123
124
  end
@@ -131,7 +132,7 @@ module Riak
131
132
  force = options.delete(:force)
132
133
  return self unless @key && (@vclock || force)
133
134
  codes = @bucket.allow_mult ? [200,300,304] : [200,304]
134
- response = @bucket.client.http.get(codes, @bucket.client.prefix, @bucket.name, @key, options, reload_headers)
135
+ response = @bucket.client.http.get(codes, @bucket.client.prefix, escape(@bucket.name), escape(@key), options, reload_headers)
135
136
  load(response) unless response[:code] == 304
136
137
  self
137
138
  end
@@ -142,7 +143,7 @@ module Riak
142
143
  # exists in the Riak database.
143
144
  def delete
144
145
  return if key.blank?
145
- @bucket.client.http.delete([204,404], @bucket.client.prefix, @bucket.name, key)
146
+ @bucket.client.http.delete([204,404], @bucket.client.prefix, escape(@bucket.name), escape(@key))
146
147
  freeze
147
148
  end
148
149
 
@@ -215,13 +216,13 @@ module Riak
215
216
 
216
217
  # @return [String] A representation suitable for IRB and debugging output
217
218
  def inspect
218
- "#<#{self.class.name} #{@bucket.client.http.path(@bucket.client.prefix, @bucket.name, @key).to_s} [#{@content_type}]:#{@data.inspect}>"
219
+ "#<#{self.class.name} #{@bucket.client.http.path(@bucket.client.prefix, escape(@bucket.name), escape(@key)).to_s} [#{@content_type}]:#{@data.inspect}>"
219
220
  end
220
221
 
221
222
  # Walks links from this object to other objects in Riak.
222
223
  def walk(*params)
223
224
  specs = WalkSpec.normalize(*params)
224
- response = @bucket.client.http.get(200, @bucket.client.prefix, @bucket.name, @key, specs.join("/"))
225
+ response = @bucket.client.http.get(200, @bucket.client.prefix, escape(@bucket.name), escape(@key), specs.join("/"))
225
226
  if boundary = Multipart.extract_boundary(response[:headers]['content-type'].first)
226
227
  Multipart.parse(response[:body], boundary).map do |group|
227
228
  map_walk_group(group)
@@ -233,7 +234,7 @@ module Riak
233
234
 
234
235
  # Converts the object to a link suitable for linking other objects to it
235
236
  def to_link(tag=nil)
236
- Link.new(@bucket.client.http.path(@bucket.client.prefix, @bucket.name, @key).path, tag)
237
+ Link.new(@bucket.client.http.path(@bucket.client.prefix, escape(@bucket.name), escape(@key)).path, tag)
237
238
  end
238
239
 
239
240
  private
@@ -0,0 +1,12 @@
1
+ module Riak
2
+ module Util
3
+ module Escape
4
+ # URI-escapes bucket or key names that may contain slashes for use in URLs.
5
+ # @param [String] bucket_or_key the bucket or key name
6
+ # @return [String] the escaped path segment
7
+ def escape(bucket_or_key)
8
+ URI.escape(bucket_or_key).gsub("/", "%2F")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -24,6 +24,8 @@ module Riak
24
24
  # Riak::WalkSpec.new({:bucket => 'tracks', :result => true})
25
25
  class WalkSpec
26
26
  include Util::Translation
27
+ include Util::Escape
28
+
27
29
  # @return [String] The bucket followed links should be restricted to. "_" represents all buckets.
28
30
  attr_accessor :bucket
29
31
 
@@ -84,7 +86,9 @@ module Riak
84
86
 
85
87
  # Converts the walk-spec into the form required by the link-walker resource URL
86
88
  def to_s
87
- "#{@bucket || '_'},#{@tag || '_'},#{@keep ? '1' : '_'}"
89
+ b = @bucket && escape(@bucket) || '_'
90
+ t = @tag && escape(@tag) || '_'
91
+ "#{b},#{t},#{@keep ? '1' : '_'}"
88
92
  end
89
93
 
90
94
  def ==(other)
@@ -94,3 +94,36 @@ class FalseClass
94
94
  Boolean.ripple_cast(value)
95
95
  end
96
96
  end
97
+
98
+ # @private
99
+ class Time
100
+ def as_json(options={})
101
+ self.utc.rfc822
102
+ end
103
+
104
+ def self.ripple_cast(value)
105
+ value.respond_to?(:to_time) && value.to_time or raise Ripple::PropertyTypeMismatch.new(self, value)
106
+ end
107
+ end
108
+
109
+ # @private
110
+ class Date
111
+ def as_json(options={})
112
+ self.to_s(:rfc822)
113
+ end
114
+
115
+ def self.ripple_cast(value)
116
+ value.respond_to?(:to_date) && value.to_date or raise Ripple::PropertyTypeMismatch.new(self, value)
117
+ end
118
+ end
119
+
120
+ # @private
121
+ class DateTime
122
+ def as_json(options={})
123
+ self.utc.to_s(:rfc822)
124
+ end
125
+
126
+ def self.ripple_cast(value)
127
+ value.respond_to?(:to_datetime) && value.to_datetime or raise Ripple::PropertyTypeMismatch.new(self, value)
128
+ end
129
+ end
@@ -26,8 +26,7 @@ module Ripple
26
26
  private
27
27
  def attribute(attr_name)
28
28
  if @attributes.include?(attr_name)
29
- value = @attributes[attr_name]
30
- value.duplicable? ? value.clone : value
29
+ @attributes[attr_name]
31
30
  else
32
31
  nil
33
32
  end
data/ripple.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ripple}
8
- s.version = "0.6.0"
8
+ s.version = "0.6.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Sean Cribbs"]
12
- s.date = %q{2010-03-05}
12
+ s.date = %q{2010-03-17}
13
13
  s.description = %q{ripple is a rich Ruby client for Riak, Basho's distributed database. It includes all the basics of accessing and manipulating Riak buckets and objects, and an object mapper library for building a rich domain on top of Riak.}
14
14
  s.email = %q{seancribbs@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -39,6 +39,7 @@ Gem::Specification.new do |s|
39
39
  "lib/riak/map_reduce.rb",
40
40
  "lib/riak/map_reduce_error.rb",
41
41
  "lib/riak/robject.rb",
42
+ "lib/riak/util/escape.rb",
42
43
  "lib/riak/util/fiber1.8.rb",
43
44
  "lib/riak/util/headers.rb",
44
45
  "lib/riak/util/multipart.rb",
@@ -72,6 +73,7 @@ Gem::Specification.new do |s|
72
73
  "spec/riak/bucket_spec.rb",
73
74
  "spec/riak/client_spec.rb",
74
75
  "spec/riak/curb_backend_spec.rb",
76
+ "spec/riak/escape_spec.rb",
75
77
  "spec/riak/headers_spec.rb",
76
78
  "spec/riak/http_backend_spec.rb",
77
79
  "spec/riak/link_spec.rb",
@@ -83,6 +85,7 @@ Gem::Specification.new do |s|
83
85
  "spec/ripple/attribute_methods_spec.rb",
84
86
  "spec/ripple/bucket_access_spec.rb",
85
87
  "spec/ripple/callbacks_spec.rb",
88
+ "spec/ripple/core_ext_spec.rb",
86
89
  "spec/ripple/document_spec.rb",
87
90
  "spec/ripple/embedded_document_spec.rb",
88
91
  "spec/ripple/finders_spec.rb",
@@ -100,12 +103,13 @@ Gem::Specification.new do |s|
100
103
  s.rdoc_options = ["--charset=UTF-8"]
101
104
  s.require_paths = ["lib"]
102
105
  s.requirements = ["`gem install curb` for better HTTP performance"]
103
- s.rubygems_version = %q{1.3.5}
106
+ s.rubygems_version = %q{1.3.6}
104
107
  s.summary = %q{ripple is a rich Ruby client for Riak, Basho's distributed database.}
105
108
  s.test_files = [
106
109
  "spec/riak/bucket_spec.rb",
107
110
  "spec/riak/client_spec.rb",
108
111
  "spec/riak/curb_backend_spec.rb",
112
+ "spec/riak/escape_spec.rb",
109
113
  "spec/riak/headers_spec.rb",
110
114
  "spec/riak/http_backend_spec.rb",
111
115
  "spec/riak/link_spec.rb",
@@ -117,6 +121,7 @@ Gem::Specification.new do |s|
117
121
  "spec/ripple/attribute_methods_spec.rb",
118
122
  "spec/ripple/bucket_access_spec.rb",
119
123
  "spec/ripple/callbacks_spec.rb",
124
+ "spec/ripple/core_ext_spec.rb",
120
125
  "spec/ripple/document_spec.rb",
121
126
  "spec/ripple/embedded_document_spec.rb",
122
127
  "spec/ripple/finders_spec.rb",
@@ -104,6 +104,12 @@ describe Riak::Bucket do
104
104
  @http.should_receive(:get).with(200, "/riak/","foo", {:props => false}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar%20baz"]}'})
105
105
  @bucket.keys.should == ["bar baz"]
106
106
  end
107
+
108
+ it "should escape the bucket name" do
109
+ @bucket.instance_variable_set :@name, "unescaped "
110
+ @http.should_receive(:get).with(200, "/riak/","unescaped%20", {:props => false}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar"]}'})
111
+ @bucket.keys.should == ["bar"]
112
+ end
107
113
  end
108
114
 
109
115
  describe "setting the bucket properties" do
@@ -120,6 +126,12 @@ describe Riak::Bucket do
120
126
  it "should raise an error if an invalid property is given" do
121
127
  lambda { @bucket.props = "blah" }.should raise_error(ArgumentError)
122
128
  end
129
+
130
+ it "should escape the bucket name" do
131
+ @bucket.instance_variable_set :@name, "foo bar"
132
+ @http.should_receive(:put).with(204, "/riak/","foo%20bar", '{"props":{"n_val":2}}', {"Content-Type" => "application/json"}).and_return({:body => "", :headers => {}})
133
+ @bucket.props = {:n_val => 2}
134
+ end
123
135
  end
124
136
 
125
137
  describe "fetching an object" do
@@ -143,6 +155,12 @@ describe Riak::Bucket do
143
155
  @http.should_receive(:get).with([200,300], "/riak/","foo", "db", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
144
156
  @bucket.get('db')
145
157
  end
158
+
159
+ it "should escape the bucket and key names" do
160
+ @bucket.instance_variable_set(:@name, "foo ")
161
+ @http.should_receive(:get).with(200, "/riak/","foo%20", "%20bar", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
162
+ @bucket.get(' bar')
163
+ end
146
164
  end
147
165
 
148
166
  describe "creating a new blank object" do
@@ -165,5 +165,10 @@ describe Riak::Client do
165
165
  @http.should_receive(:get).with(200, "/riak/", "foo", {:keys => false}, {}).and_return(@payload)
166
166
  @client.bucket("foo", :keys => false)
167
167
  end
168
+
169
+ it "should escape bucket names with invalid characters" do
170
+ @http.should_receive(:get).with(200, "/riak/", "foo%2Fbar%20", {:keys => false}, {}).and_return(@payload)
171
+ @client.bucket("foo/bar ", :keys => false)
172
+ end
168
173
  end
169
174
  end
@@ -0,0 +1,17 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe Riak::Util::Escape do
4
+ before :each do
5
+ @object = Object.new
6
+ @object.extend(Riak::Util::Escape)
7
+ end
8
+
9
+ it "should escape standard non-safe characters" do
10
+ @object.escape("some string").should == "some%20string"
11
+ @object.escape("another^one").should == "another%5Eone"
12
+ end
13
+
14
+ it "should escape slashes" do
15
+ @object.escape("some/inner/path").should == "some%2Finner%2Fpath"
16
+ end
17
+ end
@@ -45,11 +45,6 @@ describe Riak::Client::HTTPBackend do
45
45
  @backend.path("/foo/bar").to_s.should == "http://127.0.0.1:8098/foo/bar"
46
46
  end
47
47
 
48
- it "should escape appropriate characters in a relative resource path" do
49
- @backend.path("foo bar").to_s.should == "http://127.0.0.1:8098/foo%20bar"
50
- @backend.path("foo", "bar", {"param" => "a string"}).to_s.should == "http://127.0.0.1:8098/foo/bar?param=a+string"
51
- end
52
-
53
48
  it "should compute a URI from a relative resource path with a hash of query parameters" do
54
49
  @backend.path("baz", :r => 2).to_s.should == "http://127.0.0.1:8098/baz?r=2"
55
50
  end
@@ -65,4 +65,18 @@ describe Riak::Link do
65
65
  [one].should include(two)
66
66
  [two].should include(one)
67
67
  end
68
+
69
+ it "should unescape the bucket name" do
70
+ Riak::Link.new("/riak/bucket%20spaces/key", "foo").bucket.should == "bucket spaces"
71
+ end
72
+
73
+ it "should unescape the key name" do
74
+ Riak::Link.new("/riak/bucket/key%2Fname", "foo").key.should == "key/name"
75
+ end
76
+
77
+ it "should not rely on the prefix to equal /riak/ when extracting the bucket and key" do
78
+ link = Riak::Link.new("/raw/bucket/key", "foo")
79
+ link.bucket.should == "bucket"
80
+ link.key.should == "key"
81
+ end
68
82
  end
@@ -30,7 +30,7 @@ describe Riak::RObject do
30
30
  @object.key.should == "bar"
31
31
  end
32
32
 
33
- it "should initialize the links to an empty array" do
33
+ it "should initialize the links to an empty set" do
34
34
  @object = Riak::RObject.new(@bucket, "bar")
35
35
  @object.links.should == Set.new
36
36
  end
@@ -202,6 +202,11 @@ describe Riak::RObject do
202
202
  @object.load({:headers => {"content-type" => ["multipart/mixed; boundary=foo"]}, :code => 300 })
203
203
  @object.should be_conflict
204
204
  end
205
+
206
+ it "should unescape the key given in the location header" do
207
+ @object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz%20"]}})
208
+ @object.key.should == "baz "
209
+ end
205
210
  end
206
211
 
207
212
  describe "extracting siblings" do
@@ -247,7 +252,7 @@ describe Riak::RObject do
247
252
 
248
253
  describe "when links are defined" do
249
254
  before :each do
250
- @object.links = [Riak::Link.new("/riak/foo/baz", "next")]
255
+ @object.links << Riak::Link.new("/riak/foo/baz", "next")
251
256
  end
252
257
 
253
258
  it "should include a Link header with references to other objects" do
@@ -260,10 +265,15 @@ describe Riak::RObject do
260
265
  @object.store_headers.should have_key("Link")
261
266
  @object.store_headers["Link"].should_not include('riaktag="up"')
262
267
  end
268
+
269
+ it "should not allow duplicate links" do
270
+ @object.links << Riak::Link.new("/riak/foo/baz", "next")
271
+ @object.links.length.should == 1
272
+ end
263
273
  end
264
274
 
265
275
  it "should exclude the Link header when no links are present" do
266
- @object.links = []
276
+ @object.links = Set.new
267
277
  @object.store_headers.should_not have_key("Link")
268
278
  end
269
279
 
@@ -339,6 +349,12 @@ describe Riak::RObject do
339
349
  @http.should_receive(:post).with(201, "/riak/", "foo", {:dw => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
340
350
  @object.store(:dw => 2)
341
351
  end
352
+
353
+ it "should escape the bucket name" do
354
+ @bucket.should_receive(:name).and_return("foo ")
355
+ @http.should_receive(:post).with(201, "/riak/", "foo%20", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
356
+ @object.store
357
+ end
342
358
  end
343
359
 
344
360
  describe "when the object has a key" do
@@ -347,16 +363,23 @@ describe Riak::RObject do
347
363
  end
348
364
 
349
365
  it "should issue a PUT request to the bucket, and update the object properties (returning the body by default)" do
350
- @http.should_receive(:put).with([200,204], "/riak/", "foo/bar", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
366
+ @http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
351
367
  @object.store
352
368
  @object.key.should == "somereallylongstring"
353
369
  @object.vclock.should == "areallylonghashvalue"
354
370
  end
355
371
 
356
372
  it "should include persistence-tuning parameters in the query string" do
357
- @http.should_receive(:put).with([200,204], "/riak/", "foo/bar", {:dw => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
373
+ @http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:dw => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
358
374
  @object.store(:dw => 2)
359
375
  end
376
+
377
+ it "should escape the bucket and key names" do
378
+ @http.should_receive(:put).with([200,204,300], "/riak/", "foo%20/bar%2Fbaz", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
379
+ @bucket.instance_variable_set(:@name, "foo ")
380
+ @object.key = "bar/baz"
381
+ @object.store
382
+ end
360
383
  end
361
384
  end
362
385
 
@@ -410,6 +433,13 @@ describe Riak::RObject do
410
433
  @http.should_receive(:get).with([200,300,304], "/riak/", "foo", "bar", {}, {}).and_return({:code => 304})
411
434
  @object.reload
412
435
  end
436
+
437
+ it "should escape the bucket and key names" do
438
+ @bucket.should_receive(:name).and_return("some/deep/path")
439
+ @object.key = "another/deep/path"
440
+ @http.should_receive(:get).with([200,304], "/riak/", "some%2Fdeep%2Fpath", "another%2Fdeep%2Fpath", {}, {}).and_return({:code => 304})
441
+ @object.reload
442
+ end
413
443
  end
414
444
 
415
445
  describe "walking from the object to linked objects" do
@@ -443,6 +473,13 @@ describe Riak::RObject do
443
473
  @client.should_receive(:bucket).with("foo", :keys => false).and_return(@bucket)
444
474
  @object.walk(nil,"next",true)
445
475
  end
476
+
477
+ it "should escape the bucket, key and link specs" do
478
+ @object.key = "bar/baz"
479
+ @bucket.should_receive(:name).and_return("quin/quux")
480
+ @http.should_receive(:get).with(200, "/riak/", "quin%2Fquux", "bar%2Fbaz", "_,next%2F2,1").and_return(:headers => {"content-type" => ["multipart/mixed; boundary=12345"]}, :body => "\n--12345\nContent-Type: multipart/mixed; boundary=09876\n\n--09876--\n\n--12345--\n")
481
+ @object.walk(:tag => "next/2", :keep => true)
482
+ end
446
483
  end
447
484
 
448
485
  describe "when deleting" do
@@ -468,6 +505,13 @@ describe Riak::RObject do
468
505
  @http.should_receive(:delete).and_raise(Riak::FailedRequest.new(:delete, [204,404], 500, {}, ""))
469
506
  lambda { @object.delete }.should raise_error(Riak::FailedRequest)
470
507
  end
508
+
509
+ it "should escape the bucket and key names" do
510
+ @object.key = "deep/path"
511
+ @bucket.should_receive(:name).and_return("bucket spaces")
512
+ @http.should_receive(:delete).with([204,404], "/riak/", "bucket%20spaces", "deep%2Fpath").and_return({:code => 204, :headers => {}})
513
+ @object.delete
514
+ end
471
515
  end
472
516
 
473
517
  it "should convert to a link having the same url and an empty tag" do
@@ -479,4 +523,10 @@ describe Riak::RObject do
479
523
  @object = Riak::RObject.new(@bucket, "bar")
480
524
  @object.to_link("next").should == Riak::Link.new("/riak/foo/bar", "next")
481
525
  end
526
+
527
+ it "should escape the bucket and key when converting to a link" do
528
+ @object = Riak::RObject.new(@bucket, "deep/path")
529
+ @bucket.should_receive(:name).and_return("bucket spaces")
530
+ @object.to_link("bar").url.should == "/riak/bucket%20spaces/deep%2Fpath"
531
+ end
482
532
  end
@@ -60,6 +60,11 @@ describe Ripple::Document::AttributeMethods do
60
60
  it "should return the property default if defined and not set" do
61
61
  @widget.name.should == "widget"
62
62
  end
63
+
64
+ it "should expose the property directly" do
65
+ @widget.name.gsub!("w","f")
66
+ @widget.name.should == "fidget"
67
+ end
63
68
  end
64
69
 
65
70
  describe "mutators" do
@@ -0,0 +1,23 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe Time do
4
+ it "serializes to JSON in UTC, RFC 822 format" do
5
+ Time.utc(2010,3,16,12).as_json.should == "Tue, 16 Mar 2010 12:00:00 -0000"
6
+ end
7
+ end
8
+
9
+ describe Date do
10
+ it "serializes to JSON RFC 822 format" do
11
+ Date.civil(2010,3,16).as_json.should == "16 Mar 2010"
12
+ end
13
+ end
14
+
15
+ describe DateTime do
16
+ before :each do
17
+ Time.zone = :utc
18
+ end
19
+
20
+ it "serializes to JSON in UTC, RFC 822 format" do
21
+ DateTime.civil(2010,3,16,12).as_json.should == "Tue, 16 Mar 2010 12:00:00 +0000"
22
+ end
23
+ end
@@ -195,5 +195,29 @@ describe Ripple::Document::Property do
195
195
  end
196
196
  end
197
197
  end
198
+
199
+ describe "when type is a Time type" do
200
+ before :each do
201
+ @prop = Ripple::Document::Property.new(:foo, Time)
202
+ end
203
+
204
+ ["Tue, 16 Mar 2010 12:00:00 -0000","2010/03/16 12:00:00 GMT", Time.utc(2010,03,16,12)].each do |v|
205
+ it "should cast #{v.inspect} to #{Time.utc(2010,03,16,12).inspect}" do
206
+ @prop.type_cast(v).should == Time.utc(2010,03,16,12)
207
+ end
208
+ end
209
+ end
210
+
211
+ describe "when type is a Date type" do
212
+ before :each do
213
+ @prop = Ripple::Document::Property.new(:foo, Date)
214
+ end
215
+
216
+ ["Tue, 16 Mar 2010 00:00:00 -0000", "2010/03/16 12:00:00 GMT", Time.utc(2010,03,16,12), "2010/03/16"].each do |v|
217
+ it "should cast #{v.inspect} to 2010/03/16" do
218
+ @prop.type_cast(v).should == Date.civil(2010,3,16)
219
+ end
220
+ end
221
+ end
198
222
  end
199
223
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ripple
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 6
8
+ - 1
9
+ version: 0.6.1
5
10
  platform: ruby
6
11
  authors:
7
12
  - Sean Cribbs
@@ -9,79 +14,105 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-03-05 00:00:00 -05:00
17
+ date: 2010-03-17 00:00:00 -04:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: rspec
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 3
23
30
  version: "1.3"
24
- version:
31
+ type: :development
32
+ version_requirements: *id001
25
33
  - !ruby/object:Gem::Dependency
26
34
  name: fakeweb
27
- type: :development
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
30
37
  requirements:
31
38
  - - ">="
32
39
  - !ruby/object:Gem::Version
40
+ segments:
41
+ - 1
42
+ - 2
33
43
  version: "1.2"
34
- version:
44
+ type: :development
45
+ version_requirements: *id002
35
46
  - !ruby/object:Gem::Dependency
36
47
  name: rack
37
- type: :development
38
- version_requirement:
39
- version_requirements: !ruby/object:Gem::Requirement
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
40
50
  requirements:
41
51
  - - ">="
42
52
  - !ruby/object:Gem::Version
53
+ segments:
54
+ - 1
55
+ - 0
43
56
  version: "1.0"
44
- version:
57
+ type: :development
58
+ version_requirements: *id003
45
59
  - !ruby/object:Gem::Dependency
46
60
  name: yard
47
- type: :development
48
- version_requirement:
49
- version_requirements: !ruby/object:Gem::Requirement
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
50
63
  requirements:
51
64
  - - ">="
52
65
  - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
68
+ - 5
69
+ - 2
53
70
  version: 0.5.2
54
- version:
71
+ type: :development
72
+ version_requirements: *id004
55
73
  - !ruby/object:Gem::Dependency
56
74
  name: curb
57
- type: :development
58
- version_requirement:
59
- version_requirements: !ruby/object:Gem::Requirement
75
+ prerelease: false
76
+ requirement: &id005 !ruby/object:Gem::Requirement
60
77
  requirements:
61
78
  - - ">="
62
79
  - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ - 6
63
83
  version: "0.6"
64
- version:
84
+ type: :development
85
+ version_requirements: *id005
65
86
  - !ruby/object:Gem::Dependency
66
87
  name: activesupport
67
- type: :runtime
68
- version_requirement:
69
- version_requirements: !ruby/object:Gem::Requirement
88
+ prerelease: false
89
+ requirement: &id006 !ruby/object:Gem::Requirement
70
90
  requirements:
71
91
  - - ~>
72
92
  - !ruby/object:Gem::Version
93
+ segments:
94
+ - 3
95
+ - 0
96
+ - 0
97
+ - beta
73
98
  version: 3.0.0.beta
74
- version:
99
+ type: :runtime
100
+ version_requirements: *id006
75
101
  - !ruby/object:Gem::Dependency
76
102
  name: activemodel
77
- type: :runtime
78
- version_requirement:
79
- version_requirements: !ruby/object:Gem::Requirement
103
+ prerelease: false
104
+ requirement: &id007 !ruby/object:Gem::Requirement
80
105
  requirements:
81
106
  - - ~>
82
107
  - !ruby/object:Gem::Version
108
+ segments:
109
+ - 3
110
+ - 0
111
+ - 0
112
+ - beta
83
113
  version: 3.0.0.beta
84
- version:
114
+ type: :runtime
115
+ version_requirements: *id007
85
116
  description: ripple is a rich Ruby client for Riak, Basho's distributed database. It includes all the basics of accessing and manipulating Riak buckets and objects, and an object mapper library for building a rich domain on top of Riak.
86
117
  email: seancribbs@gmail.com
87
118
  executables: []
@@ -114,6 +145,7 @@ files:
114
145
  - lib/riak/map_reduce.rb
115
146
  - lib/riak/map_reduce_error.rb
116
147
  - lib/riak/robject.rb
148
+ - lib/riak/util/escape.rb
117
149
  - lib/riak/util/fiber1.8.rb
118
150
  - lib/riak/util/headers.rb
119
151
  - lib/riak/util/multipart.rb
@@ -147,6 +179,7 @@ files:
147
179
  - spec/riak/bucket_spec.rb
148
180
  - spec/riak/client_spec.rb
149
181
  - spec/riak/curb_backend_spec.rb
182
+ - spec/riak/escape_spec.rb
150
183
  - spec/riak/headers_spec.rb
151
184
  - spec/riak/http_backend_spec.rb
152
185
  - spec/riak/link_spec.rb
@@ -158,6 +191,7 @@ files:
158
191
  - spec/ripple/attribute_methods_spec.rb
159
192
  - spec/ripple/bucket_access_spec.rb
160
193
  - spec/ripple/callbacks_spec.rb
194
+ - spec/ripple/core_ext_spec.rb
161
195
  - spec/ripple/document_spec.rb
162
196
  - spec/ripple/embedded_document_spec.rb
163
197
  - spec/ripple/finders_spec.rb
@@ -183,18 +217,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
183
217
  requirements:
184
218
  - - ">="
185
219
  - !ruby/object:Gem::Version
220
+ segments:
221
+ - 0
186
222
  version: "0"
187
- version:
188
223
  required_rubygems_version: !ruby/object:Gem::Requirement
189
224
  requirements:
190
225
  - - ">="
191
226
  - !ruby/object:Gem::Version
227
+ segments:
228
+ - 0
192
229
  version: "0"
193
- version:
194
230
  requirements:
195
231
  - `gem install curb` for better HTTP performance
196
232
  rubyforge_project:
197
- rubygems_version: 1.3.5
233
+ rubygems_version: 1.3.6
198
234
  signing_key:
199
235
  specification_version: 3
200
236
  summary: ripple is a rich Ruby client for Riak, Basho's distributed database.
@@ -202,6 +238,7 @@ test_files:
202
238
  - spec/riak/bucket_spec.rb
203
239
  - spec/riak/client_spec.rb
204
240
  - spec/riak/curb_backend_spec.rb
241
+ - spec/riak/escape_spec.rb
205
242
  - spec/riak/headers_spec.rb
206
243
  - spec/riak/http_backend_spec.rb
207
244
  - spec/riak/link_spec.rb
@@ -213,6 +250,7 @@ test_files:
213
250
  - spec/ripple/attribute_methods_spec.rb
214
251
  - spec/ripple/bucket_access_spec.rb
215
252
  - spec/ripple/callbacks_spec.rb
253
+ - spec/ripple/core_ext_spec.rb
216
254
  - spec/ripple/document_spec.rb
217
255
  - spec/ripple/embedded_document_spec.rb
218
256
  - spec/ripple/finders_spec.rb