ripple 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/.document +5 -0
  2. data/.gitignore +26 -0
  3. data/LICENSE +13 -0
  4. data/README.textile +126 -0
  5. data/RELEASE_NOTES.textile +24 -0
  6. data/Rakefile +61 -0
  7. data/VERSION +1 -0
  8. data/lib/riak.rb +45 -0
  9. data/lib/riak/bucket.rb +105 -0
  10. data/lib/riak/client.rb +138 -0
  11. data/lib/riak/client/curb_backend.rb +63 -0
  12. data/lib/riak/client/http_backend.rb +209 -0
  13. data/lib/riak/client/net_http_backend.rb +49 -0
  14. data/lib/riak/failed_request.rb +37 -0
  15. data/lib/riak/i18n.rb +15 -0
  16. data/lib/riak/invalid_response.rb +25 -0
  17. data/lib/riak/link.rb +54 -0
  18. data/lib/riak/locale/en.yml +37 -0
  19. data/lib/riak/map_reduce.rb +240 -0
  20. data/lib/riak/map_reduce_error.rb +20 -0
  21. data/lib/riak/robject.rb +234 -0
  22. data/lib/riak/util/headers.rb +44 -0
  23. data/lib/riak/util/multipart.rb +52 -0
  24. data/lib/riak/util/translation.rb +29 -0
  25. data/lib/riak/walk_spec.rb +113 -0
  26. data/lib/ripple.rb +48 -0
  27. data/lib/ripple/core_ext/casting.rb +96 -0
  28. data/lib/ripple/document.rb +60 -0
  29. data/lib/ripple/document/attribute_methods.rb +111 -0
  30. data/lib/ripple/document/attribute_methods/dirty.rb +52 -0
  31. data/lib/ripple/document/attribute_methods/query.rb +49 -0
  32. data/lib/ripple/document/attribute_methods/read.rb +38 -0
  33. data/lib/ripple/document/attribute_methods/write.rb +36 -0
  34. data/lib/ripple/document/bucket_access.rb +38 -0
  35. data/lib/ripple/document/finders.rb +84 -0
  36. data/lib/ripple/document/persistence.rb +93 -0
  37. data/lib/ripple/document/persistence/callbacks.rb +48 -0
  38. data/lib/ripple/document/properties.rb +85 -0
  39. data/lib/ripple/document/validations.rb +44 -0
  40. data/lib/ripple/embedded_document.rb +38 -0
  41. data/lib/ripple/embedded_document/persistence.rb +46 -0
  42. data/lib/ripple/i18n.rb +15 -0
  43. data/lib/ripple/locale/en.yml +16 -0
  44. data/lib/ripple/property_type_mismatch.rb +23 -0
  45. data/lib/ripple/translation.rb +24 -0
  46. data/ripple.gemspec +159 -0
  47. data/spec/fixtures/cat.jpg +0 -0
  48. data/spec/fixtures/multipart-blank.txt +7 -0
  49. data/spec/fixtures/multipart-with-body.txt +16 -0
  50. data/spec/riak/bucket_spec.rb +141 -0
  51. data/spec/riak/client_spec.rb +169 -0
  52. data/spec/riak/curb_backend_spec.rb +50 -0
  53. data/spec/riak/headers_spec.rb +34 -0
  54. data/spec/riak/http_backend_spec.rb +136 -0
  55. data/spec/riak/link_spec.rb +50 -0
  56. data/spec/riak/map_reduce_spec.rb +347 -0
  57. data/spec/riak/multipart_spec.rb +36 -0
  58. data/spec/riak/net_http_backend_spec.rb +28 -0
  59. data/spec/riak/object_spec.rb +444 -0
  60. data/spec/riak/walk_spec_spec.rb +208 -0
  61. data/spec/ripple/attribute_methods_spec.rb +149 -0
  62. data/spec/ripple/bucket_access_spec.rb +48 -0
  63. data/spec/ripple/callbacks_spec.rb +86 -0
  64. data/spec/ripple/document_spec.rb +35 -0
  65. data/spec/ripple/embedded_document_spec.rb +52 -0
  66. data/spec/ripple/finders_spec.rb +146 -0
  67. data/spec/ripple/persistence_spec.rb +89 -0
  68. data/spec/ripple/properties_spec.rb +195 -0
  69. data/spec/ripple/ripple_spec.rb +43 -0
  70. data/spec/ripple/validations_spec.rb +64 -0
  71. data/spec/spec.opts +1 -0
  72. data/spec/spec_helper.rb +32 -0
  73. data/spec/support/http_backend_implementation_examples.rb +215 -0
  74. data/spec/support/mock_server.rb +58 -0
  75. metadata +221 -0
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -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.
@@ -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 &copy;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 &copy;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
@@ -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
@@ -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
@@ -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
@@ -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