ripple 0.5.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/.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
|