couchdb-client 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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