ripple 0.5.1 → 0.6.0
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.
- data/CONTRIBUTORS.textile +1 -0
- data/README.textile +6 -4
- data/RELEASE_NOTES.textile +20 -0
- data/VERSION +1 -1
- data/lib/riak/bucket.rb +52 -2
- data/lib/riak/client.rb +2 -2
- data/lib/riak/client/curb_backend.rb +42 -23
- data/lib/riak/link.rb +4 -4
- data/lib/riak/map_reduce.rb +9 -1
- data/lib/riak/robject.rb +30 -7
- data/lib/riak/util/fiber1.8.rb +48 -0
- data/lib/riak/walk_spec.rb +1 -1
- data/lib/ripple.rb +3 -1
- data/lib/ripple/document.rb +4 -3
- data/lib/ripple/document/attribute_methods.rb +1 -1
- data/lib/ripple/document/finders.rb +5 -4
- data/lib/ripple/document/timestamps.rb +22 -0
- data/lib/ripple/embedded_document.rb +1 -0
- data/lib/ripple/locale/en.yml +2 -1
- data/ripple.gemspec +6 -2
- data/spec/fixtures/multipart-with-body.txt +2 -2
- data/spec/riak/bucket_spec.rb +79 -8
- data/spec/riak/client_spec.rb +4 -4
- data/spec/riak/http_backend_spec.rb +8 -8
- data/spec/riak/link_spec.rb +12 -12
- data/spec/riak/map_reduce_spec.rb +5 -0
- data/spec/riak/object_spec.rb +54 -16
- data/spec/riak/walk_spec_spec.rb +1 -1
- data/spec/ripple/finders_spec.rb +19 -19
- data/spec/ripple/persistence_spec.rb +4 -4
- data/spec/ripple/ripple_spec.rb +1 -0
- data/spec/ripple/timestamps_spec.rb +49 -0
- data/spec/support/http_backend_implementation_examples.rb +36 -36
- metadata +6 -2
data/CONTRIBUTORS.textile
CHANGED
data/README.textile
CHANGED
@@ -76,15 +76,15 @@ class Email
|
|
76
76
|
property :body, String
|
77
77
|
end
|
78
78
|
|
79
|
-
email = Email.find("37458abc752f8413e") # GET /
|
79
|
+
email = Email.find("37458abc752f8413e") # GET /riak/emails/37458abc752f8413e
|
80
80
|
email.from = "someone@nowhere.net"
|
81
|
-
email.save # PUT /
|
81
|
+
email.save # PUT /riak/emails/37458abc752f8413e
|
82
82
|
|
83
83
|
reply = Email.new
|
84
84
|
reply.from = "justin@bashoooo.com"
|
85
85
|
reply.to = "sean@geeemail.com"
|
86
86
|
reply.body = "Riak is a good fit for scalable Ruby apps."
|
87
|
-
reply.save # POST /
|
87
|
+
reply.save # POST /riak/emails (Riak-assigned key)
|
88
88
|
</pre></notextile>
|
89
89
|
|
90
90
|
h2. How to Contribute
|
@@ -120,7 +120,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
120
120
|
See the License for the specific language governing permissions and
|
121
121
|
limitations under the License.
|
122
122
|
|
123
|
-
h2. Auxillary
|
123
|
+
h2. Auxillary Licenses
|
124
124
|
|
125
125
|
The included photo (spec/fixtures/cat.jpg) is Copyright ©2009 "Sean Cribbs":http://seancribbs.com/, and is
|
126
126
|
licensed under the "Creative Commons Attribution Non-Commercial 3.0":http://creativecommons.org/licenses/by-nc/3.0 license. !http://i.creativecommons.org/l/by-nc/3.0/88x31.png!
|
127
|
+
|
128
|
+
The "Poor Man's Fibers" implementation (lib/riak/util/fiber1.8.rb) is Copyright ©2008 Aman Gupta.
|
data/RELEASE_NOTES.textile
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
h1. Ripple Release Notes
|
2
2
|
|
3
|
+
h2. 0.6.0 Feature Release - 2010-03-05
|
4
|
+
|
5
|
+
This release contains enhancements and bugfixes in preparation for the
|
6
|
+
Riak 0.9 release.
|
7
|
+
|
8
|
+
* The CurbBackend now uses fibers to prevent curl-handle corruption when
|
9
|
+
a block is given to streaming operations.
|
10
|
+
* The default prefix is now "/riak/" to match the latest version of Riak.
|
11
|
+
* The client configuration for Ripple is now used.
|
12
|
+
* Added Bucket#new and Bucket#get_or_new for easily creating new objects.
|
13
|
+
* Added Bucket#allow_mult and Bucket#n_value accessors for more easily setting
|
14
|
+
bucket properties.
|
15
|
+
* Added timestamps! method for easily adding created_at/updated_at to documents.
|
16
|
+
[Adam Hunter]
|
17
|
+
* The 'links' collection on RObject is now a Set instead of an Array.
|
18
|
+
* All literal messages are now stored in YAML localization files.
|
19
|
+
* Object siblings (caused by concurrent updates when allow_mult is true) can now
|
20
|
+
be accessed directly.
|
21
|
+
* Map-reduce jobs now have timeouts (in parity with Riak).
|
22
|
+
|
3
23
|
h2. 0.5.1 Patch Release - 2010-02-22
|
4
24
|
|
5
25
|
This is a minor release with fixes for Ruby 1.9, bundler/edge Rails,
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
data/lib/riak/bucket.rb
CHANGED
@@ -34,7 +34,7 @@ module Riak
|
|
34
34
|
def initialize(client, name)
|
35
35
|
raise ArgumentError, t("client_type", :client => client.inspect) unless Client === client
|
36
36
|
raise ArgumentError, t("string_type", :string => name.inspect) unless String === name
|
37
|
-
@client, @name = client, name
|
37
|
+
@client, @name, @props = client, name, {}
|
38
38
|
end
|
39
39
|
|
40
40
|
# Load information for the bucket from a response given by the {Riak::Client::HTTPBackend}.
|
@@ -92,11 +92,61 @@ module Riak
|
|
92
92
|
# @return [Riak::RObject] the object
|
93
93
|
# @raise [FailedRequest] if the object is not found or some other error occurs
|
94
94
|
def get(key, options={})
|
95
|
-
|
95
|
+
code = allow_mult ? [200,300] : 200
|
96
|
+
response = @client.http.get(code, @client.prefix, name, key, options, {})
|
96
97
|
RObject.new(self, key).load(response)
|
97
98
|
end
|
98
99
|
alias :[] :get
|
99
100
|
|
101
|
+
# Create a new blank object
|
102
|
+
# @param [String] key the key of the new object
|
103
|
+
# @return [RObject] the new, unsaved object
|
104
|
+
def new(key=nil)
|
105
|
+
RObject.new(self, key).tap do |obj|
|
106
|
+
obj.content_type = "application/json"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Fetches an object if it exists, otherwise creates a new one with the given key
|
111
|
+
# @param [String] key the key to fetch or create
|
112
|
+
# @return [RObject] the new or existing object
|
113
|
+
def get_or_new(key, options={})
|
114
|
+
begin
|
115
|
+
get(key, options)
|
116
|
+
rescue Riak::FailedRequest => fr
|
117
|
+
if fr.code.to_i == 404
|
118
|
+
new(key)
|
119
|
+
else
|
120
|
+
raise fr
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [true, false] whether the bucket allows divergent siblings
|
126
|
+
def allow_mult
|
127
|
+
props['allow_mult']
|
128
|
+
end
|
129
|
+
|
130
|
+
# Set the allow_mult property. *NOTE* This will result in a PUT request to Riak.
|
131
|
+
# @param [true, false] value whether the bucket should allow siblings
|
132
|
+
def allow_mult=(value)
|
133
|
+
self.props = props.merge('allow_mult' => value)
|
134
|
+
value
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [Fixnum] the N value, or number of replicas for this bucket
|
138
|
+
def n_value
|
139
|
+
props['n_val']
|
140
|
+
end
|
141
|
+
|
142
|
+
# Set the N value (number of replicas). *NOTE* This will result in a PUT request to Riak.
|
143
|
+
# Setting this value after the bucket has objects stored in it may have unpredictable results.
|
144
|
+
# @param [Fixnum] value the number of replicas the bucket should keep of each object
|
145
|
+
def n_value=(value)
|
146
|
+
self.props = props.merge('n_val' => value)
|
147
|
+
value
|
148
|
+
end
|
149
|
+
|
100
150
|
# @return [String] a representation suitable for IRB and debugging output
|
101
151
|
def inspect
|
102
152
|
"#<Riak::Bucket #{client.http.path(client.prefix, name).to_s}#{" keys=[#{keys.join(',')}]" if defined?(@keys)}>"
|
data/lib/riak/client.rb
CHANGED
@@ -47,7 +47,7 @@ module Riak
|
|
47
47
|
# @param [Hash] options configuration options for the client
|
48
48
|
# @option options [String] :host ('127.0.0.1') The host or IP address for the Riak endpoint
|
49
49
|
# @option options [Fixnum] :port (8098) The port of the Riak HTTP endpoint
|
50
|
-
# @option options [String] :prefix ('/
|
50
|
+
# @option options [String] :prefix ('/riak/') The URL path prefix to the main HTTP endpoint
|
51
51
|
# @option options [String] :mapred ('/mapred') The path to the map-reduce HTTP endpoint
|
52
52
|
# @option options [Fixnum, String] :client_id (rand(MAX_CLIENT_ID)) The internal client ID used by Riak to route responses
|
53
53
|
# @raise [ArgumentError] raised if any options are invalid
|
@@ -56,7 +56,7 @@ module Riak
|
|
56
56
|
self.host = options[:host] || "127.0.0.1"
|
57
57
|
self.port = options[:port] || 8098
|
58
58
|
self.client_id = options[:client_id] || make_client_id
|
59
|
-
self.prefix = options[:prefix] || "/
|
59
|
+
self.prefix = options[:prefix] || "/riak/"
|
60
60
|
self.mapred = options[:mapred] || "/mapred"
|
61
61
|
raise ArgumentError, t("missing_host_and_port") unless @host && @port
|
62
62
|
end
|
@@ -13,6 +13,12 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
require 'riak'
|
15
15
|
|
16
|
+
begin
|
17
|
+
require 'fiber'
|
18
|
+
rescue LoadError
|
19
|
+
require 'riak/util/fiber1.8'
|
20
|
+
end
|
21
|
+
|
16
22
|
module Riak
|
17
23
|
class Client
|
18
24
|
# An HTTP backend for Riak::Client that uses the 'curb' library/gem.
|
@@ -20,44 +26,57 @@ module Riak
|
|
20
26
|
# the backend based on Net::HTTP.
|
21
27
|
# Conforms to the Riak::Client::HTTPBackend interface.
|
22
28
|
class CurbBackend < HTTPBackend
|
23
|
-
# @private
|
24
|
-
def initialize(client)
|
25
|
-
super
|
26
|
-
@curl = Curl::Easy.new
|
27
|
-
@curl.follow_location = false
|
28
|
-
@curl.on_header do |header_line|
|
29
|
-
@response_headers.parse(header_line)
|
30
|
-
header_line.size
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
29
|
private
|
35
30
|
def perform(method, uri, headers, expect, data=nil)
|
36
31
|
# Setup
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
32
|
+
curl.headers = headers
|
33
|
+
curl.url = uri.to_s
|
34
|
+
response_headers.initialize_http_header(nil)
|
35
|
+
if block_given?
|
36
|
+
_curl = curl
|
37
|
+
Fiber.new {
|
38
|
+
f = Fiber.current
|
39
|
+
_curl.on_body {|chunk| f.resume(chunk); chunk.size }
|
40
|
+
loop do
|
41
|
+
yield Fiber.yield
|
42
|
+
end
|
43
|
+
}.resume
|
44
|
+
else
|
45
|
+
curl.on_body
|
46
|
+
end
|
42
47
|
# Perform
|
43
48
|
case method
|
44
49
|
when :put, :post
|
45
|
-
|
50
|
+
curl.send("http_#{method}", data)
|
46
51
|
else
|
47
|
-
|
52
|
+
curl.send("http_#{method}")
|
48
53
|
end
|
49
54
|
|
50
55
|
# Verify
|
51
|
-
if valid_response?(expect,
|
52
|
-
result = { :headers =>
|
53
|
-
if return_body?(method,
|
54
|
-
result[:body] =
|
56
|
+
if valid_response?(expect, curl.response_code)
|
57
|
+
result = { :headers => response_headers.to_hash, :code => curl.response_code.to_i }
|
58
|
+
if return_body?(method, curl.response_code, block_given?)
|
59
|
+
result[:body] = curl.body_str
|
55
60
|
end
|
56
61
|
result
|
57
62
|
else
|
58
|
-
raise FailedRequest.new(method, expect,
|
63
|
+
raise FailedRequest.new(method, expect, curl.response_code, response_headers.to_hash, curl.body_str)
|
59
64
|
end
|
60
65
|
end
|
66
|
+
|
67
|
+
def curl
|
68
|
+
Thread.current[:curl_easy_handle] ||= Curl::Easy.new.tap do |c|
|
69
|
+
c.follow_location = false
|
70
|
+
c.on_header do |header_line|
|
71
|
+
response_headers.parse(header_line)
|
72
|
+
header_line.size
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def response_headers
|
78
|
+
Thread.current[:response_headers] ||= Riak::Util::Headers.new
|
79
|
+
end
|
61
80
|
end
|
62
81
|
end
|
63
82
|
end
|
data/lib/riak/link.rb
CHANGED
@@ -35,14 +35,14 @@ module Riak
|
|
35
35
|
@url, @rel = url, rel
|
36
36
|
end
|
37
37
|
|
38
|
-
# @return [String] bucket_name, if the Link url is a known Riak link ("/
|
38
|
+
# @return [String] bucket_name, if the Link url is a known Riak link ("/riak/<bucket>/<key>")
|
39
39
|
def bucket
|
40
|
-
$1 if url =~ %r{/
|
40
|
+
$1 if url =~ %r{/riak/([^/]+)/?}
|
41
41
|
end
|
42
42
|
|
43
|
-
# @return [String] key, if the Link url is a known Riak link ("/
|
43
|
+
# @return [String] key, if the Link url is a known Riak link ("/riak/<bucket>/<key>")
|
44
44
|
def key
|
45
|
-
$1 if url =~ %r{/
|
45
|
+
$1 if url =~ %r{/riak/[^/]+/([^/]+)/?}
|
46
46
|
end
|
47
47
|
|
48
48
|
def inspect; to_s; end
|
data/lib/riak/map_reduce.rb
CHANGED
@@ -124,10 +124,18 @@ module Riak
|
|
124
124
|
self
|
125
125
|
end
|
126
126
|
|
127
|
+
# Sets the timeout for the map-reduce job.
|
128
|
+
# @param [Fixnum] value the job timeout, in milliseconds
|
129
|
+
def timeout(value)
|
130
|
+
@timeout = value
|
131
|
+
end
|
132
|
+
|
127
133
|
# Convert the job to JSON for submission over the HTTP interface.
|
128
134
|
# @return [String] the JSON representation
|
129
135
|
def to_json(options={})
|
130
|
-
|
136
|
+
hash = {"inputs" => inputs, "query" => query.map(&:as_json)}
|
137
|
+
hash['timeout'] = @timeout.to_i if @timeout
|
138
|
+
ActiveSupport::JSON.encode(hash, options)
|
131
139
|
end
|
132
140
|
|
133
141
|
# Executes this map-reduce job.
|
data/lib/riak/robject.rb
CHANGED
@@ -12,6 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
require 'riak'
|
15
|
+
require 'set'
|
15
16
|
|
16
17
|
module Riak
|
17
18
|
# Parent class of all object types supported by ripple. {Riak::RObject} represents
|
@@ -36,7 +37,7 @@ module Riak
|
|
36
37
|
# @return [Object] the data stored in Riak at this object's key. Varies in format by content-type, defaulting to String from the response body.
|
37
38
|
attr_accessor :data
|
38
39
|
|
39
|
-
# @return [
|
40
|
+
# @return [Set<Link>] an Set of {Riak::Link} objects for relationships between this object and other resources
|
40
41
|
attr_accessor :links
|
41
42
|
|
42
43
|
# @return [String] the ETag header from the most recent HTTP response, useful for caching and reloading
|
@@ -54,7 +55,8 @@ module Riak
|
|
54
55
|
# @see Bucket#get
|
55
56
|
def initialize(bucket, key=nil)
|
56
57
|
@bucket, @key = bucket, key
|
57
|
-
@links, @meta =
|
58
|
+
@links, @meta = Set.new, {}
|
59
|
+
yield self if block_given?
|
58
60
|
end
|
59
61
|
|
60
62
|
# Load object data from an HTTP response
|
@@ -63,7 +65,7 @@ module Riak
|
|
63
65
|
extract_header(response, "location", :key) {|v| URI.unescape(v.split("/").last) }
|
64
66
|
extract_header(response, "content-type", :content_type)
|
65
67
|
extract_header(response, "x-riak-vclock", :vclock)
|
66
|
-
extract_header(response, "link", :links) {|v| Link.parse(v) }
|
68
|
+
extract_header(response, "link", :links) {|v| Set.new(Link.parse(v)) }
|
67
69
|
extract_header(response, "etag", :etag)
|
68
70
|
extract_header(response, "last-modified", :last_modified) {|v| Time.httpdate(v) }
|
69
71
|
@meta = response[:headers].inject({}) do |h,(k,v)|
|
@@ -72,6 +74,8 @@ module Riak
|
|
72
74
|
end
|
73
75
|
h
|
74
76
|
end
|
77
|
+
@conflict = response[:code].try(:to_i) == 300 && content_type =~ /multipart\/mixed/
|
78
|
+
@siblings = nil
|
75
79
|
@data = deserialize(response[:body]) if response[:body].present?
|
76
80
|
self
|
77
81
|
end
|
@@ -126,8 +130,9 @@ module Riak
|
|
126
130
|
def reload(options={})
|
127
131
|
force = options.delete(:force)
|
128
132
|
return self unless @key && (@vclock || force)
|
129
|
-
|
130
|
-
|
133
|
+
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
|
+
load(response) unless response[:code] == 304
|
131
136
|
self
|
132
137
|
end
|
133
138
|
|
@@ -141,6 +146,24 @@ module Riak
|
|
141
146
|
freeze
|
142
147
|
end
|
143
148
|
|
149
|
+
# Returns sibling objects when in conflict.
|
150
|
+
# @return [Array<RObject>] an array of conflicting sibling objects for this key
|
151
|
+
# @return [self] this object when not in conflict
|
152
|
+
def siblings
|
153
|
+
return self unless conflict?
|
154
|
+
@siblings ||= Multipart.parse(data, Multipart.extract_boundary(content_type)).map do |part|
|
155
|
+
RObject.new(self.bucket, self.key) do |sibling|
|
156
|
+
sibling.load(part)
|
157
|
+
sibling.vclock = vclock
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# @return [true,false] Whether this object has conflicting sibling objects (divergent vclocks)
|
163
|
+
def conflict?
|
164
|
+
@conflict.present?
|
165
|
+
end
|
166
|
+
|
144
167
|
# Serializes the internal object data for sending to Riak. Differs based on the content-type.
|
145
168
|
# This method is called internally when storing the object.
|
146
169
|
# Automatically serialized formats:
|
@@ -207,12 +230,12 @@ module Riak
|
|
207
230
|
[]
|
208
231
|
end
|
209
232
|
end
|
210
|
-
|
233
|
+
|
211
234
|
# Converts the object to a link suitable for linking other objects to it
|
212
235
|
def to_link(tag=nil)
|
213
236
|
Link.new(@bucket.client.http.path(@bucket.client.prefix, @bucket.name, @key).path, tag)
|
214
237
|
end
|
215
|
-
|
238
|
+
|
216
239
|
private
|
217
240
|
def extract_header(response, name, attribute=nil)
|
218
241
|
if response[:headers][name].present?
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Poor Man's Fiber (API compatible Thread based Fiber implementation for Ruby 1.8)
|
2
|
+
# (c) 2008 Aman Gupta (tmm1)
|
3
|
+
|
4
|
+
unless defined? Fiber
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
class FiberError < StandardError; end
|
8
|
+
|
9
|
+
class Fiber
|
10
|
+
def initialize
|
11
|
+
raise ArgumentError, 'new Fiber requires a block' unless block_given?
|
12
|
+
|
13
|
+
@yield = Queue.new
|
14
|
+
@resume = Queue.new
|
15
|
+
|
16
|
+
@thread = Thread.new{ @yield.push [ *yield(*@resume.pop) ] }
|
17
|
+
@thread.abort_on_exception = true
|
18
|
+
@thread[:fiber] = self
|
19
|
+
end
|
20
|
+
attr_reader :thread
|
21
|
+
|
22
|
+
def resume *args
|
23
|
+
raise FiberError, 'dead fiber called' unless @thread.alive?
|
24
|
+
@resume.push(args)
|
25
|
+
result = @yield.pop
|
26
|
+
result.size > 1 ? result : result.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def yield *args
|
30
|
+
@yield.push(args)
|
31
|
+
result = @resume.pop
|
32
|
+
result.size > 1 ? result : result.first
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.yield *args
|
36
|
+
raise FiberError, "can't yield from root fiber" unless fiber = Thread.current[:fiber]
|
37
|
+
fiber.yield(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.current
|
41
|
+
Thread.current[:fiber] or raise FiberError, 'not inside a fiber'
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}>"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/riak/walk_spec.rb
CHANGED
@@ -18,7 +18,7 @@ module Riak
|
|
18
18
|
# The specification of how to follow links from one object to another in Riak,
|
19
19
|
# when using the link-walker resource.
|
20
20
|
# Example link-walking operation:
|
21
|
-
# GET /
|
21
|
+
# GET /riak/artists/REM/albums,_,_/tracks,_,1
|
22
22
|
# This operation would have two WalkSpecs:
|
23
23
|
# Riak::WalkSpec.new({:bucket => 'albums'})
|
24
24
|
# Riak::WalkSpec.new({:bucket => 'tracks', :result => true})
|
data/lib/ripple.rb
CHANGED
@@ -28,10 +28,12 @@ module Ripple
|
|
28
28
|
autoload :PropertyTypeMismatch
|
29
29
|
autoload :Translation
|
30
30
|
|
31
|
+
DEFAULT_CONFIG = {}
|
32
|
+
|
31
33
|
class << self
|
32
34
|
# @return [Riak::Client] The client for the current thread.
|
33
35
|
def client
|
34
|
-
Thread.current[:ripple_client] ||= Riak::Client.new
|
36
|
+
Thread.current[:ripple_client] ||= Riak::Client.new(config)
|
35
37
|
end
|
36
38
|
|
37
39
|
# Sets the client for the current thread.
|