couchdb-client 1.0.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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in couchrb.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Gimi Liang
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Couchdb-client
2
+
3
+ 'couchdb-client' is a pure ruby, easy to use CouchDB client library.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'couchdb-client'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install couchdb-client
18
+
19
+ ## Usage
20
+
21
+ require 'couchdb-client'
22
+
23
+ client = CouchDB.connect :host => 'localhost', :port => 5984 # => CouchDB::Client instance
24
+
25
+ db = client['some_database'] # => CouchDB::Database
26
+
27
+ doc = db.put 'key' => 'value'
28
+
29
+ doc = db.get 'xxx' # => CouchDB::Document instance w/ _id 'xxx'
30
+
31
+ doc.update!(:some_field => 'new_value')
32
+ # or
33
+ db.put doc.update('some_field' => 'new_value')
34
+
35
+ doc.delete!
36
+ # or
37
+ db.delete doc._id
38
+
39
+ db.exists? 'xxx'
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/couchdb/client/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Gimi Liang"]
6
+ gem.email = ["liang.gimi@gmail.com"]
7
+ gem.description = %q{A pure Ruby CouchDB client.}
8
+ gem.summary = %q{A pure Ruby CouchDB client.}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "couchdb-client"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = CouchDB::Client::VERSION
17
+
18
+ gem.add_dependency 'json', ['~> 1.7.5']
19
+ gem.add_dependency 'httparty', ['~> 0.8.3']
20
+ end
@@ -0,0 +1,5 @@
1
+ module CouchDB
2
+ class Client
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,77 @@
1
+ module CouchDB
2
+ class Client
3
+ def self.default_options
4
+ {:host => 'localhost', :port => 5984}
5
+ end
6
+
7
+ def initialize(options = {})
8
+ options = self.class.default_options.merge normalize_options(options)
9
+ @connection = establish_connection options
10
+ end
11
+
12
+ def all_dbs
13
+ get '_all_dbs'
14
+ end
15
+
16
+ # Public: Get a Database with the given name.
17
+ def db(name, doc_class = Document)
18
+ DataBase.new self, name, doc_class
19
+ end
20
+
21
+ alias [] db
22
+
23
+ def get(path)
24
+ send_http_request :get, path
25
+ end
26
+
27
+ def put(path, options = {})
28
+ send_http_request :put, path, {:headers => {'Content-Type' => 'application/json'}}.merge!(options)
29
+ end
30
+
31
+ def post(path, options = {})
32
+ send_http_request :post, path, {:headers => {'Content-Type' => 'application/json'}}.merge!(options)
33
+ end
34
+
35
+ def delete(path, options = {})
36
+ send_http_request :delete, path, options
37
+ end
38
+
39
+ def head(path, options = {})
40
+ send_http_request :head, path, options
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :connection
46
+
47
+ def normalize_options(options)
48
+ options.inject({}) { |h, (k, v)| h[k.to_sym] = v; h }
49
+ end
50
+
51
+ def establish_connection(options)
52
+ class << self; self end.tap { |singleton_class|
53
+ singleton_class.send :include, HTTParty
54
+ singleton_class.base_uri base_uri_from_options(options)
55
+ }
56
+ end
57
+
58
+ def base_uri_from_options(options)
59
+ scheme = options[:ssl] ? 'https' : 'http'
60
+ "#{scheme}://#{options[:host]}".tap { |uri|
61
+ uri << ":#{options[:port]}" if options[:port]
62
+ }
63
+ end
64
+
65
+ def send_http_request(verb, path, options = {})
66
+ CouchDB.debug { "[CouchDB] Request: #{verb} /#{path} #{options.inspect}" }
67
+ resp = connection.send(verb, "/#{path}", options)
68
+ CouchDB.debug { "[CouchDB] Response: #{resp.code} #{resp.body.inspect}" }
69
+ if resp.code < 300
70
+ JSON.load resp.body
71
+ else
72
+ raise HTTPError, resp
73
+ end
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,99 @@
1
+ module CouchDB
2
+ class DataBase
3
+ attr_reader :name
4
+
5
+ def initialize(client, name, doc_class = Document)
6
+ raise ArgumentError, "doc_class must be a Document." unless doc_class <= Document
7
+
8
+ @client = client
9
+ @name = name
10
+ @doc_class = doc_class
11
+ end
12
+
13
+ def set_doc_class(doc_class)
14
+ raise ArgumentError, "doc_class must be a Document." unless doc_class <= Document
15
+
16
+ @doc_class = doc_class
17
+ end
18
+
19
+ # Create or update a document.
20
+ def create!
21
+ client.put name
22
+ end
23
+
24
+ def ensure_exist!
25
+ create!
26
+ rescue HTTPError => e
27
+ raise e unless e.code == 419
28
+ end
29
+
30
+ def delete!
31
+ client.delete name
32
+ end
33
+
34
+ def all_docs(options = nil)
35
+ client.get path_for('_all_docs')
36
+ end
37
+
38
+ def new_doc(data)
39
+ doc_class.new self, data
40
+ end
41
+
42
+ # Public: retrieve a document by its _id. Also see `find`.
43
+ def get(_id)
44
+ new_doc client.get(path_for(_id))
45
+ end
46
+
47
+ # Public: retrieve a document by its _id.
48
+ #
49
+ # This method is similar to the `get` method, the only difference is
50
+ # `get` will raise CouchDB::HTTPError when CouchDB returns errors,
51
+ # while `find` will only return nil. Which means `get` gives you
52
+ # a more flexible way to handle errors.
53
+ def find(_id)
54
+ get _id
55
+ rescue HTTPError => e
56
+ nil
57
+ end
58
+
59
+ # Public: put a hash-ish into the database.
60
+ #
61
+ # This method can be used to create and update a document.
62
+ def put(data)
63
+ data = new_doc data
64
+ raise InvalidObject.new(data) unless data.valid?
65
+
66
+ resp =
67
+ if id = data.delete('_id')
68
+ client.put path_for(id), :body => encode(data)
69
+ else
70
+ client.post name, :body => encode(data)
71
+ end
72
+
73
+ data.merge! '_id' => resp['id'], '_rev' => resp['rev']
74
+ end
75
+
76
+ def delete(_id, _rev)
77
+ client.delete path_for(_id), :query => {'rev' => _rev}
78
+ end
79
+
80
+ # Public: is there a document whose id is _id?
81
+ def exist?(_id)
82
+ client.head path_for(_id)
83
+ rescue CouchDB::HTTPError
84
+ raise $! unless $!.code == 404
85
+ end
86
+
87
+ private
88
+
89
+ attr_reader :client, :doc_class
90
+
91
+ def path_for(_id)
92
+ "#{name}/#{_id}"
93
+ end
94
+
95
+ def encode(document)
96
+ JSON.fast_generate document
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,54 @@
1
+ module CouchDB
2
+ class Document < JSONObject
3
+ class << self
4
+ alias fixed_schema! fixed_structure!
5
+ alias fixed_schema? fixed_structure?
6
+ alias dynamic_schema! dynamic_structure!
7
+ alias dynamic_schema? dynamic_structure?
8
+ end
9
+
10
+ property :_id, :string
11
+ property :_rev, :string
12
+
13
+ attr_reader :db
14
+
15
+ def initialize(db, attributes = nil)
16
+ super attributes
17
+ @db = db
18
+ send :after_initialize if respond_to?(:after_initialize)
19
+ end
20
+
21
+ def _rev
22
+ self['_rev']
23
+ end
24
+
25
+ alias rev _rev
26
+
27
+ def _id
28
+ self['_id']
29
+ end
30
+
31
+ alias id _id
32
+
33
+ def new_record?
34
+ _id.nil?
35
+ end
36
+
37
+ def save
38
+ send :before_save if respond_to?(:before_save)
39
+ replace db.put(self)
40
+ end
41
+
42
+ def update!(attributes)
43
+ send :before_update if respond_to?(:before_update)
44
+ update attributes
45
+ save
46
+ end
47
+
48
+ def delete!
49
+ raise InvalidOperation, "Can not delete a document without _id or _rev." unless id and rev
50
+ send :before_delete if respond_to?(:before_delete)
51
+ db.delete id, rev
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,89 @@
1
+ module CouchDB
2
+ class Error < RuntimeError; end
3
+
4
+ class InvalidOperation < Error; end
5
+
6
+ class PropertyError < Error
7
+ attr_reader :name
8
+
9
+ def initialize(name, msg = nil)
10
+ @name = name
11
+ msg ||= "Property error: #{name}"
12
+ super msg
13
+ end
14
+
15
+ def to_hash
16
+ {:property => name, :error => 'error'}
17
+ end
18
+ end
19
+
20
+ class UndefinedProperty < PropertyError
21
+ def initialize(name)
22
+ super name, "Property #{name.inspect} is not defined."
23
+ end
24
+
25
+ def to_hash
26
+ {:property => name, :error => 'undefined'}
27
+ end
28
+ end
29
+
30
+ class MissingProperty < PropertyError
31
+ def initialize(name)
32
+ super name, "Property #{name.inspect} is required, but not given."
33
+ end
34
+
35
+ def to_hash
36
+ {:property => name, :error => 'missing'}
37
+ end
38
+ end
39
+
40
+ class InvalidValue < PropertyError
41
+ attr_accessor :value, :reason
42
+
43
+ def initialize(name, value, reason = nil)
44
+ @value, @reason = value, reason
45
+ super name, "#{value.inspect} is not a valid value for #{name} (#{reason})."
46
+ end
47
+
48
+ def to_hash
49
+ {:property => name, :value => value, :error => 'invalid'}
50
+ end
51
+ end
52
+
53
+ class InvalidObject < Error
54
+ attr_reader :errors
55
+
56
+ def initialize(json_object)
57
+ @errors = json_object.errors.inject({}) { |h, (name, error)|
58
+ h[name] = case error
59
+ when PropertyError
60
+ error.to_hash.tap { |hash| hash.delete :property }
61
+ else
62
+ {:error => 'unknown'}
63
+ end
64
+ h
65
+ }
66
+
67
+ super "#{json_object} is invalid."
68
+ end
69
+
70
+ def to_hash
71
+ @errors
72
+ end
73
+
74
+ def to_json
75
+ JSON.fast_generate @errors
76
+ end
77
+ end
78
+
79
+ class HTTPError < Error
80
+ attr_reader :code, :body
81
+
82
+ def initialize(response)
83
+ @code = response.code
84
+ @body = JSON.parse response.body if response.body
85
+
86
+ super @body ? @body['reason'] : @code
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,188 @@
1
+ module CouchDB
2
+ # Public: The Ruby class that represents the JSON Object.
3
+ # All properties (keys) in a JSONObject are strings.
4
+ # (even strings are not the best hash key in Ruby)
5
+ class JSONObject < Hash
6
+ # Private: The propety definition object.
7
+ class Property
8
+ BuiltinTypes = {
9
+ :string => lambda { |v| v.to_s },
10
+ :int => lambda { |v| Integer(v) },
11
+ :float => lambda { |v| Float(v) },
12
+ :bool => lambda { |v| !!v },
13
+ :array => lambda { |v| Array(v) },
14
+ :hash => lambda { |v| v.to_hash },
15
+ :object => JSONObject
16
+ }
17
+
18
+ attr_reader :name, :default
19
+
20
+ def initialize(name, type, options = {}, &blk)
21
+ @name = name
22
+
23
+ if type.is_a?(Symbol)
24
+ raise ArgumentError, "Unknow property type #{type.inspect}." unless BuiltinTypes.has_key?(type)
25
+ type = BuiltinTypes[type]
26
+ end
27
+ type = Class.new JSONObject, &blk if type == JSONObject
28
+
29
+ @convertor =
30
+ if type.respond_to?(:call)
31
+ type
32
+ elsif convert_method = [:convert, :new].detect { |m| type.respond_to? m }
33
+ type.method convert_method
34
+ else
35
+ raise ArgumentError, "Property type should has :convert, :call or :new method."
36
+ end
37
+
38
+ @required = options[:required]
39
+ @default = options[:default]
40
+ @validator = options[:validate]
41
+ end
42
+
43
+ def required?
44
+ @required
45
+ end
46
+
47
+ def has_default?
48
+ !@default.nil?
49
+ end
50
+
51
+ def convert(value)
52
+ @convertor.call value
53
+ rescue
54
+ raise $!.is_a?(InvalidValue) ? $! : InvalidValue.new(name, value, $!.message)
55
+ end
56
+
57
+ def valid_value?(value)
58
+ @validator.nil? or value.nil? or @validator.call(value)
59
+ end
60
+ end # Property
61
+
62
+ # Public: Make this object become a fixed struture object, which means
63
+ # all properties of this object have to be declared (using the
64
+ # `property` method) before being used.
65
+ def self.fixed_structure!
66
+ @fixed_structure = true
67
+ end
68
+
69
+ # Public: Make this object a dynamic struture object, which means
70
+ # its properties are dynamic (just like a Hash).
71
+ def self.dynamic_structure!
72
+ @fixed_structure = false
73
+ end
74
+
75
+ def self.fixed_structure?
76
+ @fixed_structure
77
+ end
78
+
79
+ def self.dynamic_structure?
80
+ not fixed_structure?
81
+ end
82
+
83
+ # Public: Properties will inherit from the parent class.
84
+ def self.properties
85
+ @properties ||= {}.tap { |h| h.merge! superclass.properties if superclass < JSONObject }
86
+ end
87
+
88
+ def self.property(name, type = :string, options = {}, &blk)
89
+ name = name.to_s
90
+ properties[name] = Property.new(name, type, options, &blk)
91
+ end
92
+
93
+ # Public: lookup a property definition by its name.
94
+ def self.lookup(property_name)
95
+ properties[property_name.to_s]
96
+ end
97
+
98
+ attr_reader :errors
99
+
100
+ def initialize(data = nil)
101
+ replace data if data
102
+ set_defaults
103
+ @errors = {}
104
+ end
105
+
106
+ def []=(name, value)
107
+ super name.to_s, convert_value(name, value)
108
+ end
109
+
110
+ def store(name, value)
111
+ super name.to_s, convert_value(name, value)
112
+ end
113
+
114
+ def update(data)
115
+ super convert_hash(data)
116
+ end
117
+
118
+ def merge!(data)
119
+ super convert_hash(data)
120
+ end
121
+
122
+ def merge(data)
123
+ super convert_hash(data)
124
+ end
125
+
126
+ def replace(data)
127
+ super convert_hash(data)
128
+ end
129
+
130
+ def valid?
131
+ validate!
132
+ errors.empty?
133
+ end
134
+
135
+ def validate!
136
+ errors.clear
137
+
138
+ properties.each { |k, property|
139
+ if property.required? and self[k].nil?
140
+ errors[k] = MissingProperty.new(k)
141
+ next
142
+ end
143
+
144
+ if not property.valid_value?(self[k])
145
+ errors[k] = InvalidValue.new(k, self[k])
146
+ end
147
+ }
148
+ end
149
+
150
+ def inspect
151
+ "#{self.class.name}#{super}"
152
+ end
153
+
154
+ private
155
+
156
+ def properties
157
+ self.class.properties
158
+ end
159
+
160
+ def fixed_structure?
161
+ self.class.fixed_structure?
162
+ end
163
+
164
+ def dynamic_structure?
165
+ self.class.dynamic_structure?
166
+ end
167
+
168
+ def convert_value(property_name, value)
169
+ unless value.nil?
170
+ property = self.class.lookup property_name
171
+ raise UndefinedProperty.new(property_name) if fixed_structure? && property.nil?
172
+ value = property.convert(value) if property
173
+ end
174
+
175
+ value
176
+ end
177
+
178
+ def convert_hash(hash)
179
+ Hash[hash.map { |k, v| [k.to_s, convert_value(k, v)] }]
180
+ end
181
+
182
+ def set_defaults
183
+ properties.values.each { |property|
184
+ self[property.name] = property.default if self[property.name].nil? and property.has_default?
185
+ }
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,111 @@
1
+ module CouchDB
2
+ class Model
3
+ # call-seq
4
+ # establish_connection options
5
+ #
6
+ # call-seq
7
+ # establish_connection client
8
+ def self.establish_connection(options_or_client)
9
+ @connection = options_or_client
10
+ @connection = Client.new connection unless connection.is_a?(Client)
11
+ end
12
+
13
+ def self.connection
14
+ @connection || superclass <= Model && superclass.connection || nil
15
+ end
16
+
17
+ def self.db
18
+ @db ||= connection && connection[db_name, doc_class]
19
+ end
20
+
21
+ def self.set_db_name(name)
22
+ @db_name = name
23
+ end
24
+
25
+ def self.db_name
26
+ @db_name || superclass <= Model && superclass.db_name || nil
27
+ end
28
+
29
+ def self.set_doc_class(doc_class)
30
+ raise ArgumentError, "Not a Document." unless doc_class <= Document
31
+ return if @doc_class == doc_class
32
+ @doc_class = doc_class
33
+ @db = nil
34
+ end
35
+
36
+ def self.doc_class
37
+ @doc_class || superclass <= Model && superclass.doc_class || Document
38
+ end
39
+
40
+ def self.find(id)
41
+ doc = db.find(id)
42
+ new doc if doc
43
+ end
44
+
45
+ attr_reader :doc
46
+
47
+ def initialize(attributes = nil)
48
+ @doc = self.class.db.new_doc attributes
49
+ end
50
+
51
+ def [](attribute)
52
+ @doc[attribute]
53
+ end
54
+
55
+ def []=(attribute, value)
56
+ @doc[attribute] = value
57
+ end
58
+
59
+ def read(chained_keys, splitter = '.')
60
+ chained_keys.split(splitter).inject(@doc) { |value, key|
61
+ value = value.respond_to?(key) ? value.send(key) : value[key]
62
+ break if value.nil?
63
+ value
64
+ }
65
+ end
66
+
67
+ def _id
68
+ @doc._id
69
+ end
70
+
71
+ alias id _id
72
+
73
+ def _rev
74
+ @doc._rev
75
+ end
76
+
77
+ alias rev _rev
78
+
79
+ def new_record?
80
+ @doc.new_record?
81
+ end
82
+
83
+ def update(attributes)
84
+ @doc.update! attributes
85
+ end
86
+
87
+ def save
88
+ @doc.save
89
+ end
90
+
91
+ def delete
92
+ @doc.delete!
93
+ freeze
94
+ end
95
+
96
+ def to_hash
97
+ @doc.to_hash
98
+ end
99
+
100
+ def to_json
101
+ JSON.fast_generate @doc
102
+ end
103
+
104
+ private
105
+
106
+ def freeze
107
+ super
108
+ @doc.freeze
109
+ end
110
+ end
111
+ end
@@ -0,0 +1 @@
1
+ require 'couchdb'
data/lib/couchdb.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'json'
2
+ require 'httparty'
3
+
4
+ module CouchDB
5
+ require 'couchdb/errors'
6
+
7
+ require 'couchdb/client'
8
+ require 'couchdb/database'
9
+ require 'couchdb/json_object'
10
+ require 'couchdb/document'
11
+
12
+ require 'couchdb/model'
13
+
14
+ class << self
15
+ # Public: A sugar method for creating a Client instance.
16
+ def connect(options = {})
17
+ Client.new options
18
+ end
19
+
20
+ def logger
21
+ @logger ||= begin
22
+ require 'logger'
23
+ Logger.new($stdout).tap { |logger| logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO }
24
+ end
25
+ end
26
+
27
+ def logger=(logger)
28
+ @logger = logger
29
+ end
30
+
31
+ def debug
32
+ logger.debug yield if logger.debug?
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'test_helper'
4
+
5
+ class ClientTest < CouchDB::TestCase
6
+ def test_get_all_dbs
7
+ assert_includes client.all_dbs, 'couchdb-client_test_fixtures'
8
+ end
9
+
10
+ def test_get_all_documents
11
+ resp = db.all_docs
12
+
13
+ assert_equal resp['total_rows'], resp['rows'].size
14
+ end
15
+
16
+ def test_create_documents
17
+ doc = db.put :type => 'human', :name => 'Gimi'
18
+
19
+ assert_kind_of CouchDB::Document, doc
20
+ assert doc.id
21
+ assert doc.rev
22
+ end
23
+
24
+ def test_get_documents
25
+ doc = db.put :type => 'human', :name => 'Gimi'
26
+ doc2 = db.get doc.id
27
+
28
+ assert_kind_of CouchDB::Document, doc2
29
+ assert_equal doc.id, doc2.id
30
+ assert_equal doc.rev, doc2.rev
31
+ assert_equal doc.values_at('type', 'name'), doc2.values_at('type', 'name')
32
+ end
33
+
34
+ def test_update_documents
35
+ doc = db.put :type => 'human', :name => 'Gimi'
36
+ rev = doc.rev
37
+ doc.update! :name => 'GimiL'
38
+
39
+ assert_equal 'GimiL', doc['name']
40
+ refute_equal rev, doc.rev
41
+ end
42
+
43
+ def test_delete_documents
44
+ doc = db.put :type => 'human', :name => 'Gimi'
45
+ doc.delete!
46
+ assert_raises CouchDB::HTTPError do
47
+ db.get doc.id
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'test_helper'
4
+
5
+ class DatabaseTest < CouchDB::TestCase
6
+ def test_default_doc_class
7
+ doc = db.put :type => 'person', :name => 'Gimi'
8
+ assert_instance_of CouchDB::Document, doc
9
+ end
10
+
11
+ def test_custom_doc_class
12
+ db = client.db(db_name, TestDoc)
13
+
14
+ assert_raises CouchDB::InvalidObject do
15
+ db.put :key => 'value'
16
+ end
17
+
18
+ doc = db.put :name => 'Gimi'
19
+ assert_instance_of TestDoc, doc
20
+ assert_equal 'person', doc['type']
21
+ end
22
+
23
+ def test_exist
24
+ refute db.exist?('non_exist_id')
25
+ end
26
+
27
+ class TestDoc < CouchDB::Document
28
+ property :type, :string, :default => 'person'
29
+ property :name, :string, :required => true
30
+ end
31
+ end
@@ -0,0 +1,79 @@
1
+ require 'test_helper'
2
+
3
+ class JSONObjectTest < MiniTest::Unit::TestCase
4
+ def test_keys_are_strings
5
+ o = CouchDB::JSONObject.new
6
+ o[:key] = 'some_value'
7
+ assert_equal 'some_value', o['key']
8
+
9
+ o = CouchDB::JSONObject.new :key => 'some_value'
10
+ assert_equal 'some_value', o['key']
11
+ end
12
+
13
+ def test_builtin_type_converter
14
+ skip
15
+ end
16
+
17
+ def test_dynamic_structure
18
+ o = CouchDB::JSONObject.new
19
+ o[:key] = 'some_value'
20
+ assert o.valid?
21
+ end
22
+
23
+ def test_fixed_structure
24
+ o = Class.new(CouchDB::JSONObject) do
25
+ fixed_structure!
26
+
27
+ property :valid_key
28
+ end.new
29
+
30
+ assert o['valid_key'] = 'some_value'
31
+ assert_raises CouchDB::UndefinedProperty do
32
+ o['invalid_key'] = 'some_value'
33
+ end
34
+ end
35
+
36
+ def test_required_property
37
+ o = Class.new(CouchDB::JSONObject) do
38
+ property :required_key, :string, :required => true
39
+ end.new
40
+
41
+ refute o.valid?
42
+ assert_instance_of CouchDB::MissingProperty, o.errors['required_key']
43
+
44
+ o[:required_key] = 'some_value'
45
+ assert o.valid?
46
+ end
47
+
48
+ def test_property_validation
49
+ o = Class.new(CouchDB::JSONObject) do
50
+ property :key, :string, :validate => lambda { |v| %w[hello world].include? v }
51
+ end.new
52
+
53
+ o[:key] = 'hello'
54
+ assert o.valid?
55
+
56
+ o[:key] = 'hell'
57
+ refute o.valid?
58
+ assert_instance_of CouchDB::InvalidValue, o.errors['key']
59
+ end
60
+
61
+ def test_property_inherent
62
+ parent = Class.new CouchDB::JSONObject do
63
+ fixed_structure!
64
+
65
+ property :name, :string, :required => true
66
+ end
67
+
68
+ child = Class.new parent do
69
+ property :parent, :string, :required => true
70
+ end
71
+
72
+ p = parent.new :name => 'Gimi'
73
+ assert p.valid?
74
+
75
+ c = child.new :parent => 'Gimi'
76
+ refute c.valid?
77
+ assert_instance_of CouchDB::MissingProperty, c.errors['name']
78
+ end
79
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ class ModelTest < CouchDB::TestCase
4
+ def setup
5
+ super
6
+ CouchDB::Model.establish_connection client
7
+ end
8
+
9
+ def test_read_method
10
+ model = Class.new(CouchDB::Model) do
11
+ set_doc_class Class.new(CouchDB::Document) {
12
+ property :key_one, :object do
13
+ property :inner_key, :string
14
+ end
15
+
16
+ property :key_two, :object do
17
+ def foo
18
+ 'foo'
19
+ end
20
+ end
21
+ }
22
+ end
23
+
24
+ model.set_db_name db_name
25
+
26
+ sth = model.new :key_one => {:inner_key => 'inner_value'}, :key_two => {}
27
+
28
+ assert_equal 'inner_value', sth.read('key_one.inner_key')
29
+ assert_equal 'foo', sth.read('key_two.foo')
30
+ end
31
+ end
data/test/test_all.rb ADDED
@@ -0,0 +1 @@
1
+ Dir[File.expand_path('../*_test.rb', __FILE__)].each { |test_case| require test_case }
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+
3
+ require 'bundler'
4
+ Bundler.setup
5
+
6
+ require 'couchdb-client'
7
+ require 'minitest/autorun'
8
+
9
+ class CouchDB::TestCase < MiniTest::Unit::TestCase
10
+ attr_reader :client, :db
11
+
12
+ def setup
13
+ super
14
+
15
+ @client = CouchDB.connect
16
+ @db = @client[db_name]
17
+ @db.ensure_exist!
18
+ end
19
+
20
+ def teardown
21
+ db.delete!
22
+ end
23
+
24
+ private
25
+
26
+ def db_name
27
+ @db_name ||= 'couchdb-client_test_fixtures'
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: couchdb-client
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Gimi Liang
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-09-12 00:00:00 +08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: json
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 1
30
+ segments:
31
+ - 1
32
+ - 7
33
+ - 5
34
+ version: 1.7.5
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: httparty
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 57
46
+ segments:
47
+ - 0
48
+ - 8
49
+ - 3
50
+ version: 0.8.3
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ description: A pure Ruby CouchDB client.
54
+ email:
55
+ - liang.gimi@gmail.com
56
+ executables: []
57
+
58
+ extensions: []
59
+
60
+ extra_rdoc_files: []
61
+
62
+ files:
63
+ - .gitignore
64
+ - Gemfile
65
+ - LICENSE
66
+ - README.md
67
+ - Rakefile
68
+ - couchdb-client.gemspec
69
+ - lib/couchdb-client.rb
70
+ - lib/couchdb.rb
71
+ - lib/couchdb/client.rb
72
+ - lib/couchdb/client/version.rb
73
+ - lib/couchdb/database.rb
74
+ - lib/couchdb/document.rb
75
+ - lib/couchdb/errors.rb
76
+ - lib/couchdb/json_object.rb
77
+ - lib/couchdb/model.rb
78
+ - test/client_test.rb
79
+ - test/database_test.rb
80
+ - test/json_object_test.rb
81
+ - test/model_test.rb
82
+ - test/test_all.rb
83
+ - test/test_helper.rb
84
+ has_rdoc: true
85
+ homepage: ""
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options: []
90
+
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.3.9.5
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: A pure Ruby CouchDB client.
118
+ test_files:
119
+ - test/client_test.rb
120
+ - test/database_test.rb
121
+ - test/json_object_test.rb
122
+ - test/model_test.rb
123
+ - test/test_all.rb
124
+ - test/test_helper.rb