ripple 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +26 -0
- data/LICENSE +13 -0
- data/README.textile +126 -0
- data/RELEASE_NOTES.textile +24 -0
- data/Rakefile +61 -0
- data/VERSION +1 -0
- data/lib/riak.rb +45 -0
- data/lib/riak/bucket.rb +105 -0
- data/lib/riak/client.rb +138 -0
- data/lib/riak/client/curb_backend.rb +63 -0
- data/lib/riak/client/http_backend.rb +209 -0
- data/lib/riak/client/net_http_backend.rb +49 -0
- data/lib/riak/failed_request.rb +37 -0
- data/lib/riak/i18n.rb +15 -0
- data/lib/riak/invalid_response.rb +25 -0
- data/lib/riak/link.rb +54 -0
- data/lib/riak/locale/en.yml +37 -0
- data/lib/riak/map_reduce.rb +240 -0
- data/lib/riak/map_reduce_error.rb +20 -0
- data/lib/riak/robject.rb +234 -0
- data/lib/riak/util/headers.rb +44 -0
- data/lib/riak/util/multipart.rb +52 -0
- data/lib/riak/util/translation.rb +29 -0
- data/lib/riak/walk_spec.rb +113 -0
- data/lib/ripple.rb +48 -0
- data/lib/ripple/core_ext/casting.rb +96 -0
- data/lib/ripple/document.rb +60 -0
- data/lib/ripple/document/attribute_methods.rb +111 -0
- data/lib/ripple/document/attribute_methods/dirty.rb +52 -0
- data/lib/ripple/document/attribute_methods/query.rb +49 -0
- data/lib/ripple/document/attribute_methods/read.rb +38 -0
- data/lib/ripple/document/attribute_methods/write.rb +36 -0
- data/lib/ripple/document/bucket_access.rb +38 -0
- data/lib/ripple/document/finders.rb +84 -0
- data/lib/ripple/document/persistence.rb +93 -0
- data/lib/ripple/document/persistence/callbacks.rb +48 -0
- data/lib/ripple/document/properties.rb +85 -0
- data/lib/ripple/document/validations.rb +44 -0
- data/lib/ripple/embedded_document.rb +38 -0
- data/lib/ripple/embedded_document/persistence.rb +46 -0
- data/lib/ripple/i18n.rb +15 -0
- data/lib/ripple/locale/en.yml +16 -0
- data/lib/ripple/property_type_mismatch.rb +23 -0
- data/lib/ripple/translation.rb +24 -0
- data/ripple.gemspec +159 -0
- data/spec/fixtures/cat.jpg +0 -0
- data/spec/fixtures/multipart-blank.txt +7 -0
- data/spec/fixtures/multipart-with-body.txt +16 -0
- data/spec/riak/bucket_spec.rb +141 -0
- data/spec/riak/client_spec.rb +169 -0
- data/spec/riak/curb_backend_spec.rb +50 -0
- data/spec/riak/headers_spec.rb +34 -0
- data/spec/riak/http_backend_spec.rb +136 -0
- data/spec/riak/link_spec.rb +50 -0
- data/spec/riak/map_reduce_spec.rb +347 -0
- data/spec/riak/multipart_spec.rb +36 -0
- data/spec/riak/net_http_backend_spec.rb +28 -0
- data/spec/riak/object_spec.rb +444 -0
- data/spec/riak/walk_spec_spec.rb +208 -0
- data/spec/ripple/attribute_methods_spec.rb +149 -0
- data/spec/ripple/bucket_access_spec.rb +48 -0
- data/spec/ripple/callbacks_spec.rb +86 -0
- data/spec/ripple/document_spec.rb +35 -0
- data/spec/ripple/embedded_document_spec.rb +52 -0
- data/spec/ripple/finders_spec.rb +146 -0
- data/spec/ripple/persistence_spec.rb +89 -0
- data/spec/ripple/properties_spec.rb +195 -0
- data/spec/ripple/ripple_spec.rb +43 -0
- data/spec/ripple/validations_spec.rb +64 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/http_backend_implementation_examples.rb +215 -0
- data/spec/support/mock_server.rb +58 -0
- metadata +221 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
*.swp
|
15
|
+
|
16
|
+
## PROJECT::GENERAL
|
17
|
+
coverage
|
18
|
+
rdoc
|
19
|
+
pkg
|
20
|
+
|
21
|
+
## PROJECT::SPECIFIC
|
22
|
+
_notes
|
23
|
+
doc
|
24
|
+
.yardoc
|
25
|
+
create
|
26
|
+
update-rails
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
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.
|
data/README.textile
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
h1. ripple
|
2
|
+
|
3
|
+
ripple is a rich Ruby client for Riak, Basho's distributed database. It includes two namespaces:
|
4
|
+
|
5
|
+
* @Riak@ contains a basic wrapper around typical operations, including bucket manipulation, object CRUD, link-walking, and map-reduce.
|
6
|
+
* @Ripple@ contains an ActiveModel-compatible modeling layer that is inspired by ActiveRecord, DataMapper, and MongoMapper.
|
7
|
+
|
8
|
+
h2. Dependencies
|
9
|
+
|
10
|
+
ripple requires Ruby 1.8.7 or later and versions 3 or above of ActiveModel and ActiveSupport (and their dependencies, including i18n). Please see the Rails 3 beta release notes for installing those gems. I highly recommend the "curb":http://curb.rubyforge.org/ gem for better HTTP client performance.
|
11
|
+
|
12
|
+
In development, you will also need these gems:
|
13
|
+
|
14
|
+
* jeweler
|
15
|
+
* rspec >= 1.3
|
16
|
+
* fakeweb >= 1.2
|
17
|
+
* curb >= 0.6
|
18
|
+
* rack >= 1.0
|
19
|
+
* yard >= 0.5.2
|
20
|
+
|
21
|
+
h2. Basic Example
|
22
|
+
|
23
|
+
<notextile><pre>require 'riak'
|
24
|
+
|
25
|
+
# Create a client interface
|
26
|
+
client = Riak::Client.new
|
27
|
+
|
28
|
+
# Retrieve a bucket
|
29
|
+
bucket = client.bucket("doc") # a Riak::Bucket
|
30
|
+
|
31
|
+
# Get an object from the bucket
|
32
|
+
object = bucket.get("index.html") # a Riak::RObject
|
33
|
+
|
34
|
+
# Change the object's data and save
|
35
|
+
object.data = "<html><body>Hello, world!</body></html>"
|
36
|
+
object.store
|
37
|
+
|
38
|
+
# Reload an object you already have
|
39
|
+
object.reload # Works if you have the key and vclock, using conditional GET
|
40
|
+
object.reload :force => true # Reloads whether you have the vclock or not
|
41
|
+
|
42
|
+
# Access more like a hash, client[bucket][key]
|
43
|
+
client['doc']['index.html'] # the Riak::RObject
|
44
|
+
|
45
|
+
# Create a new object
|
46
|
+
new_one = Riak::RObject.new(bucket, "application.js")
|
47
|
+
new_one.content_type = "application/javascript" # You must set the content type.
|
48
|
+
new_one.data = "alert('Hello, World!')"
|
49
|
+
new_one.store</pre></notextile>
|
50
|
+
|
51
|
+
h2. Map-Reduce Example
|
52
|
+
|
53
|
+
<notextile><pre>
|
54
|
+
|
55
|
+
# Assuming you've already instantiated a client, get the album titles for The Beatles
|
56
|
+
results = Riak::MapReduce.new(client).
|
57
|
+
add("artists","Beatles").
|
58
|
+
link(:bucket => "albums").
|
59
|
+
map("function(v){ return [JSON.parse(v.values[0].data).title]; }", :keep => true).run
|
60
|
+
|
61
|
+
p results # => ["Please Please Me", "With The Beatles", "A Hard Day's Night",
|
62
|
+
# "Beatles For Sale", "Help!", "Rubber Soul",
|
63
|
+
# "Revolver", "Sgt. Pepper's Lonely Hearts Club Band", "Magical Mystery Tour",
|
64
|
+
# "The Beatles", "Yellow Submarine", "Abbey Road", "Let It Be"]</pre></notextile>
|
65
|
+
|
66
|
+
h2. Document model Example
|
67
|
+
|
68
|
+
<notextile><pre>
|
69
|
+
require 'ripple'
|
70
|
+
|
71
|
+
class Email
|
72
|
+
include Ripple::Document
|
73
|
+
property :from, String, :presence => true
|
74
|
+
property :to, String, :presence => true
|
75
|
+
property :sent, Time, :default => proc { Time.now }
|
76
|
+
property :body, String
|
77
|
+
end
|
78
|
+
|
79
|
+
email = Email.find("37458abc752f8413e") # GET /raw/emails/37458abc752f8413e
|
80
|
+
email.from = "someone@nowhere.net"
|
81
|
+
email.save # PUT /raw/emails/37458abc752f8413e
|
82
|
+
|
83
|
+
reply = Email.new
|
84
|
+
reply.from = "justin@bashoooo.com"
|
85
|
+
reply.to = "sean@geeemail.com"
|
86
|
+
reply.body = "Riak is a good fit for scalable Ruby apps."
|
87
|
+
reply.save # POST /raw/emails (Riak-assigned key)
|
88
|
+
</pre></notextile>
|
89
|
+
|
90
|
+
h2. How to Contribute
|
91
|
+
|
92
|
+
* Fork the project on "Github":http://github.com/seancribbs/ripple. If you have already forked, use @git pull --rebase@ to reapply your changes on top of the mainline. Example:
|
93
|
+
<notextile><pre>$ git checkout master
|
94
|
+
$ git pull --rebase seancribbs master</pre></notextile>
|
95
|
+
* Create a topic branch. If you've already created a topic branch, rebase it on top of changes from the mainline "master" branch. Examples:
|
96
|
+
** New branch:
|
97
|
+
<pre>$ git checkout -b topic</pre>
|
98
|
+
** Existing branch:
|
99
|
+
<pre>$ git rebase master</pre>
|
100
|
+
* Write an RSpec example, set of examples, and/or Cucumber story that demonstrate the necessity and validity of your changes. *Patches without specs will most often be ignored. Just do it, you'll thank me later.* Documentation patches need no specs, of course.
|
101
|
+
* Make your feature addition or bug fix. Make your specs and stories pass (green).
|
102
|
+
* Run the suite using multiruby or rvm to ensure cross-version compatibility.
|
103
|
+
* Cleanup any trailing whitespace in your code (try @whitespace-mode@ in Emacs, or "Remove Trailing Spaces in Document" in the "Text" bundle in Textmate).
|
104
|
+
* Commit, do not mess with Rakefile or VERSION. If related to an existing issue in the "tracker":http://github.com/seancribbs/ripple/issues, include "Closes #X" in the commit message (where X is the issue number).
|
105
|
+
* Send me a pull request.
|
106
|
+
|
107
|
+
h2. License & Copyright
|
108
|
+
|
109
|
+
Copyright ©2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
110
|
+
|
111
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
112
|
+
you may not use this file except in compliance with the License.
|
113
|
+
You may obtain a copy of the License at
|
114
|
+
|
115
|
+
"http://www.apache.org/licenses/LICENSE-2.0":http://www.apache.org/licenses/LICENSE-2.0
|
116
|
+
|
117
|
+
Unless required by applicable law or agreed to in writing, software
|
118
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
119
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
120
|
+
See the License for the specific language governing permissions and
|
121
|
+
limitations under the License.
|
122
|
+
|
123
|
+
h2. Auxillary License
|
124
|
+
|
125
|
+
The included photo (spec/fixtures/cat.jpg) is Copyright ©2009 "Sean Cribbs":http://seancribbs.com/, and is
|
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!
|
@@ -0,0 +1,24 @@
|
|
1
|
+
h1. Ripple Release Notes
|
2
|
+
|
3
|
+
h2. 0.5 Initial Release - 2010-02-10
|
4
|
+
|
5
|
+
This is the first release of Ripple, which would not have been possible
|
6
|
+
without the generous support of Sonian and Basho Technologies. Many thanks.
|
7
|
+
It includes:
|
8
|
+
|
9
|
+
* A robust basic client, @Riak@, with:
|
10
|
+
** multiple HTTP backends (curb, net/http)
|
11
|
+
** sensible client defaults (local, default port)
|
12
|
+
** bucket access and manipulation, including key-streaming
|
13
|
+
** object reading, storing, deleting and reloading
|
14
|
+
** automatic de-serialization of JSON, YAML, and Marshal (when given the right content type)
|
15
|
+
** streaming POST/PUT bodies (when given an IO)
|
16
|
+
** method-chained map-reduce job construction
|
17
|
+
* A document-style modeling library, Ripple, with:
|
18
|
+
** ActiveModel 3.0 compatibility
|
19
|
+
** Property/attribute definition with automatic type-casting
|
20
|
+
** Bucket selection based on class name, with single-bucket inheritance (configurable)
|
21
|
+
** Validations
|
22
|
+
** Dirty-tracking
|
23
|
+
** Simple finders - all documents, by key
|
24
|
+
** Reloading
|
data/Rakefile
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "ripple"
|
9
|
+
gem.summary = %Q{ripple is a rich Ruby client for Riak, Basho's distributed database.}
|
10
|
+
gem.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.}
|
11
|
+
gem.email = "seancribbs@gmail.com"
|
12
|
+
gem.homepage = "http://seancribbs.github.com/ripple"
|
13
|
+
gem.authors = ["Sean Cribbs"]
|
14
|
+
gem.add_development_dependency "rspec", ">= 1.3"
|
15
|
+
gem.add_development_dependency "fakeweb", ">=1.2"
|
16
|
+
gem.add_development_dependency "rack", ">=1.0"
|
17
|
+
gem.add_development_dependency "yard", ">=0.5.2"
|
18
|
+
gem.add_development_dependency "curb", ">=0.6"
|
19
|
+
gem.add_dependency "activesupport", "3.0.0.beta"
|
20
|
+
gem.add_dependency "activemodel", "3.0.0.beta"
|
21
|
+
gem.requirements << "`gem install curb` for better HTTP performance"
|
22
|
+
end
|
23
|
+
Jeweler::GemcutterTasks.new
|
24
|
+
rescue LoadError
|
25
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'spec/rake/spectask'
|
29
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
35
|
+
spec.libs << 'lib' << 'spec'
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
spec.rcov_opts = ['--exclude', 'lib\/spec,bin\/spec,config\/boot.rb,gems,spec_helper']
|
39
|
+
end
|
40
|
+
|
41
|
+
task :spec => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :spec
|
44
|
+
|
45
|
+
require 'yard'
|
46
|
+
YARD::Rake::YardocTask.new do |yard|
|
47
|
+
docfiles = FileList['lib/**/*.rb', 'README*', 'VERSION', 'LICENSE', 'RELEASE_NOTES.textile']
|
48
|
+
yard.files = docfiles
|
49
|
+
yard.options = ["--no-private"]
|
50
|
+
end
|
51
|
+
|
52
|
+
task :doc => :yard do
|
53
|
+
original_dir = Dir.pwd
|
54
|
+
docs_dir = File.expand_path(File.join(original_dir, "..", "ripple-docs"))
|
55
|
+
rm_rf File.join(docs_dir, "*")
|
56
|
+
cp_r File.join(original_dir, "doc", "."), docs_dir
|
57
|
+
touch File.join(docs_dir, '.nojekyll')
|
58
|
+
end
|
59
|
+
|
60
|
+
CLOBBER.include(".yardoc")
|
61
|
+
CLOBBER.include("doc")
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.0
|
data/lib/riak.rb
ADDED
@@ -0,0 +1,45 @@
|
|
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
|
+
$KCODE = "UTF8" if RUBY_VERSION < "1.9"
|
15
|
+
|
16
|
+
require 'active_support/all'
|
17
|
+
require 'active_support/json'
|
18
|
+
require 'base64'
|
19
|
+
require 'uri'
|
20
|
+
require 'net/http'
|
21
|
+
require 'yaml'
|
22
|
+
require 'riak/i18n'
|
23
|
+
|
24
|
+
# The Riak module contains all aspects of the HTTP client interface
|
25
|
+
# to Riak.
|
26
|
+
module Riak
|
27
|
+
# Domain objects
|
28
|
+
autoload :Bucket, "riak/bucket"
|
29
|
+
autoload :Client, "riak/client"
|
30
|
+
autoload :Link, "riak/link"
|
31
|
+
autoload :WalkSpec, "riak/walk_spec"
|
32
|
+
autoload :RObject, "riak/robject"
|
33
|
+
autoload :MapReduce, "riak/map_reduce"
|
34
|
+
|
35
|
+
# Exceptions
|
36
|
+
autoload :FailedRequest, "riak/failed_request"
|
37
|
+
autoload :InvalidResponse, "riak/invalid_response"
|
38
|
+
autoload :MapReduceError, "riak/map_reduce_error"
|
39
|
+
|
40
|
+
module Util
|
41
|
+
autoload :Headers, "riak/util/headers"
|
42
|
+
autoload :Multipart, "riak/util/multipart"
|
43
|
+
autoload :Translation, "riak/util/translation"
|
44
|
+
end
|
45
|
+
end
|
data/lib/riak/bucket.rb
ADDED
@@ -0,0 +1,105 @@
|
|
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 'riak'
|
15
|
+
|
16
|
+
module Riak
|
17
|
+
# Represents and encapsulates operations on a Riak bucket. You may retrieve a bucket
|
18
|
+
# using {Client#bucket}, or create it manually and retrieve its meta-information later.
|
19
|
+
class Bucket
|
20
|
+
include Util::Translation
|
21
|
+
# @return [Riak::Client] the associated client
|
22
|
+
attr_reader :client
|
23
|
+
|
24
|
+
# @return [String] the bucket name
|
25
|
+
attr_reader :name
|
26
|
+
|
27
|
+
# @return [Hash] Internal Riak bucket properties.
|
28
|
+
attr_reader :props
|
29
|
+
alias_attribute :properties, :props
|
30
|
+
|
31
|
+
# Create a Riak bucket manually.
|
32
|
+
# @param [Client] client the {Riak::Client} for this bucket
|
33
|
+
# @param [String] name the name of the bucket
|
34
|
+
def initialize(client, name)
|
35
|
+
raise ArgumentError, t("client_type", :client => client.inspect) unless Client === client
|
36
|
+
raise ArgumentError, t("string_type", :string => name.inspect) unless String === name
|
37
|
+
@client, @name = client, name
|
38
|
+
end
|
39
|
+
|
40
|
+
# Load information for the bucket from a response given by the {Riak::Client::HTTPBackend}.
|
41
|
+
# Used mostly internally - use {Riak::Client#bucket} to get a {Bucket} instance.
|
42
|
+
# @param [Hash] response a response from {Riak::Client::HTTPBackend}
|
43
|
+
# @return [Bucket] self
|
44
|
+
# @see Client#bucket
|
45
|
+
def load(response={})
|
46
|
+
unless response.try(:[], :headers).try(:[],'content-type').try(:first) =~ /json$/
|
47
|
+
raise Riak::InvalidResponse.new({"content-type" => ["application/json"]}, response[:headers], t("loading_bucket", :name => name))
|
48
|
+
end
|
49
|
+
payload = JSON.parse(response[:body])
|
50
|
+
@keys = payload['keys'].map {|k| URI.unescape(k) } if payload['keys']
|
51
|
+
@props = payload['props'] if payload['props']
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
# Accesses or retrieves a list of keys in this bucket.
|
56
|
+
# If a block is given, keys will be streamed through
|
57
|
+
# the block (useful for large buckets). When streaming,
|
58
|
+
# results of the operation will not be retained in the local Bucket object.
|
59
|
+
# @param [Hash] options extra options
|
60
|
+
# @yield [Array<String>] a list of keys from the current chunk
|
61
|
+
# @option options [Boolean] :reload (false) If present, will force reloading of the bucket's keys from Riak
|
62
|
+
# @return [Array<String>] Keys in this bucket
|
63
|
+
def keys(options={})
|
64
|
+
if block_given?
|
65
|
+
@client.http.get(200, @client.prefix, name, {:props => false}, {}) do |chunk|
|
66
|
+
obj = JSON.parse(chunk) rescue {}
|
67
|
+
yield obj['keys'].map {|k| URI.unescape(k) } if obj['keys']
|
68
|
+
end
|
69
|
+
elsif @keys.nil? || options[:reload]
|
70
|
+
response = @client.http.get(200, @client.prefix, name, {:props => false}, {})
|
71
|
+
load(response)
|
72
|
+
end
|
73
|
+
@keys
|
74
|
+
end
|
75
|
+
|
76
|
+
# Sets internal properties on the bucket
|
77
|
+
# Note: this results in a request to the Riak server!
|
78
|
+
# @param [Hash] properties new properties for the bucket
|
79
|
+
# @return [Hash] the properties that were accepted
|
80
|
+
# @raise [FailedRequest] if the new properties were not accepted by the Riak server
|
81
|
+
def props=(properties)
|
82
|
+
raise ArgumentError, t("hash_type", :hash => properties.inspect) unless Hash === properties
|
83
|
+
body = {'props' => properties}.to_json
|
84
|
+
@client.http.put(204, @client.prefix, name, body, {"Content-Type" => "application/json"})
|
85
|
+
@props = properties
|
86
|
+
end
|
87
|
+
|
88
|
+
# Retrieve an object from within the bucket.
|
89
|
+
# @param [String] key the key of the object to retrieve
|
90
|
+
# @param [Hash] options query parameters for the request
|
91
|
+
# @option options [Fixnum] :r - the read quorum for the request - how many nodes should concur on the read
|
92
|
+
# @return [Riak::RObject] the object
|
93
|
+
# @raise [FailedRequest] if the object is not found or some other error occurs
|
94
|
+
def get(key, options={})
|
95
|
+
response = @client.http.get(200, @client.prefix, name, key, options, {})
|
96
|
+
RObject.new(self, key).load(response)
|
97
|
+
end
|
98
|
+
alias :[] :get
|
99
|
+
|
100
|
+
# @return [String] a representation suitable for IRB and debugging output
|
101
|
+
def inspect
|
102
|
+
"#<Riak::Bucket #{client.http.path(client.prefix, name).to_s}#{" keys=[#{keys.join(',')}]" if defined?(@keys)}>"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/riak/client.rb
ADDED
@@ -0,0 +1,138 @@
|
|
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 'riak'
|
15
|
+
|
16
|
+
module Riak
|
17
|
+
# A client connection to Riak.
|
18
|
+
class Client
|
19
|
+
include Util::Translation
|
20
|
+
|
21
|
+
autoload :HTTPBackend, "riak/client/http_backend"
|
22
|
+
autoload :NetHTTPBackend, "riak/client/net_http_backend"
|
23
|
+
autoload :CurbBackend, "riak/client/curb_backend"
|
24
|
+
|
25
|
+
# When using integer client IDs, the exclusive upper-bound of valid values.
|
26
|
+
MAX_CLIENT_ID = 4294967296
|
27
|
+
|
28
|
+
# Regexp for validating hostnames, lifted from uri.rb in Ruby 1.8.6
|
29
|
+
HOST_REGEX = /^(?:(?:(?:[a-zA-Z\d](?:[-a-zA-Z\d]*[a-zA-Z\d])?)\.)*(?:[a-zA-Z](?:[-a-zA-Z\d]*[a-zA-Z\d])?)\.?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-fA-F\d]{1,4}:)*[a-fA-F\d]{1,4})?::(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)\])$/n
|
30
|
+
|
31
|
+
# @return [String] The host or IP address for the Riak endpoint
|
32
|
+
attr_reader :host
|
33
|
+
|
34
|
+
# @return [Fixnum] The port of the Riak HTTP endpoint
|
35
|
+
attr_reader :port
|
36
|
+
|
37
|
+
# @return [String] The internal client ID used by Riak to route responses
|
38
|
+
attr_reader :client_id
|
39
|
+
|
40
|
+
# @return [String] The URL path prefix to the "raw" HTTP endpoint
|
41
|
+
attr_accessor :prefix
|
42
|
+
|
43
|
+
# @return [String] The URL path to the map-reduce HTTP endpoint
|
44
|
+
attr_accessor :mapred
|
45
|
+
|
46
|
+
# Creates a client connection to Riak
|
47
|
+
# @param [Hash] options configuration options for the client
|
48
|
+
# @option options [String] :host ('127.0.0.1') The host or IP address for the Riak endpoint
|
49
|
+
# @option options [Fixnum] :port (8098) The port of the Riak HTTP endpoint
|
50
|
+
# @option options [String] :prefix ('/raw/') The URL path prefix to the "raw" HTTP endpoint
|
51
|
+
# @option options [String] :mapred ('/mapred') The path to the map-reduce HTTP endpoint
|
52
|
+
# @option options [Fixnum, String] :client_id (rand(MAX_CLIENT_ID)) The internal client ID used by Riak to route responses
|
53
|
+
# @raise [ArgumentError] raised if any options are invalid
|
54
|
+
def initialize(options={})
|
55
|
+
options.assert_valid_keys(:host, :port, :prefix, :client_id, :mapred)
|
56
|
+
self.host = options[:host] || "127.0.0.1"
|
57
|
+
self.port = options[:port] || 8098
|
58
|
+
self.client_id = options[:client_id] || make_client_id
|
59
|
+
self.prefix = options[:prefix] || "/raw/"
|
60
|
+
self.mapred = options[:mapred] || "/mapred"
|
61
|
+
raise ArgumentError, t("missing_host_and_port") unless @host && @port
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set the client ID for this client. Must be a string or Fixnum value 0 =< value < MAX_CLIENT_ID.
|
65
|
+
# @param [String, Fixnum] value The internal client ID used by Riak to route responses
|
66
|
+
# @raise [ArgumentError] when an invalid client ID is given
|
67
|
+
# @return [String] the assigned client ID
|
68
|
+
def client_id=(value)
|
69
|
+
@client_id = case value
|
70
|
+
when 0...MAX_CLIENT_ID
|
71
|
+
b64encode(value)
|
72
|
+
when String
|
73
|
+
value
|
74
|
+
else
|
75
|
+
raise ArgumentError, t("invalid_client_id", :max_id => MAX_CLIENT_ID)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Set the hostname of the Riak endpoint. Must be an IPv4, IPv6, or valid hostname
|
80
|
+
# @param [String] value The host or IP address for the Riak endpoint
|
81
|
+
# @raise [ArgumentError] if an invalid hostname is given
|
82
|
+
# @return [String] the assigned hostname
|
83
|
+
def host=(value)
|
84
|
+
raise ArgumentError, t("hostname_invalid") unless String === value && value.present? && value =~ HOST_REGEX
|
85
|
+
@host = value
|
86
|
+
end
|
87
|
+
|
88
|
+
# Set the port number of the Riak endpoint. This must be an integer between 0 and 65535.
|
89
|
+
# @param [Fixnum] value The port number of the Riak endpoint
|
90
|
+
# @raise [ArgumentError] if an invalid port number is given
|
91
|
+
# @return [Fixnum] the assigned port number
|
92
|
+
def port=(value)
|
93
|
+
raise ArgumentError, t("port_invalid") unless (0..65535).include?(value)
|
94
|
+
@port = value
|
95
|
+
end
|
96
|
+
|
97
|
+
# Automatically detects and returns an appropriate HTTP backend.
|
98
|
+
# The HTTP backend is used internally by the Riak client, but can also
|
99
|
+
# be used to access the server directly.
|
100
|
+
# @return [HTTPBackend] the HTTP backend for this client
|
101
|
+
def http
|
102
|
+
@http ||= begin
|
103
|
+
require 'curb'
|
104
|
+
CurbBackend.new(self)
|
105
|
+
rescue LoadError, NameError
|
106
|
+
warn t("install_curb")
|
107
|
+
NetHTTPBackend.new(self)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Retrieves a bucket from Riak.
|
112
|
+
# @param [String] bucket the bucket to retrieve
|
113
|
+
# @param [Hash] options options for retrieving the bucket
|
114
|
+
# @option options [Boolean] :keys (true) whether to retrieve the bucket keys
|
115
|
+
# @option options [Boolean] :props (true) whether to retreive the bucket properties
|
116
|
+
# @return [Bucket] the requested bucket
|
117
|
+
def bucket(name, options={})
|
118
|
+
options.assert_valid_keys(:keys, :props)
|
119
|
+
response = http.get(200, prefix, name, options, {})
|
120
|
+
Bucket.new(self, name).load(response)
|
121
|
+
end
|
122
|
+
alias :[] :bucket
|
123
|
+
|
124
|
+
# @return [String] A representation suitable for IRB and debugging output.
|
125
|
+
def inspect
|
126
|
+
"#<Riak::Client #{http.root_uri.to_s}>"
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def make_client_id
|
131
|
+
b64encode(rand(MAX_CLIENT_ID))
|
132
|
+
end
|
133
|
+
|
134
|
+
def b64encode(n)
|
135
|
+
Base64.encode64([n].pack("N")).chomp
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|