aqua 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/Aqua.gemspec +121 -0
  4. data/LICENCE_COUCHREST +176 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +105 -0
  7. data/Rakefile +83 -0
  8. data/VERSION +1 -0
  9. data/lib/aqua.rb +101 -0
  10. data/lib/aqua/object/config.rb +43 -0
  11. data/lib/aqua/object/extensions/ar_convert.rb +0 -0
  12. data/lib/aqua/object/extensions/ar_style.rb +0 -0
  13. data/lib/aqua/object/extensions/property.rb +0 -0
  14. data/lib/aqua/object/extensions/validation.rb +0 -0
  15. data/lib/aqua/object/pack.rb +306 -0
  16. data/lib/aqua/object/query.rb +18 -0
  17. data/lib/aqua/object/stub.rb +122 -0
  18. data/lib/aqua/object/tank.rb +54 -0
  19. data/lib/aqua/object/unpack.rb +253 -0
  20. data/lib/aqua/store/couch_db/attachments.rb +183 -0
  21. data/lib/aqua/store/couch_db/couch_db.rb +151 -0
  22. data/lib/aqua/store/couch_db/database.rb +186 -0
  23. data/lib/aqua/store/couch_db/design_document.rb +57 -0
  24. data/lib/aqua/store/couch_db/http_client/adapter/rest_client.rb +53 -0
  25. data/lib/aqua/store/couch_db/http_client/rest_api.rb +62 -0
  26. data/lib/aqua/store/couch_db/server.rb +103 -0
  27. data/lib/aqua/store/couch_db/storage_methods.rb +405 -0
  28. data/lib/aqua/store/storage.rb +59 -0
  29. data/lib/aqua/support/initializers.rb +216 -0
  30. data/lib/aqua/support/mash.rb +144 -0
  31. data/lib/aqua/support/set.rb +23 -0
  32. data/lib/aqua/support/string_extensions.rb +121 -0
  33. data/spec/aqua_spec.rb +19 -0
  34. data/spec/object/config_spec.rb +58 -0
  35. data/spec/object/object_fixtures/array_udder.rb +5 -0
  36. data/spec/object/object_fixtures/canned_hash.rb +5 -0
  37. data/spec/object/object_fixtures/gerbilmiester.rb +18 -0
  38. data/spec/object/object_fixtures/grounded.rb +13 -0
  39. data/spec/object/object_fixtures/log.rb +19 -0
  40. data/spec/object/object_fixtures/persistent.rb +12 -0
  41. data/spec/object/object_fixtures/sugar.rb +4 -0
  42. data/spec/object/object_fixtures/user.rb +38 -0
  43. data/spec/object/pack_spec.rb +607 -0
  44. data/spec/object/query_spec.rb +27 -0
  45. data/spec/object/stub_spec.rb +51 -0
  46. data/spec/object/tank_spec.rb +61 -0
  47. data/spec/object/unpack_spec.rb +361 -0
  48. data/spec/spec.opts +3 -0
  49. data/spec/spec_helper.rb +16 -0
  50. data/spec/store/couchdb/attachments_spec.rb +164 -0
  51. data/spec/store/couchdb/couch_db_spec.rb +104 -0
  52. data/spec/store/couchdb/database_spec.rb +161 -0
  53. data/spec/store/couchdb/design_document_spec.rb +43 -0
  54. data/spec/store/couchdb/fixtures_and_data/document_fixture.rb +3 -0
  55. data/spec/store/couchdb/fixtures_and_data/image_attach.png +0 -0
  56. data/spec/store/couchdb/server_spec.rb +96 -0
  57. data/spec/store/couchdb/storage_methods_spec.rb +408 -0
  58. data/utils/code_statistics.rb +134 -0
  59. data/utils/console +11 -0
  60. metadata +136 -0
@@ -0,0 +1,57 @@
1
+ # Design documents are responsible for saving views. It is also the place that Aqua will
2
+ # be saving the Class code. There will be one design document per class. There may be additional
3
+ # design documents created without being tied to a class. Don't know yet.
4
+ module Aqua
5
+ module Store
6
+ module CouchDB
7
+ class DesignDocument < Mash
8
+
9
+ # the DesignDocument is essentially a special type of Document.
10
+ include Aqua::Store::CouchDB::StorageMethods
11
+
12
+ # In the design document the name is the same as the id. That way initialization can
13
+ # include a name parameter, which will change the id, and therefore the address of the
14
+ # document. This method returns the id.
15
+ # @return [String] id for document
16
+ # @api public
17
+ def name
18
+ id
19
+ end
20
+
21
+ # Sets the id and is an alias for id=.
22
+ # @param [String] Unique identifier
23
+ # @return [String] Escaped identifier
24
+ # @api public
25
+ def name=( n )
26
+ self.id = ( n )
27
+ end
28
+
29
+ alias :document_initialize :initialize
30
+
31
+ def initialize( hash={} )
32
+ hash = Mash.new( hash ) unless hash.empty?
33
+ self.id = hash.delete(:name) if hash[:name]
34
+ document_initialize( hash )
35
+ end
36
+
37
+ # couchdb database url for the design document
38
+ # @return [String] representing CouchDB uri for document
39
+ # @api public
40
+ def uri
41
+ raise ArgumentError, 'DesignDocument must have a name' if name.nil? || name.empty?
42
+ database.uri + '/_design/' + name
43
+ end
44
+
45
+ # Updates the id and rev after a design document is successfully saved. The _design/
46
+ # portion of the id has to be stripped.
47
+ # @param [Hash] Result returned by CouchDB document save
48
+ # @api private
49
+ def update_version( result )
50
+ self.id = result['id'].gsub(/\A_design\//, '')
51
+ self.rev = result['rev']
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,53 @@
1
+ require 'rest_client'
2
+
3
+ module RestClientAdapter
4
+ def self.convert_exception(&blk)
5
+ begin
6
+ yield
7
+ rescue Exception => e
8
+ ending = e.class.to_s.match(/[a-z0-9_]*\z/i)
9
+ if e.message.match(/409\z/)
10
+ raise Aqua::Store::CouchDB::Conflict, e.message
11
+ else
12
+ begin
13
+ error = "Aqua::Store::CouchDB::#{ending}".constantize
14
+ rescue
15
+ raise e
16
+ end
17
+ raise error, e.message
18
+ end
19
+ end
20
+ end
21
+
22
+ def self.get(uri, headers={})
23
+ convert_exception do
24
+ RestClient.get(uri, headers)
25
+ end
26
+ end
27
+
28
+ def self.post(uri, hash, headers={})
29
+ convert_exception do
30
+ RestClient.post(uri, hash, headers)
31
+ end
32
+ end
33
+
34
+ def self.put(uri, hash, headers={})
35
+ convert_exception do
36
+ RestClient.put(uri, hash, headers)
37
+ end
38
+ end
39
+
40
+ def self.delete(uri, headers={})
41
+ convert_exception do
42
+ RestClient.delete(uri, headers)
43
+ end
44
+ end
45
+
46
+ def self.copy(uri, headers)
47
+ convert_exception do
48
+ RestClient::Request.execute( :method => :copy,
49
+ :url => uri,
50
+ :headers => headers)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,62 @@
1
+ # HTTP Adapters should implement the following to be used with the RestAPI module
2
+ #
3
+ # def self.get(uri, headers=nil)
4
+ # end
5
+ #
6
+ # def self.post(uri, hash, headers=nil)
7
+ # end
8
+ #
9
+ # def self.put(uri, hash, headers=nil)
10
+ # end
11
+ #
12
+ # def self.delete(uri, headers=nil)
13
+ # end
14
+ #
15
+ # def self.copy(uri, headers)
16
+ # end
17
+
18
+ module RestAPI
19
+ def self.adapter=( klass )
20
+ @adapter = klass
21
+ end
22
+
23
+ def self.adapter
24
+ @adapter
25
+ end
26
+
27
+ def put(uri, doc = nil)
28
+ hash = doc.to_json if doc
29
+ response = RestAPI.adapter.put( uri, hash )
30
+ JSON.parse( response )
31
+ end
32
+
33
+ def get(uri, streamable=false)
34
+ response = RestAPI.adapter.get(uri)
35
+ begin
36
+ JSON.parse( response , :max_nesting => false)
37
+ rescue Exception => e
38
+ if streamable
39
+ response
40
+ else
41
+ raise e
42
+ end
43
+ end
44
+ end
45
+
46
+ def post(uri, doc = nil)
47
+ hash = doc.to_json if doc
48
+ response = RestAPI.adapter.post(uri, hash)
49
+ JSON.parse( response )
50
+ end
51
+
52
+ def delete(uri)
53
+ response = RestAPI.adapter.delete(uri)
54
+ JSON.parse( response )
55
+ end
56
+
57
+ def copy(uri, destination)
58
+ response = RestAPI.adapter.copy(uri, {'Destination' => destination})
59
+ JSON.parse( response )
60
+ end
61
+
62
+ end
@@ -0,0 +1,103 @@
1
+ # This has been ripped in part from CouchRest: http://github.com/mattetti/couchrest/tree/master
2
+ # License information is in LICENSE_COUCHREST, modifications are covered under the aqua license
3
+
4
+ module Aqua
5
+ module Store
6
+ module CouchDB
7
+ class Server
8
+ attr_accessor :uri, :uuid_batch_count, :uuids
9
+ attr_reader :namespace
10
+
11
+ def initialize(opts={})
12
+ opts = Mash.new(opts) unless opts.empty?
13
+ self.uri = opts[:server] || 'http://127.0.0.1:5984'
14
+ self.uuid_batch_count = opts[:uuid_batch_count] || 1000
15
+ self.namespace = opts[:namespace].to_s
16
+ end
17
+
18
+ def namespace=( name )
19
+ default = 'aqua'
20
+ name ||= default
21
+ name = CouchDB.escape( name )
22
+ name = default if name.empty?
23
+ @namespace = name
24
+ end
25
+
26
+ # DATABASE MANAGMENT -----------------
27
+
28
+ # Lists all database names on the server
29
+ def database_names
30
+ dbs = CouchDB.get( "#{@uri}/_all_dbs" )
31
+ dbs.select{|name| name.match(/\A#{namespace}_?/)}
32
+ end
33
+
34
+ def databases
35
+ dbs = []
36
+ database_names.each do |db_name|
37
+ dbs << Database.new( db_name.gsub(/\A#{namespace}_|\A#{namespace}\z/, '') , :server => self )
38
+ end
39
+ dbs
40
+ end
41
+
42
+ # Deletes all databases named for this namespace (i.e. this server)
43
+ # Use with caution ... it is a permanent and undoable change
44
+ def delete_all!
45
+ databases.each{ |db| db.delete! }
46
+ end
47
+
48
+ # Deletes all database with the less exection raising method: database.delete. This will
49
+ # only raise errors related to request problems, and not errors related to the database not
50
+ # being found for deletion.
51
+ def delete_all
52
+ databases.each{ |db| db.delete }
53
+ end
54
+
55
+ # Returns a CouchRest::Database for the given name
56
+ def database(name)
57
+ db = Database.new( name, :server => self )
58
+ db.exists? ? db : nil
59
+ end
60
+
61
+ # Creates the database if it doesn't exist
62
+ def database!(name)
63
+ Database.create( name, :server => self )
64
+ end
65
+
66
+ # GET the welcome message
67
+ def info
68
+ CouchDB.get "#{uri}/"
69
+ end
70
+
71
+ # Restart the CouchDB instance
72
+ def restart!
73
+ CouchDB.post "#{uri}/_restart"
74
+ end
75
+
76
+ # counts the number of uuids available, used by Database to limit bulk save
77
+ def uuid_count
78
+ if uuids
79
+ uuids.size
80
+ else
81
+ load_uuids
82
+ uuid_batch_count
83
+ end
84
+ end
85
+
86
+ # Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
87
+ def next_uuid(count = @uuid_batch_count)
88
+ @uuids ||= []
89
+ if uuids.empty?
90
+ load_uuids(count)
91
+ end
92
+ uuids.pop
93
+ end
94
+
95
+ def load_uuids( count=@uuid_batch_count )
96
+ @uuids = CouchDB.get("#{@uri}/_uuids?count=#{count}")["uuids"]
97
+ end
98
+
99
+
100
+ end # Server
101
+ end # CouchDB
102
+ end # Store
103
+ end # Aqua
@@ -0,0 +1,405 @@
1
+ require 'cgi'
2
+ require 'base64'
3
+
4
+ module Aqua
5
+ module Store
6
+ module CouchDB
7
+ # This module of storage methods was built to be flexible enough to step in as a replacement
8
+ # for CouchRest core or another super lite CouchDB library. A lot of the methods are added for that
9
+ # convenience, and not for the needs of Aqua. Adding the module to a Mash/Hash class is sufficient
10
+ # to get the full core access library.
11
+ #
12
+ # @see Aqua::Storage for details on the require methods for a storage library.
13
+ module StorageMethods
14
+
15
+ def self.included( klass )
16
+ klass.class_eval do
17
+ include InstanceMethods
18
+ extend ClassMethods
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+ # Initializes a new storage document and saves it without raising any errors
24
+ #
25
+ # @param [Hash, Mash]
26
+ # @return [Aqua::Storage, false] On success it returns an aqua storage object. On failure it returns false.
27
+ #
28
+ # @api public
29
+ def create( hash )
30
+ doc = new( hash )
31
+ doc.save
32
+ end
33
+
34
+ # Initializes a new storage document and saves it raising any errors.
35
+ #
36
+ # @param [Hash, Mash]
37
+ # @return [Aqua::Storage] On success it returns an aqua storage object.
38
+ # @raise Any of the CouchDB exceptions
39
+ #
40
+ # @api public
41
+ def create!( hash )
42
+ doc = new( hash )
43
+ doc.save!
44
+ end
45
+
46
+ # Sets default database for class. This can be overwritten by individual documents
47
+ # @todo Look to CouchDB database strategy to determine if there is a database per class
48
+ # or just one big database for all classes
49
+ #
50
+ # @return [Aqua::Database]
51
+ #
52
+ # @api public
53
+ def database
54
+ @database ||= Database.create # defaults to 'aqua'
55
+ end
56
+
57
+ # Setter for the database per class. Used to override default per class or default strategy.
58
+ #
59
+ # @return [Aqua::Database]
60
+ #
61
+ # @api public
62
+ def database=( db )
63
+ db = Database.create( db ) if db.class == String
64
+ @database = db
65
+ end
66
+
67
+ # gets a document from the database based on id
68
+ # @param [String] id
69
+ # @return [Hash] representing the CouchDB data
70
+ # @api public
71
+ def get( id )
72
+ new( CouchDB.get( "#{database.uri}/#{CGI.escape(id)}" ) )
73
+ end
74
+
75
+ # Retrieves an attachment when provided the document id and attachment id, or the combined id
76
+ #
77
+ # @return [Tempfile]
78
+ #
79
+ # @api public
80
+ def attachment( document_id, attachment_id )
81
+ new( :id => document_id ).attachments.get!( attachment_id )
82
+ end
83
+
84
+ # Creates basic map reduce view for a given field
85
+ def index_on( field, opts={} )
86
+
87
+ end
88
+ end
89
+
90
+ module InstanceMethods
91
+ # Initializes a new storage document.
92
+ #
93
+ # @param [Hash, Mash]
94
+ # @return [Aqua::Storage] a Hash/Mash with some extras
95
+ #
96
+ # @api public
97
+ def initialize( hash={} )
98
+ hash = Mash.new( hash ) unless hash.empty?
99
+ self.id = hash.delete(:id) if hash[:id]
100
+
101
+ # ignore these keys
102
+ hash.delete(:rev) # This is omited to aleviate confusion
103
+ hash.delete(:_rev) # CouchDB determines _rev attribute
104
+ hash.delete(:_id) # this is set via by the id=(value) method
105
+ # TODO: have to deal with attachments as well
106
+
107
+ # feed the rest of the hash to the super
108
+ super( hash )
109
+ end
110
+
111
+ # Saves an Aqua::Storage instance to CouchDB as a document. Save can be deferred for bulk saving.
112
+ #
113
+ # @param [optional true, false] Determines whether the document is cached for bulk saving later. true will cause it to be defered. Default is false.
114
+ # @return [Aqua::Storage, false] Will return false if the document is not saved. Otherwise it will return the Aqua::Storage object.
115
+ #
116
+ # @api public
117
+ def save( defer=false )
118
+ save_logic( defer )
119
+ end
120
+
121
+ # Saves an Aqua::Storage instance to CouchDB as a document. Save can be deferred for bulk saving from the database.
122
+ # Unlike #save, this method will raise an error if the document is not saved.
123
+ #
124
+ # @param [optional true, false] Determines whether the document is cached for bulk saving later. true will cause it to be defered. Default is false.
125
+ # @return [Aqua::Storage] On success.
126
+ #
127
+ # @api public
128
+ def commit( defer=false )
129
+ save_logic( defer, false )
130
+ end
131
+ alias :save! :commit
132
+
133
+ # Internal logic used by save, save! and commit to save an object.
134
+ #
135
+ # @param [optional true, false] Determines whether a object cached for save in the database in bulk. By default this is false.
136
+ # @param [optional true, false] Determines whether an exception is raised or whether false is returned.
137
+ # @return [Aqua::Storage, false] Depening on the type of execption masking and also the outcome
138
+ # @raise Any of the CouchDB execptions.
139
+ #
140
+ # @api private
141
+ def save_logic( defer=false, mask_exception = true )
142
+ ensure_id
143
+ self[:_attachments] = attachments.pack unless attachments.empty?
144
+ if defer
145
+ database.add_to_bulk_cache( self )
146
+ else
147
+ # clear any bulk saving left over ...
148
+ database.bulk_save if database.bulk_cache.size > 0
149
+ if mask_exception
150
+ save_now
151
+ else
152
+ save_now( false )
153
+ end
154
+ end
155
+ end
156
+
157
+ # Internal logic used by save_logic to save an object immediately instead of deferring for bulk save.
158
+ #
159
+ # @param [optional true, false] Determines whether an exception is raised or whether false is returned.
160
+ # @return [Aqua::Storage, false] Depening on the type of execption masking and also the outcome
161
+ # @raise Any of the CouchDB execptions.
162
+ #
163
+ # @api private
164
+ def save_now( mask_exception = true )
165
+ begin
166
+ result = CouchDB.put( uri, self )
167
+ rescue Exception => e
168
+ if mask_exception
169
+ result = false
170
+ else
171
+ raise e
172
+ end
173
+ end
174
+
175
+ if result && result['ok']
176
+ update_version( result )
177
+ self
178
+ else
179
+ result
180
+ end
181
+ end
182
+
183
+ # couchdb database url for this document
184
+ # @return [String] representing CouchDB uri for document
185
+ # @api public
186
+ def uri
187
+ database.uri + '/' + ensure_id
188
+ end
189
+
190
+ # retrieves self from CouchDB database
191
+ # @return [Hash] representing the CouchDB data
192
+ # @api public
193
+ def retrieve
194
+ self.class.new( CouchDB.get( uri ) )
195
+ end
196
+
197
+ # reloads self from CouchDB database
198
+ # @return [Hash] representing CouchDB data
199
+ # @api public
200
+ def reload
201
+ self.replace( CouchDB.get( uri ) )
202
+ end
203
+
204
+ # Deletes an document from CouchDB. Delete can be deferred for bulk saving/deletion.
205
+ #
206
+ # @param [optional true, false] Determines whether the document is cached for bulk saving later. true will cause it to be defered. Default is false.
207
+ # @return [String, false] Will return a json string with the response if successful. Otherwise returns false.
208
+ #
209
+ # @api public
210
+ def delete(defer = false)
211
+ delete_logic( defer )
212
+ end
213
+
214
+ # Deletes an document from CouchDB. Delete can be deferred for bulk saving/deletion. This version raises an exception if an error other that resource not found is raised.
215
+ #
216
+ # @param [optional true, false] Determines whether the document is cached for bulk saving later. true will cause it to be defered. Default is false.
217
+ # @return [String, false] Will return a json string with the response if successful. It will return false if the resource was not found. Other exceptions will be raised.
218
+ # @raise Any of the CouchDB exceptions
219
+ #
220
+ # @api public
221
+ def delete!(defer = false)
222
+ delete_logic( defer, false )
223
+ end
224
+
225
+ # Internal logic used by delete and delete! to delete a resource.
226
+ #
227
+ # @param [optional true, false] Determines whether resource is deleted immediately or saved for bulk processing.
228
+ # @param [optional true, false] Determines whether an exception is raised or whether false is returned.
229
+ # @return [String, false] Depening on the type of execption masking and also the outcome
230
+ # @raise Any of the CouchDB execptions.
231
+ #
232
+ # @api private
233
+ def delete_logic( defer = false, mask_exceptions = true )
234
+ if defer
235
+ database.add_to_bulk_cache( { '_id' => self['_id'], '_rev' => rev, '_deleted' => true } )
236
+ else
237
+ begin
238
+ delete_now
239
+ rescue Exception => e
240
+ if mask_exceptions || e.class == CouchDB::ResourceNotFound
241
+ false
242
+ else
243
+ raise e
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ # Internal logic used by delete_logic delete a resource immediately.
250
+ #
251
+ # @return [String, false] Depening on the type of execption masking and also the outcome
252
+ # @raise Any of the CouchDB execptions.
253
+ #
254
+ # @api private
255
+ def delete_now
256
+ revisions.each do |rev_id|
257
+ CouchDB.delete( "#{uri}?rev=#{rev_id}" )
258
+ end
259
+ true
260
+ end
261
+
262
+ # Gets revision history, which is needed by Delete to remove all versions of a document
263
+ #
264
+ # @return [Array] Containing strings with revision numbers
265
+ #
266
+ # @api semi-private
267
+ def revisions
268
+ active_revisions = []
269
+ begin
270
+ hash = CouchDB.get( "#{uri}?revs_info=true" )
271
+ rescue
272
+ return active_revisions
273
+ end
274
+ hash['_revs_info'].each do |rev_hash|
275
+ active_revisions << rev_hash['rev'] if ['disk', 'available'].include?( rev_hash['status'] )
276
+ end
277
+ active_revisions
278
+ end
279
+
280
+
281
+ # sets the database
282
+ # @param [Aqua::Store::CouchDB::Database]
283
+ # @return [Aqua::Store::CouchDB::Database]
284
+ # @api private
285
+ attr_writer :database
286
+
287
+ # retrieves the previously set database or sets the new one with a default value
288
+ # @return [Aqua::Store::CouchDB::Database]
289
+ # @api private
290
+ def database
291
+ @database ||= determine_database
292
+ end
293
+
294
+ # Looks to class for database information about how the CouchDB store has generally
295
+ # been configured to store its data across databases and/or servers. In some cases the class for
296
+ # the parent object has configuration details about the database and server to use.
297
+ # @todo Build the strategies in CouchDB. Use them here
298
+ # @api private
299
+ def determine_database
300
+ self.class.database
301
+ end
302
+
303
+ # setters and getters couchdb document specifics -------------------------
304
+
305
+ # Gets the document id. In this engine id and _id are different data. The main reason for this is that
306
+ # CouchDB needs a relatively clean string as the key, where as the user can assign a messy string to
307
+ # the id. The user can continue to use the messy string since the engine also has access to the _id.
308
+ #
309
+ # @return [String]
310
+ #
311
+ # @api public
312
+ def id
313
+ self[:id]
314
+ end
315
+
316
+ # Allows the id to be set. If the id is changed after creation, then the CouchDB document for the old
317
+ # id is deleted, and the _rev is set to nil, making it a new document. The id can only be a string (right now).
318
+ #
319
+ # @return [String, false] Will return the string it received if it is indeed a string. Otherwise it will
320
+ # return false.
321
+ #
322
+ # @api public
323
+ def id=( str )
324
+ if str.respond_to?(:match)
325
+ escaped = CGI.escape( str )
326
+
327
+ # CLEANUP: do a bulk delete request on the old id, now that it has changed
328
+ delete(true) if !new? && escaped != self[:_id]
329
+
330
+ self[:id] = str
331
+ self[:_id] = escaped
332
+ str
333
+ end
334
+ end
335
+
336
+ # Returns CouchDB document revision identifier.
337
+ #
338
+ # @return [String]
339
+ #
340
+ # @api semi-public
341
+ def rev
342
+ self[:_rev]
343
+ end
344
+
345
+ protected
346
+ def rev=( str )
347
+ self[:_rev] = str
348
+ end
349
+ public
350
+
351
+ # Updates the id and rev after a document is successfully saved.
352
+ # @param [Hash] Result returned by CouchDB document save
353
+ # @api private
354
+ def update_version( result )
355
+ self.id = result['id']
356
+ self.rev = result['rev']
357
+ end
358
+
359
+ # Returns true if the document has never been saved or false if it has been saved.
360
+ # @return [true, false]
361
+ # @api public
362
+ def new?
363
+ !rev
364
+ end
365
+ alias :new_document? :new?
366
+
367
+ # Returns true if a document exists at the CouchDB uri for this document. Otherwise returns false
368
+ # @return [true, false]
369
+ # @api public
370
+ def exists?
371
+ begin
372
+ CouchDB.get uri
373
+ true
374
+ rescue
375
+ false
376
+ end
377
+ end
378
+
379
+ # gets a uuid from the server if one doesn't exist, otherwise escapes existing id.
380
+ # @api private
381
+ def ensure_id
382
+ self[:_id] = ( id ? escape_doc_id : database.server.next_uuid )
383
+ end
384
+
385
+ # Escapes document id. Different strategies for design documents and normal documents.
386
+ # @api private
387
+ def escape_doc_id
388
+ CGI.escape( id )
389
+ end
390
+
391
+ # Hash of attachments, keyed by name
392
+ # @params [Document] Document object that is self
393
+ # @return [Hash] Attachments keyed by name
394
+ #
395
+ # @api public
396
+ def attachments
397
+ @attachments ||= Attachments.new( self )
398
+ end
399
+
400
+ end # InstanceMethods
401
+
402
+ end # StoreMethods
403
+ end # CouchDB
404
+ end # Store
405
+ end # Aqua