couchobject 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/Manifest.txt +30 -6
- data/README.txt +580 -42
- data/TODO +2 -2
- data/config/hoe.rb +1 -1
- data/lib/couch_object.rb +7 -2
- data/lib/couch_object/database.rb +19 -34
- data/lib/couch_object/document.rb +13 -6
- data/lib/couch_object/error_classes.rb +110 -0
- data/lib/couch_object/persistable.rb +954 -36
- data/lib/couch_object/persistable/has_many_relations_array.rb +91 -0
- data/lib/couch_object/persistable/meta_classes.rb +568 -0
- data/lib/couch_object/persistable/overloaded_methods.rb +209 -0
- data/lib/couch_object/server.rb +1 -1
- data/lib/couch_object/utils.rb +44 -0
- data/lib/couch_object/version.rb +1 -1
- data/lib/couch_object/view.rb +129 -6
- data/script/console +0 -0
- data/script/destroy +0 -0
- data/script/generate +0 -0
- data/script/txt2html +0 -0
- data/spec/database_spec.rb +23 -31
- data/spec/database_spec.rb.orig +173 -0
- data/spec/document_spec.rb +21 -3
- data/spec/integration/database_integration_spec.rb +46 -15
- data/spec/integration/integration_helper.rb +3 -3
- data/spec/persistable/callback.rb +44 -0
- data/spec/persistable/callback_spec.rb +44 -0
- data/spec/persistable/cloning.rb +77 -0
- data/spec/persistable/cloning_spec.rb +77 -0
- data/spec/persistable/comparing_objects.rb +350 -0
- data/spec/persistable/comparing_objects_spec.rb +350 -0
- data/spec/persistable/deleting.rb +113 -0
- data/spec/persistable/deleting_spec.rb +113 -0
- data/spec/persistable/error_messages.rb +32 -0
- data/spec/persistable/error_messages_spec.rb +32 -0
- data/spec/persistable/loading.rb +339 -0
- data/spec/persistable/loading_spec.rb +339 -0
- data/spec/persistable/new_methods.rb +70 -0
- data/spec/persistable/new_methods_spec.rb +70 -0
- data/spec/persistable/persistable_helper.rb +194 -0
- data/spec/persistable/relations.rb +470 -0
- data/spec/persistable/relations_spec.rb +470 -0
- data/spec/persistable/saving.rb +137 -0
- data/spec/persistable/saving_spec.rb +137 -0
- data/spec/persistable/setting_storage_location.rb +65 -0
- data/spec/persistable/setting_storage_location_spec.rb +65 -0
- data/spec/persistable/timestamps.rb +76 -0
- data/spec/persistable/timestamps_spec.rb +76 -0
- data/spec/persistable/unsaved_changes.rb +211 -0
- data/spec/persistable/unsaved_changes_spec.rb +211 -0
- data/spec/server_spec.rb +5 -5
- data/spec/utils_spec.rb +60 -0
- data/spec/view_spec.rb +40 -7
- data/website/index.html +22 -7
- data/website/index.txt +13 -5
- metadata +93 -61
- data/bin/couch_ruby_view_requestor +0 -81
- data/lib/couch_object/model.rb +0 -5
- data/lib/couch_object/proc_condition.rb +0 -14
- data/spec/model_spec.rb +0 -5
- data/spec/persistable_spec.rb +0 -91
- data/spec/proc_condition_spec.rb +0 -26
data/History.txt
CHANGED
@@ -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:
|
data/Manifest.txt
CHANGED
@@ -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/
|
12
|
+
lib/couch_object/error_classes.rb
|
14
13
|
lib/couch_object/persistable.rb
|
15
|
-
lib/couch_object/
|
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/
|
33
|
-
spec/
|
34
|
-
spec/
|
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://
|
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:
|
14
|
+
>> CouchObject::Database.create!("http://localhost:5984", "mydb")
|
15
15
|
=> {"ok"=>true}
|
16
|
-
>> CouchObject::Database.all_databases("http://localhost:
|
16
|
+
>> CouchObject::Database.all_databases("http://localhost:5984")
|
17
17
|
=> ["couchobject", "couchobject_test", "foo", "mydb"]
|
18
|
-
>> db = CouchObject::Database.open("http://localhost:
|
19
|
-
=> #<CouchObject::Database:0x642fa8 @server=#<CouchObject::Server:0x642ef4 @connection=#<Net::HTTP localhost:8888 open=false>, @port=8888, @uri=#<URI::HTTP:0x321612 URL:http://localhost:
|
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:
|
22
|
+
>> CouchObject::Database.delete!("http://localhost:5984", "mydb")
|
23
23
|
=> {"ok"=>true}
|
24
|
-
>> CouchObject::Database.all_databases("http://localhost:
|
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
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
119
|
-
|
120
|
-
end
|
121
|
-
attr_accessor :wheels
|
252
|
+
>> my_account = BankAccount.new
|
253
|
+
=> #<BankAccount:0x12b5b00>
|
122
254
|
|
123
|
-
|
124
|
-
|
125
|
-
end
|
255
|
+
>> my_account.save
|
256
|
+
=> {:revision=>"3733197239", :id=>"C7A492BB92B1308E646E6536020EAE27"}
|
126
257
|
|
127
|
-
|
128
|
-
|
129
|
-
end
|
130
|
-
end
|
258
|
+
>> my_account.created_at
|
259
|
+
=> Sat Feb 02 17:07:36 -0300 2008
|
131
260
|
|
132
|
-
|
261
|
+
>> my_account.updated_at
|
262
|
+
=> Sat Feb 02 17:07:36 -0300 2008
|
133
263
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
|