ripple 0.6.0 → 0.6.1

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