couchobject 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/History.txt +10 -0
  2. data/Manifest.txt +30 -6
  3. data/README.txt +580 -42
  4. data/TODO +2 -2
  5. data/config/hoe.rb +1 -1
  6. data/lib/couch_object.rb +7 -2
  7. data/lib/couch_object/database.rb +19 -34
  8. data/lib/couch_object/document.rb +13 -6
  9. data/lib/couch_object/error_classes.rb +110 -0
  10. data/lib/couch_object/persistable.rb +954 -36
  11. data/lib/couch_object/persistable/has_many_relations_array.rb +91 -0
  12. data/lib/couch_object/persistable/meta_classes.rb +568 -0
  13. data/lib/couch_object/persistable/overloaded_methods.rb +209 -0
  14. data/lib/couch_object/server.rb +1 -1
  15. data/lib/couch_object/utils.rb +44 -0
  16. data/lib/couch_object/version.rb +1 -1
  17. data/lib/couch_object/view.rb +129 -6
  18. data/script/console +0 -0
  19. data/script/destroy +0 -0
  20. data/script/generate +0 -0
  21. data/script/txt2html +0 -0
  22. data/spec/database_spec.rb +23 -31
  23. data/spec/database_spec.rb.orig +173 -0
  24. data/spec/document_spec.rb +21 -3
  25. data/spec/integration/database_integration_spec.rb +46 -15
  26. data/spec/integration/integration_helper.rb +3 -3
  27. data/spec/persistable/callback.rb +44 -0
  28. data/spec/persistable/callback_spec.rb +44 -0
  29. data/spec/persistable/cloning.rb +77 -0
  30. data/spec/persistable/cloning_spec.rb +77 -0
  31. data/spec/persistable/comparing_objects.rb +350 -0
  32. data/spec/persistable/comparing_objects_spec.rb +350 -0
  33. data/spec/persistable/deleting.rb +113 -0
  34. data/spec/persistable/deleting_spec.rb +113 -0
  35. data/spec/persistable/error_messages.rb +32 -0
  36. data/spec/persistable/error_messages_spec.rb +32 -0
  37. data/spec/persistable/loading.rb +339 -0
  38. data/spec/persistable/loading_spec.rb +339 -0
  39. data/spec/persistable/new_methods.rb +70 -0
  40. data/spec/persistable/new_methods_spec.rb +70 -0
  41. data/spec/persistable/persistable_helper.rb +194 -0
  42. data/spec/persistable/relations.rb +470 -0
  43. data/spec/persistable/relations_spec.rb +470 -0
  44. data/spec/persistable/saving.rb +137 -0
  45. data/spec/persistable/saving_spec.rb +137 -0
  46. data/spec/persistable/setting_storage_location.rb +65 -0
  47. data/spec/persistable/setting_storage_location_spec.rb +65 -0
  48. data/spec/persistable/timestamps.rb +76 -0
  49. data/spec/persistable/timestamps_spec.rb +76 -0
  50. data/spec/persistable/unsaved_changes.rb +211 -0
  51. data/spec/persistable/unsaved_changes_spec.rb +211 -0
  52. data/spec/server_spec.rb +5 -5
  53. data/spec/utils_spec.rb +60 -0
  54. data/spec/view_spec.rb +40 -7
  55. data/website/index.html +22 -7
  56. data/website/index.txt +13 -5
  57. metadata +93 -61
  58. data/bin/couch_ruby_view_requestor +0 -81
  59. data/lib/couch_object/model.rb +0 -5
  60. data/lib/couch_object/proc_condition.rb +0 -14
  61. data/spec/model_spec.rb +0 -5
  62. data/spec/persistable_spec.rb +0 -91
  63. data/spec/proc_condition_spec.rb +0 -26
@@ -1,3 +1,13 @@
1
+ == 0.6.0 2008-05-12
2
+
3
+ * Major enhancements:
4
+ * Major Persistable module improvements thanks to Sebastian Probst Eide;
5
+ * Associations (has_one, has_many)
6
+ * Timestamping
7
+ * Defaults to setting instance variables (if no (de)serialization methods provided)
8
+ * The Ruby view server has been removed from the distribution
9
+ * Many small fixes and updates
10
+
1
11
  == 0.5.0 2007-09-15
2
12
 
3
13
  * 1 major enhancement:
@@ -4,15 +4,16 @@ Manifest.txt
4
4
  README.txt
5
5
  Rakefile
6
6
  TODO
7
- bin/couch_ruby_view_requestor
8
7
  config/hoe.rb
9
8
  config/requirements.rb
10
9
  lib/couch_object.rb
11
10
  lib/couch_object/database.rb
12
11
  lib/couch_object/document.rb
13
- lib/couch_object/model.rb
12
+ lib/couch_object/error_classes.rb
14
13
  lib/couch_object/persistable.rb
15
- lib/couch_object/proc_condition.rb
14
+ lib/couch_object/persistable/has_many_relations_array.rb
15
+ lib/couch_object/persistable/meta_classes.rb
16
+ lib/couch_object/persistable/overloaded_methods.rb
16
17
  lib/couch_object/response.rb
17
18
  lib/couch_object/server.rb
18
19
  lib/couch_object/utils.rb
@@ -25,13 +26,36 @@ script/generate
25
26
  script/txt2html
26
27
  setup.rb
27
28
  spec/database_spec.rb
29
+ spec/database_spec.rb.orig
28
30
  spec/document_spec.rb
29
31
  spec/integration/database_integration_spec.rb
30
32
  spec/integration/document_integration_spec.rb
31
33
  spec/integration/integration_helper.rb
32
- spec/model_spec.rb
33
- spec/persistable_spec.rb
34
- spec/proc_condition_spec.rb
34
+ spec/persistable/callback.rb
35
+ spec/persistable/callback_spec.rb
36
+ spec/persistable/cloning.rb
37
+ spec/persistable/cloning_spec.rb
38
+ spec/persistable/comparing_objects.rb
39
+ spec/persistable/comparing_objects_spec.rb
40
+ spec/persistable/deleting.rb
41
+ spec/persistable/deleting_spec.rb
42
+ spec/persistable/error_messages.rb
43
+ spec/persistable/error_messages_spec.rb
44
+ spec/persistable/loading.rb
45
+ spec/persistable/loading_spec.rb
46
+ spec/persistable/new_methods.rb
47
+ spec/persistable/new_methods_spec.rb
48
+ spec/persistable/persistable_helper.rb
49
+ spec/persistable/relations.rb
50
+ spec/persistable/relations_spec.rb
51
+ spec/persistable/saving.rb
52
+ spec/persistable/saving_spec.rb
53
+ spec/persistable/setting_storage_location.rb
54
+ spec/persistable/setting_storage_location_spec.rb
55
+ spec/persistable/timestamps.rb
56
+ spec/persistable/timestamps_spec.rb
57
+ spec/persistable/unsaved_changes.rb
58
+ spec/persistable/unsaved_changes_spec.rb
35
59
  spec/response_spec.rb
36
60
  spec/rspec_autotest.rb
37
61
  spec/server_spec.rb
data/README.txt CHANGED
@@ -2,26 +2,26 @@
2
2
 
3
3
  CouchObject is a set of classes to help you talk to CouchDb (http://couchdbwiki.com/) with Ruby.
4
4
 
5
- * Author: Johan Sørensen
5
+ * Author: Johan Sørensen, Sebastian Probst Eide
6
6
  * Contact: johan (at) johansorensen DOT com
7
7
  * Home: http://rubyforge.org/projects/couchobject/
8
- * Source (Git): http://repo.or.cz/w/couchobject.git
8
+ * Source (Git): http://gitorious.org/projects/couchobject
9
9
 
10
10
  == Creating, opening and deleting databases
11
11
 
12
12
  CouchObject::Database is the first interaction point to your CouchDb. Creating a CouchDb database:
13
13
 
14
- >> CouchObject::Database.create!("http://localhost:8888", "mydb")
14
+ >> CouchObject::Database.create!("http://localhost:5984", "mydb")
15
15
  => {"ok"=>true}
16
- >> CouchObject::Database.all_databases("http://localhost:8888")
16
+ >> CouchObject::Database.all_databases("http://localhost:5984")
17
17
  => ["couchobject", "couchobject_test", "foo", "mydb"]
18
- >> db = CouchObject::Database.open("http://localhost:8888/mydb")
19
- => #<CouchObject::Database:0x642fa8 @server=#<CouchObject::Server:0x642ef4 @connection=#<Net::HTTP localhost:8888 open=false>, @port=8888, @uri=#<URI::HTTP:0x321612 URL:http://localhost:8888>, @host="localhost">, @uri="http://localhost:8888", @dbname="mydb">
18
+ >> db = CouchObject::Database.open("http://localhost:5984/mydb")
19
+ => #<CouchObject::Database:0x642fa8 @server=#<CouchObject::Server:0x642ef4 @connection=#<Net::HTTP localhost:8888 open=false>, @port=8888, @uri=#<URI::HTTP:0x321612 URL:http://localhost:5984>, @host="localhost">, @uri="http://localhost:5984", @dbname="mydb">
20
20
  >> db.name
21
21
  => "mydb"
22
- >> CouchObject::Database.delete!("http://localhost:8888", "mydb")
22
+ >> CouchObject::Database.delete!("http://localhost:5984", "mydb")
23
23
  => {"ok"=>true}
24
- >> CouchObject::Database.all_databases("http://localhost:8888").include?("mydb")
24
+ >> CouchObject::Database.all_databases("http://localhost:5984").include?("mydb")
25
25
  => false
26
26
 
27
27
  === Interacting with the database
@@ -56,17 +56,7 @@ couch_ruby_view_requestor is a JsServer client for CouchDb, allowing you to quer
56
56
  [{"_rev"=>928806717, "_id"=>"28D568C5992CBD2B4711F57225A19517", "value"=>0},
57
57
  {"_rev"=>-1696868121, "_id"=>"601D858DB2E298EFC4BBA92A11760D1E", "value"=>0},
58
58
  {"_rev"=>-2093091288, "_id"=>"CABCEB3F2C8B70B3FE24A03FF6AB7A1E", "value"=>0}]
59
-
60
- But you can even do it in plain Ruby, as opposed to a string, with Database#filter:
61
-
62
- >> db.filter do |doc|
63
- ?> if doc["foo"] == "bar"
64
- >> return doc
65
- >> end
66
- >> end
67
- => [{"_rev"=>-1696868121, "_id"=>"601D858DB2E298EFC4BBA92A11760D1E", "value"=>{"_id"=>"601D858DB2E298EFC4BBA92A11760D1E", "_rev"=>-1696868121, "foo"=>"bar"}}, {"_rev"=>-2093091288, "_id"=>"CABCEB3F2C8B70B3FE24A03FF6AB7A1E", "value"=>{"_id"=>"CABCEB3F2C8B70B3FE24A03FF6AB7A1E", "_rev"=>-2093091288, "foo"=>"bar"}}]
68
-
69
- It requires that you set JsServer in your couch.ini to the path of the couch_ruby_view_requestor executable
59
+
70
60
 
71
61
  == The Document object
72
62
 
@@ -108,34 +98,582 @@ Since CouchObject::Database#get returns a CouchObject::Response object we can co
108
98
 
109
99
  == CouchObject::Persistable
110
100
 
111
- It all started with this module, it maps ruby objects to CouchDb documents, using two mapping methods. It's highly experimental and may go away n future releases
101
+ It all started with this module, it maps ruby objects to CouchDb documents.
102
+
103
+ require 'rubygems'
104
+ require 'couch_object'
105
+
106
+ class RacingCar
107
+ include CouchObject::Persistable
108
+
109
+ def initialize
110
+ @races_won = 0
111
+ end
112
+
113
+ def won_a_race
114
+ @races_won += 1
115
+ end
116
+
117
+ def lost_a_race
118
+ @races_won -= 1
119
+ end
120
+ end
121
+
122
+
123
+ >> fast_car = RacingCar.new
124
+ => #<RacingCar:0x12b630c @races_won=0>
125
+
126
+ The first time an object is saved to the database, the database uri
127
+ has to be supplied as an argument to the +save+ method:
128
+
129
+ >> fast_car.save("http://localhost:5984/mydb/")
130
+ => {:revision=>"2107049750", :id=>"B1D0576DA2E7846550DCD61DCC8CDAE4"}
131
+
132
+ >> fast_car.won_a_race
133
+ => 1
134
+
135
+ Later the object can be saved without supplying a database uri
136
+
137
+ >> fast_car.save
138
+ => {:revision=>"3335068490", :id=>"B1D0576DA2E7846550DCD61DCC8CDAE4"}
139
+
140
+ We could also use the alias get instead of get_by_id
141
+ >> loading_the_fast_car = RacingCar.get_by_id(db_address, "B1D0576DA2E7846550DCD61DCC8CDAE4")
142
+ => #<RacingCar:0x129fddc @races_won=1, @revision="3335068490", @location="http://localhost:5984/mydb", @id="B1D0576DA2E7846550DCD61DCC8CDAE4">
143
+
144
+ >> loading_the_fast_car.revision == fast_car.revision && loading_the_fast_car.id == fast_car.id
145
+ => true
146
+
147
+
148
+ To omit having to type in the database uri each single time a new object gets saved, it is possible to set the uri to the database in the class itself:
149
+
150
+ require 'rubygems'
151
+ require 'couch_object'
152
+
153
+ class Balloon
154
+ include CouchObject::Persistable
155
+ database 'http://localhost:5984/mydb'
156
+ end
157
+
158
+ >> my_balloon = Balloon.new
159
+ => #<Balloon:0x12b4b74>
160
+
161
+ >> my_balloon.save
162
+ => {:revision=>"1118628227", :id=>"ASD21"}
163
+
164
+ >> other_balloon = Balloon.get_by_id("123")
165
+ => #<Balloon:0x12a6024 @id="123", @location="http://localhost:5984/mydb", @revision="1234">
166
+
167
+
168
+
169
+ If you want more control over the way the object is serialized and deserialized (and currently also if the initializer requires arguments) you have to implement and instance method called +to_couch+, which returns a hash of the values you want stored, and a class method called +from_couch+, which should take an array of attributes, which initializes a new object.
170
+
171
+ require 'rubygems'
172
+ require 'couch_object'
173
+
174
+ class Pizza
175
+ include CouchObject::Persistable
176
+
177
+ def initialize(owner, slices_eaten)
178
+ owner == "Sebastian" ? @my_pizza = true : @my_pizza = false
179
+ @owner_name = owner
180
+ @slices_left = 10 - slices_eaten
181
+ end
182
+
183
+ def my_pizza?
184
+ @my_pizza == true
185
+ end
186
+
187
+ def eat
188
+ @slices_left -= 1
189
+ end
190
+
191
+ def to_couch
192
+ { :owner => @owner_name,
193
+ :slices_left => @slices_left }
194
+ end
195
+
196
+ def self.from_couch(attributes)
197
+ slices_eaten = 10 - attributes["slices_left"]
198
+ owner = attributes["owner"]
199
+ new(owner, slices_eaten)
200
+ end
201
+ end
202
+
203
+
204
+ >> the_pizza = Pizza.new("Hans",0)
205
+ => #<Pizza:0x12b2644 @slices_left=10, @owner_name="Hans", @my_pizza=false>
206
+
207
+ >> the_pizza.my_pizza?
208
+ => false
209
+ Not our pizza... we better eat it fast before the owner comes.
210
+
211
+ >> the_pizza.eat
212
+ >> the_pizza.eat
213
+ >> the_pizza.eat
214
+ >> the_pizza.eat
215
+ >> the_pizza.eat
216
+ >> the_pizza.eat
217
+ >> the_pizza.eat
218
+ >> the_pizza.eat
219
+ >> the_pizza.eat
220
+ => 1
221
+ Let's leave a slice for Hans
222
+
223
+ >> the_pizza.save("http://localhost:5984/mydb")
224
+ => {:revision=>"3412312521", :id=>"88781A212BB4676B48B352B209A4D979"}
225
+
226
+ >> hans_pizza = Pizza.get_by_id("88781A212BB4676B48B352B209A4D979","http://localhost:5984/mydb")
227
+ => #<Pizza:0x12989c4 @id="88781A212BB4676B48B352B209A4D979", @slices_left=1, @location="http://localhost:5984/mydb", @owner_name="Hans", @revision="3412312521", @my_pizza=false>
228
+
229
+ >> hans_pizza.my_pizza?
230
+ => false
231
+ Still not my pizza... damnit
232
+
233
+ >> hans_pizza.eat
234
+ => 0
235
+
236
+ Objects that are loaded from the database automatically know where they are stored and therefore don't need the database address as a parameter for the save method
237
+ >> hans_pizza.save
238
+ => {:revision=>"2414021920", :id=>"88781A212BB4676B48B352B209A4D979"}
239
+
240
+
241
+ You can also have timestamps added automatically to your class using the +add_timestamp_for+ method which takes the values +:on_create+ and +:on_update+.
242
+
243
+ require 'rubygems'
244
+ require 'couch_object'
112
245
 
113
- gem "couchobject"
114
- require "couch_object"
115
- class Bike
116
- include CouchObject::Persistable
246
+ class BankAccount
247
+ include CouchObject::Persistable
248
+ add_timestamp_for :on_create, :on_update
249
+ database 'http://localhost:5984/mydb'
250
+ end
117
251
 
118
- def initialize(wheels)
119
- @wheels = wheels
120
- end
121
- attr_accessor :wheels
252
+ >> my_account = BankAccount.new
253
+ => #<BankAccount:0x12b5b00>
122
254
 
123
- def to_couch
124
- {:wheels => @wheels}
125
- end
255
+ >> my_account.save
256
+ => {:revision=>"3733197239", :id=>"C7A492BB92B1308E646E6536020EAE27"}
126
257
 
127
- def self.from_couch(attributes)
128
- new(attributes["wheels"])
129
- end
130
- end
258
+ >> my_account.created_at
259
+ => Sat Feb 02 17:07:36 -0300 2008
131
260
 
132
- By including the CouchObject::Persistable and defining two methods on our class we specify how we should serialize and deserialize our object to and from a CouchDb:
261
+ >> my_account.updated_at
262
+ => Sat Feb 02 17:07:36 -0300 2008
133
263
 
134
- >> bike_4wd = Bike.new(4)
135
- => #<Bike:0x6a0a68 @wheels=4>
136
- >> bike_4wd.save("http://localhost:8888/couchobject")
137
- => {"_rev"=>1745167971, "_id"=>"6FA2AFB623A93E0E77DEAAF59BB02565", "ok"=>true}
138
- >> bike = Bike.get_by_id("http://localhost:8888/couchobject", bike_4wd.id)
139
- => #<Bike:0x64846c @wheels=4>
264
+ >> my_account.save
265
+ => {:revision=>"486590755", :id=>"C7A492BB92B1308E646E6536020EAE27"}
266
+
267
+ >> my_account.created_at
268
+ => Sat Feb 02 17:07:36 -0300 2008
269
+
270
+ >> my_account.updated_at
271
+ => Sat Feb 02 17:07:53 -0300 2008
272
+
273
+
274
+ === Relations
275
+
276
+ There are two different types of relations available for persistable objects:
277
+
278
+ 1. Sub objects are made a part of the object itself and stored in the same database document, or
279
+ 2. Sub objects are stored to the database as separate documents and only loaded when needed.
280
+ (3. The hackish pro way)
281
+
282
+ 1) If you want to use the first option, you have to make sure each sub class either responds to a +to_json+ method or includes the CouchObject::Persistable module so they can be serialized.
283
+ CouchObject will then take care of serialization and initialization for you.
284
+
285
+ Example:
286
+
287
+ class House
288
+ include CouchObject::Persistable
289
+ database 'http://localhost:5984/mydb'
290
+
291
+ def initialize
292
+ @owner = OwnerPerson.new
293
+ end
294
+ attr_accessor :owner
295
+ end
296
+
297
+ class OwnerPerson
298
+ include CouchObject::Persistable
299
+
300
+ def initialize
301
+ @name = "Unknown owner"
302
+ end
303
+ attr_accessor :name
304
+ end
305
+
306
+ >> my_home = House.new
307
+ >> my_home.owner.name = "Sebastian"
308
+ => "Sebastian"
309
+
310
+ >> my_home.save
311
+ => {:revision=>"519758193", :id=>"3E8E2F7A0AE7DACC7D537ECE5220C6FF"}
312
+
313
+ >> your_home = House.get_by_id("3E8E2F7A0AE7DACC7D537ECE5220C6FF")
314
+ >> your_home.owner.name
315
+ => "Sebastian"
316
+
317
+ Make sure to understand the fact that the OwnerPerson object is stored as part of the House document in the database. The OwnerObject will therefore always be read and initialized when the House is loaded from the database!
318
+ If your application has a BlogEntry class and each entry can potentially have thousands of comments and you sometimes want to load the blog entries without loading all the comments at the same time then option number 2, described under, is a better choice for you!
319
+
320
+ 2) There are two types of relations where the sub object(s) are NOT stored in the same database document: +has_one+ relationships and +has_many+ relationships:
321
+
322
+ ==== has_many / belongs_to, :as
323
+
324
+ In cases where your object has many related sub objects that you might not always need when accessing the object this is the route to go.
325
+ Relations are specified using the +has_many+ and +belongs_to ,:as+ methods.
326
+
327
+ The syntax for relations is as follows:
328
+
329
+ has_many :name_of_relation
330
+ belongs_to :name_of_relation, :as => :name_of_the_has_many_relation
331
+
332
+ To work, a relation has to be defined in both the has_many and belongs_to class!
333
+
334
+ As CouchDB doesn't care what we put in it's documents neither does CouchObject. What you name your relations is therefore completely up to you.
335
+ An example says more than a thousand words:
336
+
337
+ Example:
338
+
339
+ class House
340
+ include CouchObject::Persistable
341
+ database 'http://localhost:5984/mydb'
342
+ has_many :inhabitants
343
+ has_many :has
344
+ end
345
+
346
+ class Inhabitant
347
+ include CouchObject::Persistable
348
+ belongs_to :house, :as => :inhabitants
349
+ end
350
+
351
+ class Book
352
+ include CouchObject::Persistable
353
+ belongs_to :is_in, :as => :has
354
+ end
355
+
356
+ class Bed
357
+ include CouchObject::Persistable
358
+ belongs_to :is_in, :as => :has
359
+ end
360
+
361
+ The class definitions above allow us to write the following code:
362
+
363
+ >> my_home = House.new
364
+ >> me = Inhabitant.new
365
+ >> a_book = Book.new
366
+ >> a_bed = Bed.new
367
+
368
+ >> my_home.inhabitants << me
369
+
370
+ >> me.house
371
+ => #<House:0x126ebd8 @couch_object_inhabitants=[#<Inhabitant:0x126cbd0 @couch_object_house=#<House:0x126ebd8 ...>>]>
372
+
373
+ >> my_home.has << a_bed
374
+ >> my_home.has << a_book
375
+
376
+ >> a_book.is_in
377
+ => #<House:0x126ebd8 @couch_object_has=[#<Bed:0x1268dc8 @couch_object_is_in=#<House:0x126ebd8 ...>>, #<Book:0x126ac90 @couch_object_is_in=#<House:0x126ebd8 ...>>], @couch_object_inhabitants=[#<Inhabitant:0x126cbd0 @couch_object_house=#<House:0x126ebd8 ...>>]>
378
+
379
+ >> a_bed.is_in
380
+ => #<House:0x126ebd8 @couch_object_has=[#<Bed:0x1268dc8 @couch_object_is_in=#<House:0x126ebd8 ...>>, #<Book:0x126ac90 @couch_object_is_in=#<House:0x126ebd8 ...>>], @couch_object_inhabitants=[#<Inhabitant:0x126cbd0 @couch_object_house=#<House:0x126ebd8 ...>>]>
381
+
382
+ Saving the master object also saves the children.
383
+ >> my_home.save
384
+ => {:revision=>"1793606849", :id=>"C65B9837CD572FC66931F12392A3181E"}
385
+
386
+
387
+ >> home_from_db = House.get_by_id("C65B9837CD572FC66931F12392A3181E")
388
+ => #<House:0x1229790 @revision="1793606849", @id="C65B9837CD572FC66931F12392A3181E", @location="http://localhost:5984/mydb">
389
+
390
+ All relations are lazily loaded the first time they are needed.
391
+ >> home_from_db.has
392
+ => [#<Bed:0x1224614 @couch_object_is_in=#<House:0x1229790 @revision="1793606849", @couch_object_has=[...], @id="C65B9837CD572FC66931F12392A3181E", @location="http://localhost:5984/mydb">, @revision="1572453885", @id="37A414CE5440B095C2DF46CEFCBBAF33", @belongs_to_id="C65B9837CD572FC66931F12392A3181E">, #<Book:0x1224358 @couch_object_is_in=#<House:0x1229790 @revision="1793606849", @couch_object_has=[...], @id="C65B9837CD572FC66931F12392A3181E", @location="http://localhost:5984/mydb">, @revision="1604857972", @id="DA67DCB6F4BECE8D8DD9F87F814E8340", @belongs_to_id="C65B9837CD572FC66931F12392A3181E">]
393
+
394
+ >> home_from_db.has.size
395
+ => 2
396
+
397
+ >> home_from_db.inhabitants
398
+ => [#<Inhabitant:0x121f7e0 @couch_object_house=#<House:0x1229790 @revision="1793606849", @couch_object_has=[#<Bed:0x1224614 @couch_object_is_in=#<House:0x1229790 ...>, @revision="1572453885", @id="37A414CE5440B095C2DF46CEFCBBAF33", @belongs_to_id="C65B9837CD572FC66931F12392A3181E">, #<Book:0x1224358 @couch_object_is_in=#<House:0x1229790 ...>, @revision="1604857972", @id="DA67DCB6F4BECE8D8DD9F87F814E8340", @belongs_to_id="C65B9837CD572FC66931F12392A3181E">], @couch_object_inhabitants=[...], @id="C65B9837CD572FC66931F12392A3181E", @location="http://localhost:5984/mydb">, @revision="1062171341", @id="ED5DFCF2E28C2D01AE9D4945434CE587", @belongs_to_id="C65B9837CD572FC66931F12392A3181E">]
399
+
400
+ >> home_from_db.inhabitants.first.house == home_from_db
401
+ => true
402
+
403
+ >> home_from_db == my_home
404
+ => true
405
+
406
+
407
+ ===== Setting and removing relations
408
+
409
+ The following examples use the class definitions used in the example above to show different ways to initiate and end relationships
410
+
411
+ >> my_home = House.new
412
+ >> me = Inhabitant.new
413
+
414
+ The relationship can be set either as
415
+
416
+ >> my_home.inhabitants << me
417
+ or
418
+ >> me.house = my_home
419
+
420
+ and ended the following ways:
421
+
422
+ >> me.house = nil
423
+ or
424
+ >> my_home.inhabitants.remove(me)
425
+
426
+
427
+
428
+ ==== has_one / belongs_to
429
+
430
+ This type of relationship works very much like has_many relationships. The following example shows the difference:
431
+
432
+ Example:
433
+
434
+ class Person
435
+ include CouchObject::Persistable
436
+ database 'http://localhost:5984/mydb'
437
+ has_one :life
438
+ end
439
+
440
+ class Life
441
+ include CouchObject::Persistable
442
+ database 'http://localhost:5984/mydb'
443
+ belongs_to :person, :as => :life
444
+ end
445
+
446
+ >> me = Person.new
447
+ => #<Person:0x1270a3c>
448
+ >> my_life = Life.new
449
+ => #<Life:0x126eac0>
450
+
451
+ >> other_persons_life = Life.new
452
+ => #<Life:0x126c8b0>
453
+
454
+ >> my_life.person == me
455
+ => true
456
+
457
+ Creating the relationship between the "other_persons_life" and "me" removes the connection between "me" and "my_life"
458
+ >> other_persons_life.person = me
459
+ => #<Person:0x1270a3c @couch_object_has_one_life=[#<Life:0x126c8b0 @couch_object_person=#<Person:0x1270a3c ...>>]>
460
+
461
+ >> me.life == other_persons_life
462
+ => true
463
+
464
+ >> me.life == my_life
465
+ => false
466
+
467
+
468
+ 3) In some cases none of the solutions above completely cover your needs. That's when the last option comes to your aid.
469
+ It might be that you need to load a huge number of different types of relations in one swoop, or you want to load objects that aren't strictly related to your class at all.
470
+ By using the class level method +get_from_view+ from within your class you can load an arbitrary number of objects you can then do whatever you please with.
471
+
472
+ The method takes the name of the view as the first parameter and a hash of options as an optional second parameter.
473
+ The options can be any of the querying options supported by CouchDB (http://www.couchdbwiki.com/index.php?title=HTTP_View_API) in addition to the database uri if it hasn't been specified on the class level.
474
+
475
+ Recommended reading:
476
+ http://www.couchdbwiki.com/index.php?title=Views
477
+ http://www.couchdbwiki.com/index.php?title=View_Collation
478
+
479
+
480
+ ==== Stopping relations from being loaded
481
+
482
+ When adding an object to a has_many relationship all the other objects in the relationship are automatically loaded. In cases where you don't want to access the other relations at all and just want to add a new object, you can use the +do_not_load_has_many_relations+ instance method, which tells the object not to load its relations.
483
+ This method call has to be made before the relations are accessed though, or it wont have any effect at all.
484
+
485
+ Example:
486
+
487
+ >> home_from_db = House.get_by_id("C65B9837CD572FC66931F12392A3181E")
488
+ => #<House:0x1229790 @revision="1793606849", @id="C65B9837CD572FC66931F12392A3181E", @location="http://localhost:5984/mydb">
489
+
490
+ Make sure the relations are NOT loaded
491
+ >> home_from_db.do_not_load_has_many_relations
492
+
493
+ Normally all relations are lazily loaded the first time they are needed.
494
+ >> home_from_db.has
495
+ => #<House:0x1229790 @revision="1793606849", @id="C65B9837CD572FC66931F12392A3181E", @location="http://localhost:5984/mydb">
496
+
497
+ >> home_from_db.has.size
498
+ => 0
499
+
500
+ There is also a method called +do_load_has_many_relations+. But please be aware that relations are only loaded the first time the relations are accessed. We therefore get behavior like this when continuing with the code example from above:
501
+
502
+ Reactivate loading of relations
503
+ >> home_from_db.do_load_has_many_relations
504
+
505
+ >> home_from_db.has
506
+ => #<House:0x1229790 @revision="1793606849", @id="C65B9837CD572FC66931F12392A3181E", @location="http://localhost:5984/mydb">
507
+
508
+ >> home_from_db.has.size
509
+ => 0
510
+
511
+ As the relations are only loaded the first time they are accessed they wont be loaded again later although loading of relations has been reactivated.
512
+ This feature is helpful in cases like this though:
513
+
514
+ >> my_home = House.new
515
+ => #<House:0x1263f80>
516
+ >> my_home.inhabitants << Inhabitant.new
517
+ >> my_home.has << Book.new << Bed.new
518
+ >> my_home.save
519
+ => {:id=>"4CDC2355A21DDC4D8887882540A84A14", :revision=>"3659215175"}
520
+
521
+ >> loaded_home = House.get_by_id("4CDC2355A21DDC4D8887882540A84A14")
522
+
523
+ We want to add a book to the "has" relation without loading the already saved book and bed
524
+ >> loaded_home.do_not_load_has_many_relations
525
+ => true
526
+
527
+ >> loaded_home.has << Book.new
528
+
529
+ From here there are two different routes:
530
+
531
+ 1) We do not reactivate loading of has_many relations
532
+
533
+ >> loaded_home.inhabitants.size
534
+ => 0
535
+
536
+ 2) We reactivate loading of has_many relations
537
+
538
+ >> loaded_home.do_load_has_many_relations
539
+ => false
540
+
541
+ >> loaded_home.inhabitants.size
542
+ => 1
543
+
544
+
545
+
546
+ ==== Deleting objects
547
+
548
+ Persistable objects can be deleted from the database using the +delete+ method. Deleting an object that has has_many relations will also delete the relations. Deleting an object that is in a belongs_to relation will on the other hand NOT delete its relation!
549
+
550
+
551
+ ==== Saving relations
552
+
553
+ When saving an object that has has_many relations, all its relations are also saved.
554
+ When saving an object that is in a belongs_to relationship with a new object, the master object is also saved which then again saves all its has_many relations as well! This is because the child object on which the save command was issued needs its parents ID to be validly saved in a relation, which it can't get until the parent has been saved.
555
+ When saving an object that is in a belongs_to relationship with a previously saved object only the child gets saved.
556
+
557
+
558
+ ===== Callbacks.
559
+
560
+ CouchObject::Persistable performs callbacks by calling the following methods before and after save, create, update and delete:
561
+
562
+ +before_save+
563
+ +after_save+
564
+ +before_create+
565
+ +after_create+
566
+ +before_update+
567
+ +after_update+
568
+ +before_delte+
569
+ +after_delete+
570
+
571
+ Example:
572
+
573
+ class WithCallbacks
574
+ include CouchObject::Persistable
575
+ database 'foo'
576
+
577
+ def initialize
578
+ @calls = []
579
+ end
580
+ attr_accessor :calls
581
+
582
+ def before_save
583
+ @calls << "before_save"
584
+ end
585
+ def after_save
586
+ @calls << "after_save"
587
+ end
588
+ def before_create
589
+ @calls << "before_create"
590
+ end
591
+ def after_create
592
+ @calls << "after_create"
593
+ end
594
+ def before_update
595
+ @calls << "before_update"
596
+ end
597
+ def after_update
598
+ @calls << "after_update"
599
+ end
600
+ def before_delete
601
+ @calls << "before_delete"
602
+ end
603
+ def after_delete
604
+ @calls << "after_delete"
605
+ end
606
+ end
607
+
608
+ wc = WithCallbacks.new
609
+ wc.save
610
+ wc.save
611
+ wc.delete
612
+ wc.calls.should == ["before_save",
613
+ "before_create",
614
+ "after_create",
615
+ "after_save",
616
+ "before_save",
617
+ "before_update",
618
+ "after_update",
619
+ "after_save",
620
+ "before_delete",
621
+ "after_delete"]
140
622
 
623
+ === Smart save
624
+
625
+ The way a CouchObject instance acts by default is to save itself and all
626
+ its relatives regardless of if it needs to be saved or not when the +save+
627
+ method is called.
628
+
629
+ When activating smart save each instance contains a copy of its original
630
+ state, and can, based on that, make smart decisions regarding wether or
631
+ not it actually needs to save.
632
+
633
+ Smart save should be used selectively though because keeping a copy of it's
634
+ original state will increase the memory usage drastically for objects that
635
+ contain a lot of data! There is also an overhead while initializing the
636
+ object when the state is created, although that will in most cases be
637
+ rather insignificant. These factors add up quickly though if you are working
638
+ with many objects at the same time.
639
+
640
+ Smart save can either be defined in the class definition:
641
+
642
+ class SmartClass
643
+ include CouchObject::Persistable
644
+ smart_save
645
+ end
646
+
647
+ instance_with_smart_save_activated = SmartClass.get("foo")
648
+
649
+ where all instances will receive the smart saving feature by default,
650
+ or it can be toggled manually:
651
+
652
+ class NotSoSmartClass
653
+ include CouchObject::Persistable
654
+ end
655
+
656
+ instance_WITHOUT_smart_save_activated = NotSoSmartClass.get("foo")
657
+
658
+ NotSoSmartClass.smart_save
659
+
660
+ instance_WITH_smart_save_activated = NotSoSmartClass.get("bar")
661
+
662
+ NotSoSmartClass.deactivate_smart_save
663
+
664
+ instance_WITHOUT_smart_save_activated_2 = NotSoSmartClass.get("dong")
665
+
666
+ The same as in the example above can be achieved with the convenience
667
+ method +get_with_smart_save+ which takes the same parameters as +get_by_id+.
668
+ The same example could therefor also be written as:
669
+
670
+ class NotSoSmartClass
671
+ include CouchObject::Persistable
672
+ end
673
+
674
+ instance_WITHOUT_smart_save_activated = NotSoSmartClass.get("foo")
675
+
676
+ instance_WITH_smart_save_activated = NotSoSmartClass.get_with_smart_save("bar")
677
+
678
+ instance_WITHOUT_smart_save_activated_2 = NotSoSmartClass.get("dong")
141
679