aqua 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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