ShyCouch 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,117 @@
1
+ ShyCouch
2
+ --------
3
+
4
+
5
+ Ruby library for CouchDB providing a native objects layer, abstracting the HTTP interface.
6
+
7
+ It's for those who think that ActiveRecord and even Django's lovely ORMs don't get you far enough away from SQL. It's for people who want to make funky websites and self-managed / self-hosted web services, not like, you know, Define Business Requirements And Cardinalities or whatever.
8
+
9
+ Soon it'll have support for native Ruby views on the view server. In the meantime, views are written inline in Ruby and then parsed in JavaScript using ShyRubyJS. ShyRubyJS is not very mature, so for anything complex you should write them as inline JavaScript.
10
+
11
+ If anyone can think of a good semantics to prevent confusion between Couch views and the views in an MVC framework that might use this library, I'd love to hear it.
12
+
13
+ Usage
14
+ -----
15
+
16
+ Create a database object, automatically initializing the database on the CouchDB instance if it doesn't exist
17
+
18
+ ````ruby
19
+ couch_settings = {
20
+ "db"=> {
21
+ "host" => "localhost",
22
+ "port" => 5984,
23
+ "name" => "food-app",
24
+ "user" => "myUsername",
25
+ "password" => "myPassword"
26
+ }
27
+ }
28
+
29
+ @couchdb = ShyCouch::getDB(couch_settings)
30
+ ````
31
+ Create a design document and give it some views.
32
+
33
+ Note: soon there'll be a syntax for having default HTTP query options (e.g. `?include_docs=true`) on particular views.
34
+
35
+ ````ruby
36
+ design = ShyCouch::Data::Design.new :food, {:push_to => @couchdb}
37
+
38
+ view1 = ShyCouch::Data::View :all_recipes do
39
+ map do
40
+ emit(doc._id, null) if doc.kind == "Recipe"
41
+ end
42
+ end
43
+ view2 = ShyCouch::Data::View :recipe_count do
44
+ map do
45
+ emit(doc._id, null) if doc.kind == "Recipe"
46
+ end
47
+ reduce do
48
+ return sum(values)
49
+ end
50
+ end
51
+
52
+ design.add_views [view1, view2]
53
+ design.push!
54
+ ````
55
+ Subclass the CouchDocument class to represent your data types:
56
+
57
+ ````ruby
58
+ class Recipe < ShyCouch::Data::CouchDocument
59
+ needs :name
60
+ needs :ingredients
61
+ needs :difficulty
62
+ suggests :cost
63
+ end
64
+
65
+ ````
66
+ Do whatever application logic, and then push your documents (soon you'll be able to define the default `:push_to` for the whole class):
67
+
68
+ ````ruby
69
+ recipe_data = {
70
+ :name = "tuesday snack"
71
+ :ingredients => "sawdust, apples",
72
+ :difficulty => "not hard enough needs more boss fights",
73
+ :cost => "cheap eh"
74
+ }
75
+ recipe = Recipe.new(:push_to => @couchdb, :data => recipe_data)
76
+ recipe.push!
77
+ ````
78
+ Note the `:needs` and `:suggests` syntax when you define a class. Your documents will always raise an error if you try to push without something that's in `:needs` but you can override `:suggests`:
79
+
80
+ ````ruby
81
+ recipe_data = {
82
+ :name => "soup for guests"
83
+ :ingredients => "king rat, chicken stock"
84
+ :difficult => "extr3m3"
85
+ }
86
+ recipe = Recipe.new(:push_to => @couchdb, :data => recipe_data)
87
+ recipe.push! :ignore_suggests => :cost
88
+ ````
89
+
90
+ Class inheritence maintains document validation:
91
+
92
+ ````ruby
93
+ class FascistRecipe < CouchDocument
94
+ needs :who_is_allowed_to_cook_it
95
+ end
96
+ recipe_data = {
97
+ :who_is_allowed_to_cook_it => "alan"
98
+ }
99
+ recipe = Recipe.new :push_to => @couchdb
100
+ recipe.push!
101
+ >>>ShyCouch::DocumentValidationError: Document Missing required fields: [:name, :ingredients, :difficulty]
102
+ ````
103
+
104
+ You can query your views:
105
+
106
+ ````ruby
107
+ recipes = @couchdb.design(:food).query_view(:all_recipes)
108
+ ````
109
+
110
+ Querying your views will return the raw view results as a hash keyed by whatever the view was keyed by.
111
+
112
+ If you call it like this, though:
113
+
114
+ ````ruby
115
+ recipes = @couchdb.design(:food).query_view(:all_recipes, :include_docs => true)
116
+ ````
117
+ Then you'll get your view results as a `ShyCouch::Data::DocumentCollection` object, where each object is an instance of `ShyCouch::Data::CouchDocument`.
@@ -7,49 +7,41 @@ require_relative '../lib/ShyCouch'
7
7
  # Settings for a database that is set up and working, with an admin user
8
8
  $settings = {
9
9
  "db"=> {
10
- # "host" => "ramponeau.local",
11
- "host" => "localhost",
12
- "port" => 5984,
13
- "name" => "test",
14
- "user" => "cerales",
15
- "password" => "password"
16
- },
10
+ # "host" => "ramponeau.local",
11
+ "host" => "localhost",
12
+ "port" => 5984,
13
+ "name" => "test",
14
+ "user" => "cerales",
15
+ "password" => "password"
16
+ },
17
17
  }
18
18
 
19
19
  class ShyCouchTestHelper < Test::Unit::TestCase
20
20
  # The majority of the tests in this suite need the setup provided in here
21
21
  # And it means I don't forget to delete the database in their teardown functions
22
22
  def setup
23
- valid_settings = $settings
24
- @couchdb = ShyCouch.getDB(valid_settings)
23
+ valid_settings = $settings
24
+ @couchdb = ShyCouch.getDB(valid_settings)
25
25
  end
26
26
  def teardown
27
- @couchdb.delete!
28
- @couchdb = nil
27
+ @couchdb.delete!
28
+ @couchdb = nil
29
29
  end
30
30
 
31
31
  def add_some_documents
32
- 4.times do
33
- recipe = Recipe.new(:push_to => @couchdb)
34
- recipe.push!
35
- end
32
+ 4.times do
33
+ recipe = Recipe.new(:push_to => @couchdb, :data => {:length => 10})
34
+ recipe.push!
35
+ end
36
36
  end
37
37
 
38
38
  class Recipe < ShyCouch::Data::CouchDocument; end
39
39
  end
40
40
 
41
- # test ShyCouch::CouchDBAPI
42
41
  require_relative 'test_couchdb_api'
43
-
44
- # test ShyCouch::Fields
45
- # some of the tests in here are disabled cos they involve attempting to resolve a bad domain name
46
42
  require_relative 'test_fields'
47
- #
48
- # # test ShyCouch::Data::CouchDocument
49
43
  require_relative 'test_couch_document'
50
- #
51
44
  require_relative 'test_couchdb_factory'
52
- #
53
45
  require_relative 'test_design_documents'
54
- #
55
- require_relative 'test_views'
46
+ require_relative 'test_document_validation'
47
+ require_relative 'test_views'
@@ -3,6 +3,17 @@ require_relative '../lib/ShyCouch.rb'
3
3
 
4
4
  class CouchDocumentTests
5
5
 
6
+ class ShyCouchDocumentTestHelper < Test::Unit::TestCase
7
+ def setup
8
+ valid_settings = $settings
9
+ @couchdb = ShyCouch.getDB(valid_settings)
10
+ end
11
+ def teardown
12
+ @couchdb.delete!
13
+ @couchdb = nil
14
+ end
15
+ end
16
+
6
17
  class TestDocumentCreation < Test::Unit::TestCase
7
18
  def setup
8
19
  valid_settings = $settings
@@ -45,8 +56,39 @@ class CouchDocumentTests
45
56
 
46
57
  end
47
58
 
48
- class TestDocumentPulling < Test::Unit::TestCase
49
- #TODO
59
+
60
+ class TestDocumentDeletion < ShyCouchDocumentTestHelper
61
+ def setup
62
+ super
63
+ # make some documents, store the ids
64
+ @documents = []
65
+ @collection = ShyCouch::Data::CouchDocumentCollection.new :push_to => @couchdb
66
+ @collection << ShyCouch::Data::CouchDocument.new
67
+ @collection << ShyCouch::Data::CouchDocument.new
68
+ @collection << ShyCouch::Data::CouchDocument.new
69
+ @collection.push_all!
70
+ end
71
+
72
+ def test_delete
73
+ assert_nothing_raised ShyCouch::ShyCouchError do
74
+ @collection.each do |document|
75
+ document.delete! :from => @couchdb
76
+ end
77
+ end
78
+ end
79
+
80
+ def test_try_to_pull_after_delete
81
+ @collection.each do |document|
82
+ document.delete! :from => @couchdb
83
+ end
84
+ @collection.each do |document|
85
+ assert_raises ShyCouch::ResourceNotFound do
86
+ @couchdb.pull_document document
87
+ end
88
+ end
89
+ end
90
+
91
+ def teardown; super; end
50
92
  end
51
93
 
52
94
  class TestDocumentPushing < Test::Unit::TestCase
@@ -69,7 +111,7 @@ class CouchDocumentTests
69
111
  ShyCouch::Data::CouchDocument.new(:data => {"whatever"=>"yep"}),
70
112
  ShyCouch::Data::CouchDocument.new(:data => {"is_a_document"=>true, "number_of_docs_this_is"=>1})
71
113
  ].each { |doc|
72
- doc.push!($couchdb)
114
+ doc.push!(:push_to => $couchdb)
73
115
  }
74
116
  @invalid_documents = nil # make sure user can't set rev maybe? or is that legal?
75
117
  end
@@ -90,7 +132,7 @@ class CouchDocumentTests
90
132
  def test_push_new_documents
91
133
  @valid_documents.each { |doc|
92
134
  # put the document on the server, grab the server's response
93
- res = doc.push!($couchdb)
135
+ res = doc.push!(:push_to => $couchdb)
94
136
  # check that the server included "ok"=>true in its response
95
137
  assert(res["ok"])
96
138
  # check that the doc now has an id and a rev
@@ -115,7 +157,7 @@ class CouchDocumentTests
115
157
  doc.buttonCount = 5
116
158
  doc.friends = ["alan", "alex", "all me other mates"]
117
159
 
118
- res = doc.push!($couchdb)
160
+ res = doc.push!(:push_to => $couchdb)
119
161
  assert(res["ok"])
120
162
 
121
163
  # pull it from the database again
@@ -132,7 +174,7 @@ class CouchDocumentTests
132
174
  @existing_valid_documents.each { |doc|
133
175
  doc._rev = "hurr"
134
176
  assert_raise RestClient::BadRequest do
135
- res = doc.push!($couchdb)
177
+ res = doc.push!(:push_to => $couchdb)
136
178
  end
137
179
  }
138
180
  end
@@ -140,6 +182,117 @@ class CouchDocumentTests
140
182
  end
141
183
  end
142
184
 
185
+ # Define a couple of classes with different push_to values for the next test
186
+ class Hand < ShyCouch::Data::CouchDocument
187
+ push_to ShyCouch::getDB(
188
+ "db"=> {
189
+ "host" => "localhost",
190
+ "port" => 5984,
191
+ "name" => "test1",
192
+ "user" => "cerales",
193
+ "password" => "password"
194
+ }
195
+ )
196
+ end
197
+ class Foot < ShyCouch::Data::CouchDocument
198
+ push_to ShyCouch::getDB(
199
+ "db"=> {
200
+ "host" => "localhost",
201
+ "port" => 5984,
202
+ "name" => "test2",
203
+ "user" => "cerales",
204
+ "password" => "password"
205
+ }
206
+ )
207
+ end
208
+
209
+ class TestCouchDocumentPushingDefinedOnClass < Test::Unit::TestCase
210
+ # Set up two different databases
211
+ # The class definitions here are implicitly a test, too
212
+ # TODO - find a way to test this nicely
213
+ def setup
214
+ Hand.target_db.init!
215
+ Foot.target_db.init!
216
+ @hand = Hand.new
217
+ @hand.push!
218
+ @foot = Foot.new
219
+ @foot.push!
220
+ end
221
+
222
+ def test_pushing_documents_with_different_class_push_to
223
+ # create an instance of both kinds of document and push them
224
+ assert_nothing_raised ShyCouch::ShyCouchError do
225
+ hand = Hand.new
226
+ hand.push!
227
+ end
228
+ assert_nothing_raised ShyCouch::ShyCouchError do
229
+ foot = Foot.new
230
+ foot.push!
231
+ end
232
+ end
233
+
234
+ def test_pulling_documents_with_different_class_push_to
235
+ # Create a couple of documents
236
+ # Test it on the doc instance
237
+ assert_nothing_raised ShyCouch::ShyCouchError do
238
+ @hand.pull!
239
+ end
240
+ assert_nothing_raised ShyCouch::ShyCouchError do
241
+ @foot.pull!
242
+ end
243
+
244
+ # Test it on the database objects
245
+ handDB = Hand.target_db
246
+ footDB = Foot.target_db
247
+ assert_nothing_raised ShyCouch::ShyCouchError do
248
+ hand2 = handDB.pull_document @hand
249
+ assert_equal @hand, hand2
250
+ end
251
+ assert_nothing_raised ShyCouch::ShyCouchError do
252
+ foot2 = footDB.pull_document @foot
253
+ assert_equal @foot, foot2
254
+ end
255
+
256
+ end
257
+
258
+ def test_class_push_and_pull_overriden_by_method_argument
259
+ # push some documents, try to pull them from a db where they don't exist
260
+ hand = Hand.new
261
+ foot = Foot.new
262
+ hand.push!
263
+ foot.push!
264
+
265
+ assert_raises ShyCouch::ResourceNotFound do
266
+ hand.pull! :pull_from => Foot.target_db
267
+ foot.pull! :pull_from => Hand.target_db
268
+ end
269
+ assert_nothing_raised ShyCouch::ResourceNotFound do
270
+ # verify that they can be pulled from their default db
271
+ hand.pull!
272
+ foot.pull!
273
+ end
274
+
275
+ # now test that they can be successfully pulled from other db
276
+ hand.push! :push_to => Foot.target_db
277
+ foot.push! :push_to => Hand.target_db
278
+
279
+ assert_nothing_raised ShyCouch::ResourceNotFound do
280
+ hand.pull! :pull_from => Foot.target_db
281
+ foot.pull! :pull_from => Foot.target_db
282
+ end
283
+ end
284
+
285
+ def test_pushing_documents_in_collection
286
+ # TODO
287
+ end
288
+
289
+ def teardown
290
+ super
291
+ Hand.target_db.delete!
292
+ Foot.target_db.delete!
293
+ end
294
+ end
295
+
143
296
  class CouchDocumentCollectionPushingAndPulling < ShyCouchTestHelper
144
297
 
145
298
  def setup
@@ -163,6 +316,17 @@ class CouchDocumentCollectionPushingAndPulling < ShyCouchTestHelper
163
316
  assert_equal(item, collection2[i])
164
317
  end
165
318
  end
319
+
320
+ def test_pull_doc_without_id
321
+ document = ShyCouch::Data::CouchDocument.new
322
+ assert_raises ShyCouch::DocumentValidationError do
323
+ document.pull!
324
+ end
325
+ assert_raises ShyCouch::DocumentValidationError do
326
+ document = document.pull
327
+ end
328
+
329
+ end
166
330
 
167
331
  def test_pull_all_docs_forcefully
168
332
  # tests pulling w/ exclamation yo
@@ -238,4 +402,4 @@ end
238
402
  # super
239
403
  # end
240
404
  #
241
- # end
405
+ # end
@@ -3,16 +3,55 @@ require_relative '../lib/ShyCouch.rb'
3
3
 
4
4
  class TestCouchDBAPI < Test::Unit::TestCase
5
5
  def setup
6
- valid_settings = $settings
7
- $database = ShyCouch.getDB(valid_settings)
6
+ valid_settings = $settings
7
+ $database = ShyCouch.getDB(valid_settings)
8
8
  end
9
9
 
10
10
  def teardown
11
- $database.delete!
12
- $database = nil
11
+ $database.delete!
12
+ $database = nil
13
13
  end
14
14
 
15
15
  def test_connection
16
- assert_equal(true, $database.connect["ok"])
16
+ assert_equal(true, $database.connect["ok"])
17
17
  end
18
- end
18
+
19
+ def test_invalid_settings
20
+ invalid_settings = []
21
+ invalid_settings << {
22
+ "db"=> {
23
+ # Has "database" instead of "name"
24
+ "host" => "localhost",
25
+ "port" => 5984,
26
+ "database" => "test",
27
+ "user" => "cerales",
28
+ "password" => "password"
29
+ }
30
+ }
31
+ invalid_settings << {
32
+ "db" => {
33
+ # Missing port
34
+ "host" => "localhost",
35
+ "name" => "test"
36
+ }
37
+ }
38
+ invalid_settings << {
39
+ "db" => {
40
+ # Has username but no password
41
+ "host" => "localhost",
42
+ "port" => 5984,
43
+ "database" => "test",
44
+ "user" => "cerales"
45
+ }
46
+ }
47
+ # should throw an error when the settings hash is invalid
48
+ invalid_settings.each do |settings|
49
+ assert_raises ShyCouch::DatabaseError do
50
+ ShyCouch::CouchDatabase.new settings
51
+ end
52
+ assert_raises ArgumentError do
53
+ ShyCouch::CouchDatabase.new
54
+ end
55
+ end
56
+ end
57
+ end