couch-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1 @@
1
+ v0.0.1. first release
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010 Robert Sosinski (http://www.robertsosinski.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,33 @@
1
+ CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ README.markdown
5
+ Rakefile
6
+ TODO
7
+ lib/couch-client.rb
8
+ lib/couch-client/attachment.rb
9
+ lib/couch-client/attachment_list.rb
10
+ lib/couch-client/collection.rb
11
+ lib/couch-client/connection.rb
12
+ lib/couch-client/connection_handler.rb
13
+ lib/couch-client/consistent_hash.rb
14
+ lib/couch-client/database.rb
15
+ lib/couch-client/design.rb
16
+ lib/couch-client/document.rb
17
+ lib/couch-client/hookup.rb
18
+ lib/couch-client/row.rb
19
+ spec/attachment_list_spec.rb
20
+ spec/attachment_spec.rb
21
+ spec/collection_spec.rb
22
+ spec/conection_handler_spec.rb
23
+ spec/connection_spec.rb
24
+ spec/consistent_hash_spec.rb
25
+ spec/couch-client_spec.rb
26
+ spec/database_spec.rb
27
+ spec/design_spec.rb
28
+ spec/document_spec.rb
29
+ spec/files/image.png
30
+ spec/files/plain.txt
31
+ spec/hookup_spec.rb
32
+ spec/row_spec.rb
33
+ spec/spec_helper.rb
data/README.markdown ADDED
@@ -0,0 +1,176 @@
1
+ Introduction
2
+ ============
3
+
4
+ CouchClient is Ruby library that can be used to interact with CouchDB. The goal of CouchClient is to make documents feel as much as possible as what they already represent, a Hash of primitives, Arrays and other Hashes. As such, the interface for documents closely represents that of Hash and Array, but also includes additional methods and state in order to manage documents and interface with the CouchDB Server.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ gem install couch-client
10
+
11
+ # In your ruby application
12
+ require 'couch-client'
13
+
14
+ Connecting to CouchDB
15
+ ---------------------
16
+
17
+ # Using hash syntax
18
+ Couch = CouchClient::Conection.new({
19
+ :scheme => "http",
20
+ :host => "localhost",
21
+ :port => 5984,
22
+ :database => "sandbox",
23
+ :username => "admin",
24
+ :password => "nimda"
25
+ })
26
+
27
+ # Using block syntax
28
+ Couch = CouchClient::Connection.new do |c|
29
+ c.scheme = "http"
30
+ c.host = "localhost"
31
+ c.port = 5984
32
+ c.database = "sandbox"
33
+ c.username = "admin"
34
+ c.password = "nimda"
35
+ end
36
+
37
+ # Using both hash and block syntax
38
+ # NOTE: Hash parameters take precedence over block parameters
39
+ Couch = CouchClient::Connection.new(:username => "admin", :password => "nidma") do |c|
40
+ c.scheme = "http"
41
+ c.host = "localhost"
42
+ c.port = 5984
43
+ c.database = "sandbox"
44
+ end
45
+
46
+ Fetching a Document
47
+ -------------------
48
+
49
+ # Using Hash syntax
50
+ person = Couch["a3b556796203eab59c31fa21b00043e3"]
51
+
52
+ # You can also pass options if desired
53
+ person = Couch["a3b556796203eab59c31fa21b00043e3", :include_docs => true]
54
+
55
+ Getting a Document's id, rev and attachments
56
+ --------------------------------------------
57
+ # A document's id
58
+ person.id # => "a3b556796203eab59c31fa21b00043e3"
59
+
60
+ # A document's rev
61
+ person.rev # => "1-6665e6330ba75e757ce1f6d793305d67"
62
+
63
+ # A document's attachments
64
+ # NOTE: This will be an CouchClient::AttachmentList, and attachments will be CouchClient::Attachment objects
65
+ person.attachments # => {"plain.txt"=>{"content_type"=>"text/plain", "revpos"=>2, "length"=>406, "stub"=>true}}
66
+
67
+
68
+ Working with a Document
69
+ -----------------------
70
+
71
+ # Building new documents
72
+ # Couch.build({:name => "alice"})
73
+
74
+ # Getting and setting fields (with indifferent access and value "stringification")
75
+ # person[:name] # => "alice"
76
+ # person[:city] = :nyc
77
+ # person["city"] # => "nyc"
78
+
79
+ # Fetching the same document on the server
80
+ person.saved_doc # => {"_id" => "7f22af967b04d1b88212d3d26b017ff6", "_rev" => "1-f867d6b9aa0a5c31d647d57110fa7d36", "name" => "alice"}
81
+
82
+ # Saving a document
83
+ person.save # => true
84
+ person.rev # => "2-1734c07abaf18db573706bc1db59e09d"
85
+
86
+ # Deleting a field
87
+ person.delete(:city) # => true
88
+ person[:city] # => nil
89
+
90
+ # Attaching a file (documents must be refreshed for attachments to be available)
91
+ person.attach("plain.txt", "Hello World", "plain/text") # => true
92
+ person = person.saved_doc
93
+ person.attachments # => {"plain.txt"=>{"content_type"=>"text/plain", "revpos"=>2, "length"=>406, "stub"=>true}}
94
+
95
+ # Getting attached files
96
+ attachment = person.attachments["plain.txt"]
97
+ attachment.uri # => http://localhost:5984/sandbox/7f22af967b04d1b88212d3d26b017ff6/plain.txt
98
+ attachment.path # => /sandbox/7f22af967b04d1b88212d3d26b017ff6/plain.txt
99
+ attachment.data # => "Hello World"
100
+
101
+ # Deleting a document
102
+ person.delete!
103
+ person.deleted? # => true
104
+
105
+ # Identifying a design document
106
+ person.design? # => false
107
+ c["_design/people"].design? # => true
108
+
109
+ # Identifying errors and conflicts
110
+ person.error # => {"conflict"=>"Document update conflict."}
111
+ person.error? # => true
112
+ person.conflict? # => true
113
+ person.invalid? # => false
114
+
115
+ Working with Collections
116
+ ------------------------
117
+
118
+ # Getting all documents
119
+ Couch.all_docs # => [{"id"=>"7f22af967b04d1b88212d3d26b017ff6", "key"=>"7f22af967b04d1b88212d3d26b017ff6", "value"=>{"rev"=>"1-f867d6b9aa0a5c31d647d57110fa7d36"}},
120
+ # {"id"=>"7f22af967b04d1b88212d3d26b018e89", "key"=>"7f22af967b04d1b88212d3d26b018e89", "value"=>{"rev"=>"3-3a635c1a2b5a8ff94bb5d63eee3cd6ef"}}]
121
+
122
+ # Getting all documents with document fields
123
+ Couch.all_docs(:include_docs => true)
124
+
125
+ # Specifying a `key`, `start_key` or `end_key`
126
+ couch.all_docs(:key => "7f22af967b04d1b88212d3d26b018e89")
127
+ couch.all_docs(:start_key => 200)
128
+ couch.all_docs(:end_key => [2010, 01, 01])
129
+
130
+ # Getting additional collection information
131
+ Couch.all_docs.info # => {"total_rows" => 2, "offset" => 0}
132
+
133
+ Using Design Documents
134
+ ----------------------
135
+
136
+ # Map Views
137
+ Couch.design(:people).view(:all) # => [{"id"=>"7f22af967b04d1b88212d3d26b017ff6", "key"=>"7f22af967b04d1b88212d3d26b017ff6", "value"=>{"name" => "alice"}},
138
+ # {"id"=>"7f22af967b04d1b88212d3d26b018e89", "key"=>"7f22af967b04d1b88212d3d26b018e89", "value"=>{"name" => "bob"}}]
139
+
140
+ # MapReduce Views
141
+ Couch.design(:people).view(:sum) # => [{"key" => "male", "value" => 1}, {"key" => "female", "value" => 1}]
142
+
143
+ Using FullText Searching (Must Have CouchDB-Lucene Installed)
144
+ -------------------------------------------------------------
145
+
146
+ # Getting search results
147
+ Couch.design(:people).fulltext(:by_name, :q => "alice") # => [{"id"=>"a6c92090bbee241e892be1ac4464b9d9", "score"=>4.505526065826416, "fields"=>{"default"=>"alice"}}]
148
+
149
+ # Getting additional search results information
150
+ Couch.design(:people).fulltext(:by_name, :q => "alice").info # => {"q"=>"default:alice", "etag"=>"11e1541e20d9b860", "skip"=>0, "limit"=>25,
151
+ # "total_rows"=>7, "search_duration"=>0, "fetch_duration"=>1}
152
+
153
+ # Getting search index information
154
+ Couch.design(:people).fulltext(:by_name) # => {"current"=>true, "disk_size"=>3759, "doc_count"=>25, "doc_del_count"=>3, "fields"=>["default"],
155
+ # "last_modified"=>"1288403429000", "optimized"=>false, "ref_count"=>2}
156
+
157
+ Database Administration
158
+ -----------------------
159
+
160
+ # Create a database
161
+ Couch.database.create
162
+
163
+ # See if a database exists
164
+ Couch.database.exists
165
+
166
+ # Get database stats
167
+ Couch.database.stats
168
+
169
+ # Compact the database
170
+ Couch.database.compact!
171
+
172
+ # Delete the database
173
+ Couch.databse.delete!
174
+
175
+
176
+
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rake'
2
+ require 'rspec/core/rake_task'
3
+ require 'echoe'
4
+
5
+ desc "Run all specs"
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.rspec_opts = %w[--colour --format progress]
8
+ end
9
+
10
+ Echoe.new("couch-client") do |p|
11
+ p.author = "Robert Sosinski"
12
+ p.email = "email@robertsosinski.com"
13
+ p.url = "http://github.com/robertsosinski/couch-client"
14
+ p.description = "CouchClient is Ruby library that can be used to interact with CouchDB"
15
+ p.summary = "The goal of CouchClient is to make documents feel as much as possible as what they already represent, a Hash of primitives, Arrays and other Hashes."
16
+ p.runtime_dependencies = ["json"]
17
+ end
data/TODO ADDED
@@ -0,0 +1,11 @@
1
+ Need
2
+ * design save rake task
3
+
4
+ Next
5
+ * batch insert
6
+ * batch update
7
+ * batch delete
8
+ * batch upload
9
+ * show design
10
+ * list design
11
+ * replication
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{couch-client}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Robert Sosinski"]
9
+ s.date = %q{2010-10-29}
10
+ s.description = %q{CouchClient is Ruby library that can be used to interact with CouchDB}
11
+ s.email = %q{email@robertsosinski.com}
12
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.markdown", "TODO", "lib/couch-client.rb", "lib/couch-client/attachment.rb", "lib/couch-client/attachment_list.rb", "lib/couch-client/collection.rb", "lib/couch-client/connection.rb", "lib/couch-client/connection_handler.rb", "lib/couch-client/consistent_hash.rb", "lib/couch-client/database.rb", "lib/couch-client/design.rb", "lib/couch-client/document.rb", "lib/couch-client/hookup.rb", "lib/couch-client/row.rb"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "README.markdown", "Rakefile", "TODO", "lib/couch-client.rb", "lib/couch-client/attachment.rb", "lib/couch-client/attachment_list.rb", "lib/couch-client/collection.rb", "lib/couch-client/connection.rb", "lib/couch-client/connection_handler.rb", "lib/couch-client/consistent_hash.rb", "lib/couch-client/database.rb", "lib/couch-client/design.rb", "lib/couch-client/document.rb", "lib/couch-client/hookup.rb", "lib/couch-client/row.rb", "spec/attachment_list_spec.rb", "spec/attachment_spec.rb", "spec/collection_spec.rb", "spec/conection_handler_spec.rb", "spec/connection_spec.rb", "spec/consistent_hash_spec.rb", "spec/couch-client_spec.rb", "spec/database_spec.rb", "spec/design_spec.rb", "spec/document_spec.rb", "spec/files/image.png", "spec/files/plain.txt", "spec/hookup_spec.rb", "spec/row_spec.rb", "spec/spec_helper.rb", "couch-client.gemspec"]
14
+ s.homepage = %q{http://github.com/robertsosinski/couch-client}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Couch-client", "--main", "README.markdown"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{couch-client}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{The goal of CouchClient is to make documents feel as much as possible as what they already represent, a Hash of primitives, Arrays and other Hashes.}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<json>, [">= 0"])
27
+ else
28
+ s.add_dependency(%q<json>, [">= 0"])
29
+ end
30
+ else
31
+ s.add_dependency(%q<json>, [">= 0"])
32
+ end
33
+ end
@@ -0,0 +1,50 @@
1
+ $:.unshift(File.dirname(File.expand_path(__FILE__)))
2
+
3
+ # require '../core_ext/hash'
4
+
5
+ require 'couch-client/consistent_hash'
6
+ require 'couch-client/connection'
7
+ require 'couch-client/connection_handler'
8
+ require 'couch-client/hookup'
9
+ require 'couch-client/database'
10
+ require 'couch-client/document'
11
+ require 'couch-client/attachment_list'
12
+ require 'couch-client/attachment'
13
+ require 'couch-client/design'
14
+ require 'couch-client/collection'
15
+ require 'couch-client/row'
16
+
17
+ # The CouchClient module is the overall container of all CouchClient logic.
18
+ module CouchClient
19
+ VERSION = "0.0.1"
20
+
21
+ class Error < Exception; end
22
+
23
+ # Start using CouchClient by constructing a new CouchClient::Connection object with a Hash:
24
+ #
25
+ # CouchClient.connect(:database => "db_name")
26
+ #
27
+ # with a block:
28
+ #
29
+ # CouchClient.connect do |c|
30
+ # c.database = "db_name"
31
+ # end
32
+ #
33
+ # or with both:
34
+ #
35
+ # CouchClient.connect(:username => "user", :password => "pass") do |c|
36
+ # c.database = "db_name"
37
+ # end
38
+ #
39
+ # CouchClient.connect takes the following options.
40
+ #
41
+ # scheme: Protocol used (e.g. http or https), default being "http".
42
+ # username: Username used by HTTP Basic authentication.
43
+ # password: Password used by HTTP Basic authentication.
44
+ # host: The domain for your CouchDB erver, default being "localhost".
45
+ # port: The port for your CouchDB server, default being 5984.
46
+ # database: The database you wish to connect to.
47
+ def self.connect(args = {}, &block)
48
+ Connection.new(args, &block)
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ module CouchClient
2
+ # The Attachment is an extended Hash that provides additional methods to
3
+ # interact with attached files saved within a document.
4
+ class Attachment < ConsistentHash
5
+ attr_reader :name
6
+
7
+ # Attachment is constructed the id of the document it is attached to,
8
+ # the filename, file stub and connection object.
9
+ def initialize(id, name, stub, connection)
10
+ self.merge!(stub)
11
+
12
+ @id = id
13
+ @name = name
14
+ @connection = connection
15
+ end
16
+
17
+ # Returns the path for the attachment
18
+ def path
19
+ @connection.hookup.handler.path([@id, @name])
20
+ end
21
+
22
+ # Returns the uri for the attachment
23
+ def uri
24
+ @connection.hookup.handler.uri([@id, @name])
25
+ end
26
+
27
+ # Returns a string that contains file data
28
+ def data
29
+ @connection.hookup.get([@id, @name], nil, self["content_type"]).last
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ module CouchClient
2
+ # The AttachmentList prevents ConsistentHash from absorbing
3
+ # instances of Attachment and making them a ConsistentHash.
4
+ class AttachmentList < ConsistentHash
5
+ # AttachmentList is constructed with a hash of attachments.
6
+ def initialize(attachments)
7
+ self.merge!(attachments)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module CouchClient
2
+ # The Document is an extended Array that provides additional methods
3
+ # and state to get status codes, info and connect documents to the server.
4
+ class Collection < Array
5
+ attr_reader :code, :info
6
+
7
+ # Collection is constructed with a status code, response body,
8
+ # and connection object.
9
+ def initialize(code, body, connection)
10
+ # Iterate over each row to set them a CouchClient::Row object.
11
+ body.delete("rows").each.with_index do |row, idx|
12
+ self[idx] = Row.new(code, row, connection)
13
+ end
14
+
15
+ @code = code
16
+ @info = body
17
+ @connection = connection
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,104 @@
1
+ module CouchClient
2
+ class DatabaseNotGiven < Exception; end
3
+ class DocumentNotValid < Exception; end
4
+ class DocumentNotFound < Exception; end
5
+
6
+ # The Connection is the high-level interface used to interact with the CouchDB Server.
7
+ class Connection
8
+ attr_reader :hookup, :database
9
+
10
+ # Connection is constructed with a Hash or with a block specifying connection parameters.
11
+ # An error will be raised if a database is not specified.
12
+ def initialize(args = {})
13
+ handler = ConnectionHandler.new
14
+
15
+ # Set ConnectionHandler settings via a block
16
+ if block_given?
17
+ yield(handler)
18
+ end
19
+
20
+ # Set remaining ConnectionHandler settings via a Hash
21
+ args.each_pair do |key, value|
22
+ handler.send("#{key}=", value)
23
+ end
24
+
25
+ # Ensure a database is provided
26
+ unless handler.database
27
+ raise DatabaseNotGiven.new("specify a database to connect to")
28
+ end
29
+
30
+ # `@hookup` is used as the HTTP interface and `@database` is a namespace for all
31
+ # database specific commands such as creation, deletion, compaction and replication.
32
+ @hookup = Hookup.new(handler)
33
+ @database = Database.new(self)
34
+ end
35
+
36
+ # Fetches documents from the CouchDB server. Although `[]` makes get requests and therefore
37
+ # could fetch design views and more, anything received that is not a valid document will
38
+ # raise an error. As such, fetching designs can only be done through the `design` method.
39
+ def [](id, options = {})
40
+ code, body = @hookup.get([id], options)
41
+
42
+ case code
43
+ # If something was found
44
+ when 200
45
+ # And that something is a document
46
+ if body["_id"] && body["_rev"]
47
+ # Make a new document object
48
+ Document.new(code, body, self)
49
+ else
50
+ # Else raise an error as `[]` should only return document objects
51
+ raise DocumentNotValid.new("the id '#{id}' does not correspond to a document")
52
+ end
53
+ # If nothing was found
54
+ when 404
55
+ case body["reason"]
56
+ # Because the document was deleted
57
+ when "deleted"
58
+ # Tell the user it was deleted
59
+ raise DocumentNotFound.new("the document with id '#{id}' has been deleted")
60
+ else
61
+ # Else tell the user it was never there to begin with
62
+ raise DocumentNotFound.new("a document could not be found with id '#{id}'")
63
+ end
64
+ # If something else happened
65
+ else
66
+ # Raise an error
67
+ raise Error.new("code: #{code}, error: #{body["error"]}, reason: #{body["reason"]}")
68
+ end
69
+ end
70
+
71
+ # Constructs a new design factory that manages `views`, `shows`, `lists` and `fulltext` searches.
72
+ def design(id)
73
+ Design.new(id, self)
74
+ end
75
+
76
+ # Acts as the interface to CouchDB's `_all_docs` map view.
77
+ def all_docs(options = {})
78
+ # key, startkey and endkey must be JSON encoded
79
+ ["key", "startkey", "endkey"].each do |key|
80
+ options[key] &&= options[key].to_json
81
+ end
82
+
83
+ # Create a new Collection with the response code, body and connection.
84
+ Collection.new(*@hookup.get(["_all_docs"], options), self)
85
+ end
86
+
87
+ # The interface used to construct new CouchDB documents. Once constructed
88
+ # these documents can be saved, updated, validated and deleted.
89
+ def build(body = {})
90
+ Document.new(nil, body, self)
91
+ end
92
+
93
+ def inspect
94
+ head = "#<#{self.class}: "
95
+ body = []
96
+ body << "username: #{@hookup.handler.username}" if @hookup.handler.username
97
+ body << "password: #{@hookup.handler.password}" if @hookup.handler.password
98
+ body << "uri: #{@hookup.handler.uri}"
99
+ tail = ">"
100
+
101
+ head + body.join(", ") + tail
102
+ end
103
+ end
104
+ end