riak-client 0.9.8 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/.gitignore +32 -0
  2. data/Gemfile +17 -11
  3. data/Guardfile +14 -0
  4. data/Rakefile +18 -44
  5. data/erl_src/riak_kv_test_backend.beam +0 -0
  6. data/erl_src/riak_kv_test_backend.erl +461 -128
  7. data/erl_src/riak_search_test_backend.beam +0 -0
  8. data/erl_src/riak_search_test_backend.erl +175 -0
  9. data/lib/active_support/cache/riak_store.rb +0 -13
  10. data/lib/riak.rb +11 -16
  11. data/lib/riak/bucket.rb +59 -41
  12. data/lib/riak/cache_store.rb +1 -14
  13. data/lib/riak/client.rb +145 -73
  14. data/lib/riak/client/beefcake/messages.rb +36 -31
  15. data/lib/riak/client/beefcake/object_methods.rb +27 -19
  16. data/lib/riak/client/beefcake_protobuffs_backend.rb +27 -33
  17. data/lib/riak/client/excon_backend.rb +0 -13
  18. data/lib/riak/client/http_backend.rb +95 -60
  19. data/lib/riak/client/http_backend/configuration.rb +144 -19
  20. data/lib/riak/client/http_backend/key_streamer.rb +1 -14
  21. data/lib/riak/client/http_backend/object_methods.rb +16 -16
  22. data/lib/riak/client/http_backend/request_headers.rb +0 -13
  23. data/lib/riak/client/http_backend/transport_methods.rb +26 -56
  24. data/lib/riak/client/net_http_backend.rb +11 -13
  25. data/lib/riak/client/protobuffs_backend.rb +21 -19
  26. data/lib/riak/client/pump.rb +1 -15
  27. data/lib/riak/client/search.rb +85 -0
  28. data/lib/riak/cluster.rb +151 -0
  29. data/lib/riak/core_ext.rb +1 -0
  30. data/lib/riak/core_ext/deep_dup.rb +13 -0
  31. data/lib/riak/core_ext/json.rb +15 -0
  32. data/lib/riak/core_ext/stringify_keys.rb +1 -1
  33. data/lib/riak/core_ext/symbolize_keys.rb +1 -1
  34. data/lib/riak/encoding.rb +6 -0
  35. data/lib/riak/failed_request.rb +2 -15
  36. data/lib/riak/i18n.rb +0 -13
  37. data/lib/riak/json.rb +19 -8
  38. data/lib/riak/link.rb +18 -20
  39. data/lib/riak/locale/en.yml +13 -16
  40. data/lib/riak/map_reduce.rb +40 -20
  41. data/lib/riak/map_reduce/filter_builder.rb +14 -18
  42. data/lib/riak/map_reduce/phase.rb +0 -13
  43. data/lib/riak/map_reduce_error.rb +0 -13
  44. data/lib/riak/node.rb +38 -0
  45. data/lib/riak/node/configuration.rb +286 -0
  46. data/lib/riak/node/console.rb +139 -0
  47. data/lib/riak/node/control.rb +207 -0
  48. data/lib/riak/node/defaults.rb +70 -0
  49. data/lib/riak/node/generation.rb +99 -0
  50. data/lib/riak/node/log.rb +34 -0
  51. data/lib/riak/node/version.rb +37 -0
  52. data/lib/riak/robject.rb +45 -41
  53. data/lib/riak/search.rb +2 -161
  54. data/lib/riak/serializers.rb +74 -0
  55. data/lib/riak/stamp.rb +77 -0
  56. data/lib/riak/test_server.rb +56 -220
  57. data/lib/riak/util/escape.rb +58 -17
  58. data/lib/riak/util/headers.rb +2 -15
  59. data/lib/riak/util/multipart.rb +0 -13
  60. data/lib/riak/util/multipart/stream_parser.rb +0 -13
  61. data/lib/riak/util/tcp_socket_extensions.rb +1 -14
  62. data/lib/riak/util/translation.rb +0 -13
  63. data/lib/riak/version.rb +3 -0
  64. data/lib/riak/walk_spec.rb +0 -13
  65. data/riak-client.gemspec +27 -47
  66. data/spec/fixtures/multipart-with-marked-tombstones.txt +17 -0
  67. data/spec/fixtures/multipart-with-unmarked-tombstone.txt +16 -0
  68. data/spec/integration/riak/cache_store_spec.rb +2 -40
  69. data/spec/integration/riak/cluster_spec.rb +88 -0
  70. data/spec/integration/riak/http_backends_spec.rb +6 -30
  71. data/spec/integration/riak/node_spec.rb +184 -0
  72. data/spec/integration/riak/protobuffs_backends_spec.rb +2 -26
  73. data/spec/integration/riak/test_server_spec.rb +31 -167
  74. data/spec/riak/beefcake_protobuffs_backend_spec.rb +5 -4
  75. data/spec/riak/bucket_spec.rb +26 -36
  76. data/spec/riak/client_spec.rb +44 -38
  77. data/spec/riak/escape_spec.rb +56 -30
  78. data/spec/riak/excon_backend_spec.rb +4 -17
  79. data/spec/riak/headers_spec.rb +1 -14
  80. data/spec/riak/http_backend/configuration_spec.rb +211 -34
  81. data/spec/riak/http_backend/object_methods_spec.rb +52 -18
  82. data/spec/riak/http_backend/transport_methods_spec.rb +5 -38
  83. data/spec/riak/http_backend_spec.rb +84 -78
  84. data/spec/riak/link_spec.rb +19 -18
  85. data/spec/riak/map_reduce/filter_builder_spec.rb +1 -14
  86. data/spec/riak/map_reduce/phase_spec.rb +1 -14
  87. data/spec/riak/map_reduce_spec.rb +141 -43
  88. data/spec/riak/multipart_spec.rb +1 -14
  89. data/spec/riak/net_http_backend_spec.rb +2 -15
  90. data/spec/riak/robject_spec.rb +129 -97
  91. data/spec/riak/search_spec.rb +45 -62
  92. data/spec/riak/serializers_spec.rb +93 -0
  93. data/spec/riak/stamp_spec.rb +54 -0
  94. data/spec/riak/stream_parser_spec.rb +3 -16
  95. data/spec/riak/walk_spec_spec.rb +1 -14
  96. data/spec/spec_helper.rb +22 -27
  97. data/spec/support/http_backend_implementation_examples.rb +49 -79
  98. data/spec/support/integration_setup.rb +10 -0
  99. data/spec/support/mock_server.rb +0 -14
  100. data/spec/support/mocks.rb +0 -13
  101. data/spec/support/test_server.rb +30 -0
  102. data/spec/support/test_server.yml.example +14 -2
  103. data/spec/support/unified_backend_examples.rb +36 -27
  104. metadata +100 -31
  105. data/lib/riak/client/curb_backend.rb +0 -89
  106. data/spec/riak/curb_backend_spec.rb +0 -76
@@ -1,17 +1,4 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- require File.expand_path("../spec_helper", File.dirname(__FILE__))
1
+ require 'spec_helper'
15
2
 
16
3
  describe Riak::Link do
17
4
  describe "parsing a link header" do
@@ -39,12 +26,26 @@ describe Riak::Link do
39
26
  result[0].bucket.should be_nil
40
27
  result[0].key.should be_nil
41
28
  end
29
+
30
+ it "should parse the Riak 1.0 URL scheme" do
31
+ result = Riak::Link.parse('</buckets/b/keys/k>; riaktag="tag"').first
32
+ result.bucket.should == 'b'
33
+ result.key.should == 'k'
34
+ result.tag.should == 'tag'
35
+ end
42
36
  end
43
37
 
44
- it "should convert to a string appropriate for use in the Link header" do
45
- Riak::Link.new("/riak/foo", "up").to_s.should == '</riak/foo>; riaktag="up"'
46
- Riak::Link.new("/riak/foo/bar", "next").to_s.should == '</riak/foo/bar>; riaktag="next"'
47
- Riak::Link.new("/riak", "riak_kv_wm_raw").to_s.should == '</riak>; riaktag="riak_kv_wm_raw"'
38
+ context "converting to a string" do
39
+ it "should convert to a string appropriate for use in the Link header" do
40
+ Riak::Link.new("/riak/foo", "up").to_s.should == '</riak/foo>; riaktag="up"'
41
+ Riak::Link.new("/riak/foo/bar", "next").to_s.should == '</riak/foo/bar>; riaktag="next"'
42
+ Riak::Link.new("/riak", "riak_kv_wm_raw").to_s.should == '</riak>; riaktag="riak_kv_wm_raw"'
43
+ end
44
+
45
+ it "should convert to a string using the new URL scheme" do
46
+ Riak::Link.new("bucket", "key", "tag").to_s(true).should == '</buckets/bucket/keys/key>; riaktag="tag"'
47
+ Riak::Link.parse('</riak/bucket/key>; riaktag="tag"').first.to_s(true).should == '</buckets/bucket/keys/key>; riaktag="tag"'
48
+ end
48
49
  end
49
50
 
50
51
  it "should convert to a walk spec when pointing to an object" do
@@ -1,17 +1,4 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- require File.expand_path("../../spec_helper", File.dirname(__FILE__))
1
+ require 'spec_helper'
15
2
 
16
3
  describe Riak::MapReduce::FilterBuilder do
17
4
  subject { Riak::MapReduce::FilterBuilder.new }
@@ -1,17 +1,4 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- require File.expand_path("../../spec_helper", File.dirname(__FILE__))
1
+ require 'spec_helper'
15
2
 
16
3
  describe Riak::MapReduce::Phase do
17
4
  before :each do
@@ -1,17 +1,4 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- require File.expand_path("../spec_helper", File.dirname(__FILE__))
1
+ require 'spec_helper'
15
2
 
16
3
  describe Riak::MapReduce do
17
4
  before :each do
@@ -49,21 +36,11 @@ describe Riak::MapReduce do
49
36
  @mr.inputs.should == [["foo","bar"]]
50
37
  end
51
38
 
52
- it "should add bucket/key pairs to the inputs" do
53
- @mr.add("[foo]","(bar)")
54
- @mr.inputs.should == [["%5Bfoo%5D","%28bar%29"]]
55
- end
56
-
57
39
  it "should add an array containing a bucket/key pair to the inputs" do
58
40
  @mr.add(["foo","bar"])
59
41
  @mr.inputs.should == [["foo","bar"]]
60
42
  end
61
43
 
62
- it "should add an escaped array containing a bucket/key pair to the inputs" do
63
- @mr.add(["[foo]","(bar)"])
64
- @mr.inputs.should == [["%5Bfoo%5D","%28bar%29"]]
65
- end
66
-
67
44
  it "should add an object to the inputs by its bucket and key" do
68
45
  bucket = Riak::Bucket.new(@client, "foo")
69
46
  obj = Riak::RObject.new(bucket, "bar")
@@ -71,23 +48,11 @@ describe Riak::MapReduce do
71
48
  @mr.inputs.should == [["foo", "bar"]]
72
49
  end
73
50
 
74
- it "should add an object to the inputs by its escaped bucket and key" do
75
- bucket = Riak::Bucket.new(@client, "[foo]")
76
- obj = Riak::RObject.new(bucket, "(bar)")
77
- @mr.add(obj)
78
- @mr.inputs.should == [["%5Bfoo%5D", "%28bar%29"]]
79
- end
80
-
81
51
  it "should add an array containing a bucket/key/key-data triple to the inputs" do
82
52
  @mr.add(["foo","bar",1000])
83
53
  @mr.inputs.should == [["foo","bar",1000]]
84
54
  end
85
55
 
86
- it "should add an escaped array containing a bucket/key/key-data triple to the inputs" do
87
- @mr.add(["[foo]","(bar)","[]()"])
88
- @mr.inputs.should == [["%5Bfoo%5D", "%28bar%29","[]()"]]
89
- end
90
-
91
56
  it "should use a bucket name as the single input" do
92
57
  @mr.add(Riak::Bucket.new(@client, "foo"))
93
58
  @mr.inputs.should == "foo"
@@ -95,13 +60,6 @@ describe Riak::MapReduce do
95
60
  @mr.inputs.should == "docs"
96
61
  end
97
62
 
98
- it "should use an escaped bucket name as the single input" do
99
- @mr.add(Riak::Bucket.new(@client, "[foo]"))
100
- @mr.inputs.should == "%5Bfoo%5D"
101
- @mr.add("docs")
102
- @mr.inputs.should == "docs"
103
- end
104
-
105
63
  it "should accept a list of key-filters along with a bucket" do
106
64
  @mr.add("foo", [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]])
107
65
  @mr.inputs.should == {:bucket => "foo", :key_filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
@@ -115,6 +73,146 @@ describe Riak::MapReduce do
115
73
  end
116
74
  @mr.inputs.should == {:bucket => "foo", :key_filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
117
75
  end
76
+
77
+ context "using secondary indexes as inputs" do
78
+ it "should set the inputs for equality" do
79
+ @mr.index("foo", "email_bin", "sean@basho.com").should == @mr
80
+ @mr.inputs.should == {:bucket => "foo", :index => "email_bin", :key => "sean@basho.com"}
81
+ end
82
+
83
+ it "should set the inputs for a range" do
84
+ @mr.index("foo", "rank_int", 10..20).should == @mr
85
+ @mr.inputs.should == {:bucket => "foo", :index => "rank_int", :start => 10, :end => 20}
86
+ end
87
+
88
+ it "should raise an error when given an invalid query" do
89
+ expect { @mr.index("foo", "rank_int", 1.0348) }.to raise_error(ArgumentError)
90
+ expect { @mr.index("foo", "rank_int", Range.new(1.03, 1.05)) }.to raise_error(ArgumentError)
91
+ end
92
+ end
93
+
94
+ describe "escaping" do
95
+ before { @oldesc, Riak.escaper = Riak.escaper, CGI }
96
+ after { Riak.escaper = @oldesc }
97
+
98
+ context "when url_decoding is false" do
99
+ before { @urldecode, Riak.url_decoding = Riak.url_decoding, false }
100
+ after { Riak.url_decoding = @urldecode }
101
+
102
+ it "should add bucket/key pairs to the inputs with bucket and key escaped" do
103
+ @mr.add("[foo]","(bar)")
104
+ @mr.inputs.should == [["%5Bfoo%5D","%28bar%29"]]
105
+ end
106
+
107
+ it "should add an escaped array containing a bucket/key pair to the inputs" do
108
+ @mr.add(["[foo]","(bar)"])
109
+ @mr.inputs.should == [["%5Bfoo%5D","%28bar%29"]]
110
+ end
111
+
112
+ it "should add an object to the inputs by its escaped bucket and key" do
113
+ bucket = Riak::Bucket.new(@client, "[foo]")
114
+ obj = Riak::RObject.new(bucket, "(bar)")
115
+ @mr.add(obj)
116
+ @mr.inputs.should == [["%5Bfoo%5D", "%28bar%29"]]
117
+ end
118
+
119
+ it "should add an escaped array containing a bucket/key/key-data triple to the inputs" do
120
+ @mr.add(["[foo]","(bar)","[]()"])
121
+ @mr.inputs.should == [["%5Bfoo%5D", "%28bar%29","[]()"]]
122
+ end
123
+
124
+ it "should use an escaped bucket name as the single input" do
125
+ @mr.add(Riak::Bucket.new(@client, "[foo]"))
126
+ @mr.inputs.should == "%5Bfoo%5D"
127
+ @mr.add("docs")
128
+ @mr.inputs.should == "docs"
129
+ end
130
+ end
131
+
132
+ context "when url_decoding is true" do
133
+ before { @urldecode, Riak.url_decoding = Riak.url_decoding, true }
134
+ after { Riak.url_decoding = @urldecode }
135
+
136
+ it "should add bucket/key pairs to the inputs with bucket and key unescaped" do
137
+ @mr.add("[foo]","(bar)")
138
+ @mr.inputs.should == [["[foo]","(bar)"]]
139
+ end
140
+
141
+ it "should add an unescaped array containing a bucket/key pair to the inputs" do
142
+ @mr.add(["[foo]","(bar)"])
143
+ @mr.inputs.should == [["[foo]","(bar)"]]
144
+ end
145
+
146
+ it "should add an object to the inputs by its unescaped bucket and key" do
147
+ bucket = Riak::Bucket.new(@client, "[foo]")
148
+ obj = Riak::RObject.new(bucket, "(bar)")
149
+ @mr.add(obj)
150
+ @mr.inputs.should == [["[foo]","(bar)"]]
151
+ end
152
+
153
+ it "should add an unescaped array containing a bucket/key/key-data triple to the inputs" do
154
+ @mr.add(["[foo]","(bar)","[]()"])
155
+ @mr.inputs.should == [["[foo]","(bar)","[]()"]]
156
+ end
157
+
158
+ it "should use an unescaped bucket name as the single input" do
159
+ @mr.add(Riak::Bucket.new(@client, "[foo]"))
160
+ @mr.inputs.should == "[foo]"
161
+ @mr.add("docs")
162
+ @mr.inputs.should == "docs"
163
+ end
164
+ end
165
+ end
166
+
167
+ context "escaping" do
168
+ before { @oldesc, Riak.escaper = Riak.escaper, CGI }
169
+ after { Riak.escaper = @oldesc }
170
+
171
+ it "should add bucket/key pairs to the inputs with bucket and key escaped" do
172
+ @mr.add("[foo]","(bar)")
173
+ @mr.inputs.should == [["%5Bfoo%5D","%28bar%29"]]
174
+ end
175
+
176
+ it "should add an escaped array containing a bucket/key pair to the inputs" do
177
+ @mr.add(["[foo]","(bar)"])
178
+ @mr.inputs.should == [["%5Bfoo%5D","%28bar%29"]]
179
+ end
180
+
181
+ it "should add an object to the inputs by its escaped bucket and key" do
182
+ bucket = Riak::Bucket.new(@client, "[foo]")
183
+ obj = Riak::RObject.new(bucket, "(bar)")
184
+ @mr.add(obj)
185
+ @mr.inputs.should == [["%5Bfoo%5D", "%28bar%29"]]
186
+ end
187
+
188
+ it "should add an escaped array containing a bucket/key/key-data triple to the inputs" do
189
+ @mr.add(["[foo]","(bar)","[]()"])
190
+ @mr.inputs.should == [["%5Bfoo%5D", "%28bar%29","[]()"]]
191
+ end
192
+
193
+ it "should use an escaped bucket name as the single input" do
194
+ @mr.add(Riak::Bucket.new(@client, "[foo]"))
195
+ @mr.inputs.should == "%5Bfoo%5D"
196
+ @mr.add("docs")
197
+ @mr.inputs.should == "docs"
198
+ end
199
+ end
200
+
201
+ context "when adding an input that will result in full-bucket mapreduce" do
202
+ before { Riak.disable_list_keys_warnings = false }
203
+ after { Riak.disable_list_keys_warnings = true }
204
+
205
+ it "should warn about list-keys on buckets" do
206
+ @mr.should_receive(:warn).twice
207
+ @mr.add("foo")
208
+ @mr.add(Riak::Bucket.new(@client, "foo"))
209
+ end
210
+
211
+ it "should warn about list-keys on key-filters" do
212
+ @mr.should_receive(:warn)
213
+ @mr.filter("foo") { matches "bar" }
214
+ end
215
+ end
118
216
  end
119
217
 
120
218
  [:map, :reduce].each do |type|
@@ -1,17 +1,4 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- require File.expand_path("../spec_helper", File.dirname(__FILE__))
1
+ require 'spec_helper'
15
2
 
16
3
  describe Riak::Util::Multipart do
17
4
  it "should extract the boundary string from a header value" do
@@ -1,18 +1,5 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- require File.expand_path("../spec_helper", File.dirname(__FILE__))
15
-
1
+ require 'spec_helper'
2
+
16
3
  describe Riak::Client::NetHTTPBackend do
17
4
  before :each do
18
5
  @client = Riak::Client.new(:http_backend => :NetHTTP)
@@ -1,17 +1,4 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- require File.expand_path("../spec_helper", File.dirname(__FILE__))
1
+ require 'spec_helper'
15
2
 
16
3
  describe Riak::RObject do
17
4
  before :each do
@@ -40,6 +27,13 @@ describe Riak::RObject do
40
27
  @object.meta.should == {}
41
28
  end
42
29
 
30
+ it "should initialize indexes to an empty hash with a Set for the default value" do
31
+ @object = Riak::RObject.new(@bucket, "bar")
32
+ @object.indexes.should be_kind_of(Hash)
33
+ @object.indexes.should be_empty
34
+ @object.indexes['foo_bin'].should be_kind_of(Set)
35
+ end
36
+
43
37
  it "should yield itself to a given block" do
44
38
  Riak::RObject.new(@bucket, "bar") do |r|
45
39
  r.key.should == "bar"
@@ -52,80 +46,18 @@ describe Riak::RObject do
52
46
  @object = Riak::RObject.new(@bucket, "bar")
53
47
  end
54
48
 
55
- it "should change the data into a string by default when serializing" do
56
- @object.serialize("foo").should == "foo"
57
- @object.serialize(2).should == "2"
58
- end
59
-
60
- it "should not change the data when it is an IO" do
61
- file = File.open("#{File.dirname(__FILE__)}/../fixtures/cat.jpg", "r")
62
- file.should_not_receive(:to_s)
63
- @object.serialize(file).should == file
49
+ it 'delegates #serialize to the appropriate serializer for the content type' do
50
+ @object.content_type = 'text/plain'
51
+ Riak::Serializers.should respond_to(:serialize).with(2).arguments
52
+ Riak::Serializers.should_receive(:serialize).with('text/plain', "foo").and_return("serialized foo")
53
+ @object.serialize("foo").should == "serialized foo"
64
54
  end
65
55
 
66
- it "should not modify the data by default when deserializing" do
67
- @object.deserialize("foo").should == "foo"
68
- end
69
-
70
- describe "when the content type is YAML" do
71
- before :each do
72
- @object.content_type = "text/x-yaml"
73
- end
74
-
75
- it "should serialize into a YAML stream" do
76
- @object.serialize({"foo" => "bar"}).should == YAML.dump({"foo" => "bar"})
77
- end
78
-
79
- it "should deserialize a YAML stream" do
80
- @object.deserialize(YAML.dump({"foo" => "bar"})).should == {"foo" => "bar"}
81
- end
82
- end
83
-
84
- describe "when the content type is JSON" do
85
- before :each do
86
- @object.content_type = "application/json"
87
- end
88
-
89
- it "should serialize into a JSON blob" do
90
- @object.serialize({"foo" => "bar"}).should == '{"foo":"bar"}'
91
- @object.serialize([1,2,3]).should == "[1,2,3]"
92
- end
93
-
94
- it "should deserialize a JSON blob" do
95
- @object.deserialize('{"foo":"bar"}').should == {"foo" => "bar"}
96
- @object.deserialize('[1,2,3]').should == [1,2,3]
97
- end
98
-
99
- it "should respect the max nesting option" do
100
- # Sadly, this spec will not fail for me when using yajl-ruby
101
- # on Ruby 1.9, even when passing the options to #to_json is
102
- # not implemented.
103
- Riak.json_options = {:max_nesting => 51}
104
- h = {}
105
- p = h
106
- (1..50).each do |i|
107
- p['a'] = {}
108
- p = p['a']
109
- end
110
- s = h.to_json(Riak.json_options)
111
- lambda { @object.serialize(h) }.should_not raise_error
112
- lambda { @object.deserialize(s) }.should_not raise_error
113
- end
114
- end
115
-
116
- describe "when the content type is application/x-ruby-marshal" do
117
- before :each do
118
- @object.content_type = "application/x-ruby-marshal"
119
- @payload = Marshal.dump({"foo" => "bar"})
120
- end
121
-
122
- it "should dump via Marshal" do
123
- @object.serialize({"foo" => "bar"}).should == @payload
124
- end
125
-
126
- it "should load from Marshal" do
127
- @object.deserialize(@payload).should == {"foo" => "bar"}
128
- end
56
+ it 'delegates #deserialize to the appropriate serializer for the content type' do
57
+ @object.content_type = 'text/plain'
58
+ Riak::Serializers.should respond_to(:deserialize).with(2).arguments
59
+ Riak::Serializers.should_receive(:deserialize).with('text/plain', "foo").and_return("deserialized foo")
60
+ @object.deserialize("foo").should == "deserialized foo"
129
61
  end
130
62
  end
131
63
 
@@ -138,7 +70,7 @@ describe Riak::RObject do
138
70
  describe "for raw data" do
139
71
  describe "when unserialized data was already provided" do
140
72
  before do
141
- @object.data = { :some => :data }
73
+ @object.data = { 'some' => 'data' }
142
74
  end
143
75
 
144
76
  it "should reset unserialized forms when stored" do
@@ -176,6 +108,22 @@ describe Riak::RObject do
176
108
  it "should lazily deserialize when read" do
177
109
  @object.data.should == { "some" => "data" }
178
110
  end
111
+
112
+ context 'for an IO-like object' do
113
+ let(:io_object) { stub(:read => 'the io object') }
114
+
115
+ it 'reads the object before deserializing it' do
116
+ @object.should_receive(:deserialize).with('the io object').and_return('deserialized')
117
+ @object.raw_data = io_object
118
+ @object.data.should == 'deserialized'
119
+ end
120
+
121
+ it 'does not allow it to be assigned directly to data since it should be assigned to raw_data instead' do
122
+ expect {
123
+ @object.data = io_object
124
+ }.to raise_error(ArgumentError)
125
+ end
126
+ end
179
127
  end
180
128
 
181
129
  it "should not unnecessarily marshal/demarshal" do
@@ -202,7 +150,12 @@ describe Riak::RObject do
202
150
  "X-Riak-VTag"=>"5bnavU3rrubcxLI8EvFXhB",
203
151
  "content-type"=>"application/json",
204
152
  "X-Riak-Last-Modified"=>"Mon, 12 Jul 2010 21:37:43 GMT",
205
- "X-Riak-Meta"=>{"X-Riak-Meta-King-Of-Robots"=>"I"}},
153
+ "X-Riak-Meta"=>{"X-Riak-Meta-King-Of-Robots"=>"I"},
154
+ "index" => {
155
+ "email_bin" => ["sean@basho.com","seancribbs@gmail.com"],
156
+ "rank_int" => 50
157
+ }
158
+ },
206
159
  "data"=>
207
160
  "{\"email\":\"mail@test.com\",\"_type\":\"User\"}"
208
161
  }
@@ -235,6 +188,12 @@ describe Riak::RObject do
235
188
  @object.links.first.rel.should == "home_address"
236
189
  end
237
190
 
191
+ it "should load and parse indexes" do
192
+ @object.indexes.should have(2).items
193
+ @object.indexes['email_bin'].should have(2).items
194
+ @object.indexes['rank_int'].should have(1).item
195
+ end
196
+
238
197
  it "should set the ETag" do
239
198
  @object.etag.should == "5bnavU3rrubcxLI8EvFXhB"
240
199
  end
@@ -311,7 +270,7 @@ describe Riak::RObject do
311
270
  end
312
271
 
313
272
  it "should pass along quorum parameters and returnbody to the backend" do
314
- @backend.should_receive(:store_object).with(@object, false, 3, 2).and_return(true)
273
+ @backend.should_receive(:store_object).with(@object, :returnbody => false, :w => 3, :dw => 2).and_return(true)
315
274
  @object.store(:returnbody => false, :w => 3, :dw => 2)
316
275
  end
317
276
  end
@@ -337,15 +296,15 @@ describe Riak::RObject do
337
296
  end
338
297
 
339
298
  it "should reload the object if the key is present" do
340
- @backend.should_receive(:reload_object).with(@object, nil).and_return(@object)
299
+ @backend.should_receive(:reload_object).with(@object, {}).and_return(@object)
341
300
  @object.reload
342
301
  end
343
302
 
344
303
  it "should pass along the requested R quorum" do
345
- @backend.should_receive(:reload_object).with(@object, 2).and_return(@object)
304
+ @backend.should_receive(:reload_object).with(@object, :r => 2).and_return(@object)
346
305
  @object.reload :r => 2
347
306
  end
348
-
307
+
349
308
  it "should disable matching conditions if the key is present and the :force option is given" do
350
309
  @backend.should_receive(:reload_object) do |obj, _|
351
310
  obj.etag.should be_nil
@@ -378,7 +337,7 @@ describe Riak::RObject do
378
337
  end
379
338
 
380
339
  it "should make a DELETE request to the Riak server and freeze the object" do
381
- @backend.should_receive(:delete_object).with(@bucket, "bar", nil)
340
+ @backend.should_receive(:delete_object).with(@bucket, "bar", {})
382
341
  @object.delete
383
342
  @object.should be_frozen
384
343
  end
@@ -393,6 +352,12 @@ describe Riak::RObject do
393
352
  @backend.should_receive(:delete_object).and_raise(Riak::HTTPFailedRequest.new(:delete, [204,404], 500, {}, ""))
394
353
  lambda { @object.delete }.should raise_error(Riak::FailedRequest)
395
354
  end
355
+
356
+ it "should send the vector clock if present" do
357
+ @object.vclock = "somevclock"
358
+ @backend.should_receive(:delete_object).with(@bucket, "bar", {:vclock => "somevclock"})
359
+ @object.delete
360
+ end
396
361
  end
397
362
 
398
363
  it "should not convert to link without a tag" do
@@ -411,9 +376,76 @@ describe Riak::RObject do
411
376
  @object.to_link("bar").url.should == "/riak/bucket%20spaces/deep%2Fpath"
412
377
  end
413
378
 
414
- it "should provide a useful inspect output even when the key is nil" do
415
- @object = Riak::RObject.new(@bucket)
416
- lambda { @object.inspect }.should_not raise_error
417
- @object.inspect.should be_kind_of(String)
379
+ describe "#inspect" do
380
+ let(:object) { Riak::RObject.new(@bucket) }
381
+
382
+ it "provides useful output even when the key is nil" do
383
+ expect { object.inspect }.not_to raise_error
384
+ object.inspect.should be_kind_of(String)
385
+ end
386
+
387
+ it 'uses the serializer output in inspect' do
388
+ object.raw_data = { 'a' => 7 }
389
+ object.content_type = 'inspect/type'
390
+ Riak::Serializers['inspect/type'] = Object.new.tap do |o|
391
+ def o.load(object); "serialize for inspect"; end
392
+ end
393
+
394
+ object.inspect.should =~ /serialize for inspect/
395
+ end
396
+ end
397
+
398
+ describe '.on_conflict' do
399
+ it 'adds the hook to the list of on conflict hooks' do
400
+ hook_run = false
401
+ described_class.on_conflict_hooks.should be_empty
402
+ described_class.on_conflict { hook_run = true }
403
+ described_class.on_conflict_hooks.size.should == 1
404
+ described_class.on_conflict_hooks.first.call
405
+ hook_run.should == true
406
+ end
407
+ end
408
+
409
+ describe '#attempt_conflict_resolution' do
410
+ let(:conflicted_robject) { Riak::RObject.new(@bucket, "conflicted") { |r| r.conflict = true } }
411
+ let(:resolved_robject) { Riak::RObject.new(@bucket, "resolved") }
412
+ let(:invoked_resolvers) { [] }
413
+ let(:resolver_1) { lambda { |r| invoked_resolvers << :resolver_1; nil } }
414
+ let(:resolver_2) { lambda { |r| invoked_resolvers << :resolver_2; :not_an_robject } }
415
+ let(:resolver_3) { lambda { |r| invoked_resolvers << :resolver_3; r } }
416
+ let(:resolver_4) { lambda { |r| invoked_resolvers << :resolver_4; resolved_robject } }
417
+
418
+ before(:each) do
419
+ described_class.on_conflict(&resolver_1)
420
+ described_class.on_conflict(&resolver_2)
421
+ end
422
+
423
+ it 'calls each resolver until one of them returns an robject' do
424
+ described_class.on_conflict(&resolver_3)
425
+ described_class.on_conflict(&resolver_4)
426
+ conflicted_robject.attempt_conflict_resolution
427
+ invoked_resolvers.should == [:resolver_1, :resolver_2, :resolver_3]
428
+ end
429
+
430
+ it 'returns the robject returned by the last invoked resolver' do
431
+ described_class.on_conflict(&resolver_4)
432
+ conflicted_robject.attempt_conflict_resolution.should be(resolved_robject)
433
+ end
434
+
435
+ it 'allows the resolver to return the original robject' do
436
+ described_class.on_conflict(&resolver_3)
437
+ conflicted_robject.attempt_conflict_resolution.should be(conflicted_robject)
438
+ end
439
+
440
+ it 'returns the robject and does not call any resolvers if the robject is not in conflict' do
441
+ resolved_robject.attempt_conflict_resolution.should be(resolved_robject)
442
+ invoked_resolvers.should == []
443
+ end
444
+
445
+ it 'returns the original robject if none of the resolvers returns an robject' do
446
+ conflicted_robject.attempt_conflict_resolution.should be(conflicted_robject)
447
+ invoked_resolvers.should == [:resolver_1, :resolver_2]
448
+ end
418
449
  end
419
450
  end
451
+