aqua 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/Aqua.gemspec +121 -0
- data/LICENCE_COUCHREST +176 -0
- data/LICENSE +20 -0
- data/README.rdoc +105 -0
- data/Rakefile +83 -0
- data/VERSION +1 -0
- data/lib/aqua.rb +101 -0
- data/lib/aqua/object/config.rb +43 -0
- data/lib/aqua/object/extensions/ar_convert.rb +0 -0
- data/lib/aqua/object/extensions/ar_style.rb +0 -0
- data/lib/aqua/object/extensions/property.rb +0 -0
- data/lib/aqua/object/extensions/validation.rb +0 -0
- data/lib/aqua/object/pack.rb +306 -0
- data/lib/aqua/object/query.rb +18 -0
- data/lib/aqua/object/stub.rb +122 -0
- data/lib/aqua/object/tank.rb +54 -0
- data/lib/aqua/object/unpack.rb +253 -0
- data/lib/aqua/store/couch_db/attachments.rb +183 -0
- data/lib/aqua/store/couch_db/couch_db.rb +151 -0
- data/lib/aqua/store/couch_db/database.rb +186 -0
- data/lib/aqua/store/couch_db/design_document.rb +57 -0
- data/lib/aqua/store/couch_db/http_client/adapter/rest_client.rb +53 -0
- data/lib/aqua/store/couch_db/http_client/rest_api.rb +62 -0
- data/lib/aqua/store/couch_db/server.rb +103 -0
- data/lib/aqua/store/couch_db/storage_methods.rb +405 -0
- data/lib/aqua/store/storage.rb +59 -0
- data/lib/aqua/support/initializers.rb +216 -0
- data/lib/aqua/support/mash.rb +144 -0
- data/lib/aqua/support/set.rb +23 -0
- data/lib/aqua/support/string_extensions.rb +121 -0
- data/spec/aqua_spec.rb +19 -0
- data/spec/object/config_spec.rb +58 -0
- data/spec/object/object_fixtures/array_udder.rb +5 -0
- data/spec/object/object_fixtures/canned_hash.rb +5 -0
- data/spec/object/object_fixtures/gerbilmiester.rb +18 -0
- data/spec/object/object_fixtures/grounded.rb +13 -0
- data/spec/object/object_fixtures/log.rb +19 -0
- data/spec/object/object_fixtures/persistent.rb +12 -0
- data/spec/object/object_fixtures/sugar.rb +4 -0
- data/spec/object/object_fixtures/user.rb +38 -0
- data/spec/object/pack_spec.rb +607 -0
- data/spec/object/query_spec.rb +27 -0
- data/spec/object/stub_spec.rb +51 -0
- data/spec/object/tank_spec.rb +61 -0
- data/spec/object/unpack_spec.rb +361 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/store/couchdb/attachments_spec.rb +164 -0
- data/spec/store/couchdb/couch_db_spec.rb +104 -0
- data/spec/store/couchdb/database_spec.rb +161 -0
- data/spec/store/couchdb/design_document_spec.rb +43 -0
- data/spec/store/couchdb/fixtures_and_data/document_fixture.rb +3 -0
- data/spec/store/couchdb/fixtures_and_data/image_attach.png +0 -0
- data/spec/store/couchdb/server_spec.rb +96 -0
- data/spec/store/couchdb/storage_methods_spec.rb +408 -0
- data/utils/code_statistics.rb +134 -0
- data/utils/console +11 -0
- metadata +136 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
module Aqua
|
2
|
+
module Store
|
3
|
+
module CouchDB
|
4
|
+
# Attachments is a Hash-like container with keys that are attacment names and values that are file-type
|
5
|
+
# objects. Initializing and adding to the collection assures the types of both keys and values. The
|
6
|
+
# collection implements a lazy-loading scheme, such that when an attachment is requested and not found,
|
7
|
+
# it will try to load it from CouchDB.
|
8
|
+
class Attachments < Mash
|
9
|
+
attr_reader :document
|
10
|
+
attr_reader :stubs
|
11
|
+
|
12
|
+
# Creates a new attachment collection with keys that are attachment names and values that are
|
13
|
+
# file-type objects. The collection manages both the key and the value types.
|
14
|
+
#
|
15
|
+
# @param [String] Document uri; used to save and retrieve attachments directly
|
16
|
+
# @param [Hash] Initialization values
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def initialize( doc, hash={} )
|
20
|
+
raise ArgumentError, "must be initialized with a document" unless doc.respond_to?( :retrieve )
|
21
|
+
@document = doc
|
22
|
+
self.class.validate_hash( hash ) unless hash.empty?
|
23
|
+
super( hash )
|
24
|
+
end
|
25
|
+
|
26
|
+
# Adds an attachment to the collection, checking for type. Does not add directly to the database.
|
27
|
+
#
|
28
|
+
# @param [String, Symbol] Name of the attachment as a string or symbol
|
29
|
+
# @param [File] The attachment
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
def add( name, file )
|
33
|
+
self.class.validate_hash( name => file )
|
34
|
+
self[name] = file
|
35
|
+
end
|
36
|
+
|
37
|
+
# Adds an attachment to the collection and to the database. Document doesn't have to be saved,
|
38
|
+
# but it does need to have an id.
|
39
|
+
#
|
40
|
+
# @param [String, Symbol] Name of the attachment as a string or symbol
|
41
|
+
# @param [File] The attachment
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def add!( name, file )
|
45
|
+
add( name, file )
|
46
|
+
content_type = MIME::Types.type_for(file.path).first
|
47
|
+
content_type = content_type.nil? ? "text\/plain" : content_type.simplified
|
48
|
+
data = {
|
49
|
+
'content_type' => content_type,
|
50
|
+
'data' => Base64.encode64( file.read ).gsub(/\s/,'')
|
51
|
+
}
|
52
|
+
file.rewind
|
53
|
+
response = CouchDB.put( uri_for( name ), data )
|
54
|
+
update_doc_rev( response )
|
55
|
+
file
|
56
|
+
end
|
57
|
+
|
58
|
+
# Deletes an attachment from the collection, and from the database. Use #delete (from Hash) to just
|
59
|
+
# delete the attachment from the collection.
|
60
|
+
#
|
61
|
+
# @param [String, Symbol] Name of the attachment as a string or symbol
|
62
|
+
# @return [File, nil] File at that location or nil if no file found
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
def delete!( name )
|
66
|
+
if self[name]
|
67
|
+
file = delete( name )
|
68
|
+
unless document.new?
|
69
|
+
CouchDB.delete( uri_for( name ) )
|
70
|
+
end
|
71
|
+
file
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Gets an attachment from the collection first. If not found, it will be requested from the database.
|
76
|
+
#
|
77
|
+
# @param [String, Symbol] Name of the attachment
|
78
|
+
# @return [File, nil] File for that name, or nil if not found in hash or in database
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
def get( name, stream=false )
|
82
|
+
file = self[name]
|
83
|
+
unless file
|
84
|
+
file = get!( name, stream )
|
85
|
+
end
|
86
|
+
file.rewind if file # just in case of previous streaming
|
87
|
+
file
|
88
|
+
end
|
89
|
+
|
90
|
+
# Gets an attachment from the database. Stores it in the hash.
|
91
|
+
#
|
92
|
+
# @param [String, Symbol] Name of the attachment
|
93
|
+
# @param [true, false] Stream boolean flag indicating whether the data should be converted to
|
94
|
+
# a file or kept as a stream
|
95
|
+
# @return [File, nil] File for that name, or nil if not found in the database
|
96
|
+
# @raise Any error encountered on retrieval of the attachment, json, http_client, Aqua etc
|
97
|
+
#
|
98
|
+
# @todo make this more memory favorable, maybe streaming/saving in a max number of bytes
|
99
|
+
# @api public
|
100
|
+
def get!( name, stream=false )
|
101
|
+
file = nil
|
102
|
+
response = CouchDB.get( uri_for( name, false ), true ) rescue nil
|
103
|
+
data = response && response.respond_to?(:keys) ? Base64.decode64( response['data'] ) : nil
|
104
|
+
if data || response
|
105
|
+
file = Tempfile.new( CGI.escape( name.to_s ) )
|
106
|
+
file.binmode if file.respond_to?( :binmode )
|
107
|
+
data ? file.write( data ) : file.write( response )
|
108
|
+
file.rewind
|
109
|
+
self[name] = file
|
110
|
+
end
|
111
|
+
stream ? file.read : file
|
112
|
+
end
|
113
|
+
|
114
|
+
# Constructs the standalone attachment uri for PUT and DELETE actions.
|
115
|
+
#
|
116
|
+
# @param [String] Name of the attachment as a string or symbol
|
117
|
+
#
|
118
|
+
# @api private
|
119
|
+
def uri_for( name, include_rev = true )
|
120
|
+
raise ArgumentError, 'Document must have id in order to save an attachment' if document.id.nil? || document.id.empty?
|
121
|
+
document.uri + "/#{CGI.escape( name.to_s )}" + ( document.rev && include_rev ? "?rev=#{document.rev}" : "" )
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
# Validates and throws an error on a hash, insisting that the key is a string or symbol,
|
126
|
+
# and the value is a file.
|
127
|
+
#
|
128
|
+
# @param [Hash]
|
129
|
+
#
|
130
|
+
# @api private
|
131
|
+
def self.validate_hash( hash )
|
132
|
+
hash.each do |name, file|
|
133
|
+
raise ArgumentError, "Attachment name, #{name.inspect}, must be a Symbol or a String" unless [Symbol, String ].include?( name.class )
|
134
|
+
raise ArgumentError, "Attachment file, #{file.inspect}, must be a File-like object" unless file.respond_to?( :read )
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Goes into the document and updates it's rev to match the returned rev. That way #new? will return false
|
139
|
+
# when an attachment is created before the document is saved. It also means that future attempts to save
|
140
|
+
# the doc won't fail with a conflict.
|
141
|
+
#
|
142
|
+
# @param [Hash] response from the put request
|
143
|
+
# @api private
|
144
|
+
def update_doc_rev( response )
|
145
|
+
document[:_rev] = response['rev']
|
146
|
+
end
|
147
|
+
|
148
|
+
# Creates a hash for the CouchDB _attachments key.
|
149
|
+
# @example
|
150
|
+
# "_attachments":
|
151
|
+
# {
|
152
|
+
# "foo.txt":
|
153
|
+
# {
|
154
|
+
# "content_type":"text\/plain",
|
155
|
+
# "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
|
156
|
+
# },
|
157
|
+
#
|
158
|
+
# "bar.txt":
|
159
|
+
# {
|
160
|
+
# "content_type":"text\/plain",
|
161
|
+
# "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
|
162
|
+
# }
|
163
|
+
# }
|
164
|
+
def pack
|
165
|
+
pack_hash = {}
|
166
|
+
self.keys.each do |key|
|
167
|
+
file = self[key]
|
168
|
+
content_type = MIME::Types.type_for(file.path).first
|
169
|
+
content_type = content_type.nil? ? "text\/plain" : content_type.simplified
|
170
|
+
data = {
|
171
|
+
'content_type' => content_type,
|
172
|
+
'data' => Base64.encode64( file.read ).gsub(/\s/,'')
|
173
|
+
}
|
174
|
+
file.rewind
|
175
|
+
pack_hash[key.to_s] = data
|
176
|
+
end
|
177
|
+
pack_hash
|
178
|
+
end
|
179
|
+
|
180
|
+
end # Attachments
|
181
|
+
end # CouchDB
|
182
|
+
end # Store
|
183
|
+
end # Aqua
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/http_client/rest_api'
|
2
|
+
require File.dirname(__FILE__) + '/server'
|
3
|
+
require File.dirname(__FILE__) + '/database'
|
4
|
+
require File.dirname(__FILE__) + '/attachments'
|
5
|
+
require File.dirname(__FILE__) + '/storage_methods'
|
6
|
+
require File.dirname(__FILE__) + '/design_document'
|
7
|
+
|
8
|
+
module Aqua
|
9
|
+
module Store
|
10
|
+
module CouchDB
|
11
|
+
|
12
|
+
class ResourceNotFound < IOError; end
|
13
|
+
class RequestFailed < IOError; end
|
14
|
+
class RequestTimeout < IOError; end
|
15
|
+
class ServerBrokeConnection < IOError; end
|
16
|
+
class Conflict < ArgumentError; end
|
17
|
+
|
18
|
+
# Returns a string describing the http adapter in use, or loads the default and returns a similar string
|
19
|
+
# @return [String] A string identifier for the HTTP adapter in use
|
20
|
+
def self.http_adapter
|
21
|
+
@adapter ||= set_http_adapter
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets a class variable with the library name that will be loaded.
|
25
|
+
# Then attempts to load said library from the adapter directory.
|
26
|
+
# It is extended into the HttpAbstraction module. Then the RestAPI which
|
27
|
+
# references the HttpAbstraction module is loaded/extended into Aqua
|
28
|
+
# this makes available Aqua.get 'http:://someaddress.com' and other requests
|
29
|
+
|
30
|
+
# Loads an http_adapter from the internal http_client libraries. Right now there is only the
|
31
|
+
# RestClient Adapter. Other adapters will be added when people get motivated to write and submit them.
|
32
|
+
# By default the RestClientAdapter is used, and if the CouchDB module is used without prior configuration
|
33
|
+
# it is automatically loaded.
|
34
|
+
#
|
35
|
+
# @param [optional, String] Maps to the HTTP Client Adapter module name, file name is inferred by removing the 'Adapter' suffix and underscoring the string
|
36
|
+
# @return [String] Name of HTTP Client Adapter module
|
37
|
+
# @see Aqua::Store::CouchDB::RestAPI Has detail about the required interface
|
38
|
+
# @api public
|
39
|
+
def self.set_http_adapter( mod_string='RestClientAdapter' )
|
40
|
+
|
41
|
+
# what is happening here:
|
42
|
+
# strips the Adapter portion of the module name to get at the client name
|
43
|
+
# convention over configurationing to get the file name as it relates to files in http_client/adapter
|
44
|
+
# require the hopefully found file
|
45
|
+
# modify the RestAPI class to extend the Rest methods from the adapter
|
46
|
+
# add the RestAPI to Aqua for easy access throughout the library
|
47
|
+
|
48
|
+
@adapter = mod_string
|
49
|
+
mod = @adapter.gsub(/Adapter/, '')
|
50
|
+
file = mod.underscore
|
51
|
+
require File.dirname(__FILE__) + "/http_client/adapter/#{file}"
|
52
|
+
RestAPI.adapter = "#{mod_string}".constantize
|
53
|
+
extend(::RestAPI)
|
54
|
+
@adapter # return the adapter
|
55
|
+
end
|
56
|
+
|
57
|
+
# Cache of CouchDB Servers used by Aqua. Each is identified by its namespace.
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
def self.servers
|
61
|
+
@servers ||= {}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Reader for getting or initializtion and getting a server by namespace. Used by various parts of store
|
65
|
+
# to define storage strategies. Also conserves memory so that there is only one instance of a Server per
|
66
|
+
# namespace.
|
67
|
+
#
|
68
|
+
# @param [String] Server Namespace
|
69
|
+
# @api private
|
70
|
+
def self.server( namespace=nil )
|
71
|
+
namespace ||= :aqua
|
72
|
+
namespace = namespace.to_sym unless namespace.class == Symbol
|
73
|
+
s = servers[ namespace ]
|
74
|
+
s = servers[namespace.to_sym] = Server.new( :namespace => namespace ) unless s
|
75
|
+
s
|
76
|
+
end
|
77
|
+
|
78
|
+
# Clears the cached servers. So far this is most useful for testing.
|
79
|
+
# API will depend on usefulness outside this.
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
def self.clear_servers
|
83
|
+
@servers = {}
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# TEXT HELPERS ================================================
|
88
|
+
|
89
|
+
# This comes from the CouchRest Library and its licence applies.
|
90
|
+
# It is included in this library as LICENCE_COUCHREST.
|
91
|
+
# The method breaks the parameters into a url query string.
|
92
|
+
#
|
93
|
+
# @param [String] The base url upon which to attach query params
|
94
|
+
# @param [optional Hash] A series of key value pairs that define the url query params
|
95
|
+
# @api semi-public
|
96
|
+
def self.paramify_url( url, params = {} )
|
97
|
+
if params && !params.empty?
|
98
|
+
query = params.collect do |k,v|
|
99
|
+
v = v.to_json if %w{key startkey endkey}.include?(k.to_s)
|
100
|
+
"#{k}=#{CGI.escape(v.to_s)}"
|
101
|
+
end.join("&")
|
102
|
+
url = "#{url}?#{query}"
|
103
|
+
end
|
104
|
+
url
|
105
|
+
end
|
106
|
+
|
107
|
+
# A convenience method for escaping a string,
|
108
|
+
# namespaced classes with :: notation will be converted to __
|
109
|
+
# all other non-alpha numeric characters besides hyphens and underscores are removed
|
110
|
+
#
|
111
|
+
# @param [String] to be converted
|
112
|
+
# @return [String] converted
|
113
|
+
#
|
114
|
+
# @api private
|
115
|
+
def self.escape( str )
|
116
|
+
str.gsub!('::', '__')
|
117
|
+
str.gsub!(/[^a-z0-9\-_]/, '')
|
118
|
+
str
|
119
|
+
end
|
120
|
+
|
121
|
+
# DATABASE STRATEGIES ----------------------------------
|
122
|
+
# This library was built with to be flexible but have some sensible defaults. Database strategies is
|
123
|
+
# one of those areas. You can configure the CouchDB module to use one of three ways of managing data
|
124
|
+
# into databases:
|
125
|
+
# * :single - This is the default. It uses the CouchDB.server(:aqua) to build a single database where
|
126
|
+
# all the documents are stored.
|
127
|
+
# * :per_class - This strategy is the opposite of the single strategy in that each class has it's own
|
128
|
+
# database. This will make complex cross class lookups more difficult.
|
129
|
+
# * :configured - Each class configures its own database and server namespace. Any server not
|
130
|
+
# configured will default to the CouchDB.server. Any database not configured will default to the
|
131
|
+
# default database ... that set by the server namespace.
|
132
|
+
# TODO: store these strategies; give feedback to documents about the appropriate database.
|
133
|
+
|
134
|
+
|
135
|
+
# AUTOLOADING ---------
|
136
|
+
# auto loads the default http_adapter if Aqua gets used without configuring it first
|
137
|
+
|
138
|
+
class << self
|
139
|
+
def method_missing( method, *args )
|
140
|
+
if @adapter.nil?
|
141
|
+
set_http_adapter # loads up the adapter related stuff
|
142
|
+
send( method.to_sym, eval(args.map{|value| "'#{value}'"}.join(', ')) )
|
143
|
+
else
|
144
|
+
raise NoMethodError
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end # CouchDB
|
150
|
+
end # Store
|
151
|
+
end # Aqua
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require "base64"
|
2
|
+
|
3
|
+
module Aqua
|
4
|
+
module Store
|
5
|
+
module CouchDB
|
6
|
+
class Database
|
7
|
+
attr_reader :server, :name, :uri
|
8
|
+
attr_accessor :bulk_cache
|
9
|
+
|
10
|
+
# Create a CouchDB database representation from a name. Does not actually create a database on couchdb.
|
11
|
+
# It does not ensure that the database actually exists either. Just creates a ruby representation
|
12
|
+
# of a ruby database interface.
|
13
|
+
#
|
14
|
+
# @param [optional String] Name of database. If not provided server namespace will be used as database name.
|
15
|
+
# @param [optional Hash] Options for initialization. Currently the only option is :server which must be either a CouchDB server object or a symbol representing a server stored in the CouchDB module.
|
16
|
+
# @return [Database] The initialized object
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def initialize( name=nil, opts={})
|
20
|
+
name = nil if name && name.empty?
|
21
|
+
opts = Mash.new( opts ) unless opts.empty?
|
22
|
+
@name = name if name
|
23
|
+
initialize_server( opts[:server] )
|
24
|
+
@uri = "#{server.uri}/#{namespaced( name )}"
|
25
|
+
self.bulk_cache = []
|
26
|
+
end
|
27
|
+
|
28
|
+
# Initializes the database server with the option provided. If not option is provided the default CouchDB
|
29
|
+
# server is used instead.
|
30
|
+
#
|
31
|
+
# @param [Symbol, Server]
|
32
|
+
# If server_option argument is a Symbol then the CouchDB server stash will be queried for a matching
|
33
|
+
# server. CouchDB manages the creation of that server in the stash, if not found.
|
34
|
+
# If server_option argument is a Server object then then it is added directly to the database object.
|
35
|
+
# No management of the server will be done with CouchDB's server stash.
|
36
|
+
# @raise [ArgumentError] Raised if a server_option is passed in and if that option is neither a Hash nor a Symbol.
|
37
|
+
# @return [Server]
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
def initialize_server( server_option )
|
41
|
+
if server_option
|
42
|
+
if server_option.class == Symbol
|
43
|
+
@server = CouchDB.server( server_option )
|
44
|
+
elsif server_option.class == Aqua::Store::CouchDB::Server
|
45
|
+
@server = server_option # WARNING: this won't get stashed in CouchDB for use with other database.
|
46
|
+
else
|
47
|
+
raise ArgumentError, ":server option must be a symbol identifying a CouchDB server, or a Server object"
|
48
|
+
end
|
49
|
+
else
|
50
|
+
@server = CouchDB.server
|
51
|
+
end
|
52
|
+
@server
|
53
|
+
end
|
54
|
+
|
55
|
+
# Namespaces the database path for the given server. If no name is provided, then the database name is
|
56
|
+
# just the Server's namespace.
|
57
|
+
#
|
58
|
+
# @param [String] Name that the database is initialized with, if any.
|
59
|
+
# @return [String] Namespaced database for use as a http path
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
def namespaced( name )
|
63
|
+
if name
|
64
|
+
"#{server.namespace}_#{CouchDB.escape(@name)}"
|
65
|
+
else
|
66
|
+
server.namespace
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Creates a database representation and PUTs it on the CouchDB server.
|
71
|
+
# If successfull returns a database object. If not successful in creating
|
72
|
+
# the database on the CouchDB server then, false will be returned.
|
73
|
+
#
|
74
|
+
# @see Aqua::Store::CouchDB#initialize for option details
|
75
|
+
# @return [Database, false] Will return the database on success, and false if it did not succeed.
|
76
|
+
#
|
77
|
+
# @api pubilc
|
78
|
+
def self.create( name=nil, opts={} )
|
79
|
+
db = new(name, opts)
|
80
|
+
begin
|
81
|
+
CouchDB.put( db.uri )
|
82
|
+
rescue Exception => e # catch database already exists errors ...
|
83
|
+
unless e.message.match(/412/)
|
84
|
+
db = false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
db
|
88
|
+
end
|
89
|
+
|
90
|
+
# Creates a database representation and PUTs it on the CouchDB server.
|
91
|
+
# This version of the #create method raises an error if the PUT request fails.
|
92
|
+
# The exception on this, is if the database already exists then the 412 HTTP code will be ignored.
|
93
|
+
#
|
94
|
+
# @see Aqua::Store::CouchDB#initialize for option details
|
95
|
+
# @return [Database] Will return the database on success.
|
96
|
+
# @raise HttpAdapter Exceptions depending on the reason for failure.
|
97
|
+
#
|
98
|
+
# @api pubilc
|
99
|
+
def self.create!( name=nil, opts={} )
|
100
|
+
db = new( name, opts )
|
101
|
+
begin
|
102
|
+
CouchDB.put( db.uri )
|
103
|
+
rescue Exception => e # catch database already exists errors ...
|
104
|
+
raise e unless e.class == RequestFailed && e.message.match(/412/)
|
105
|
+
end
|
106
|
+
db
|
107
|
+
end
|
108
|
+
|
109
|
+
# Checks to see if the database exists on the couchdb server.
|
110
|
+
#
|
111
|
+
# @return [true, false] depending on whether the database already exists in CouchDB land
|
112
|
+
#
|
113
|
+
# @api public
|
114
|
+
def exists?
|
115
|
+
begin
|
116
|
+
info
|
117
|
+
true
|
118
|
+
rescue CouchDB::ResourceNotFound
|
119
|
+
false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# GET the database info from CouchDB
|
124
|
+
def info
|
125
|
+
CouchDB.get( uri )
|
126
|
+
end
|
127
|
+
|
128
|
+
# Deletes a database; use with caution as this isn't reversible.
|
129
|
+
#
|
130
|
+
# @return A JSON response on success. nil if the resource is not found. And raises an error if another exception was raised
|
131
|
+
# @raise Exception related to request failure that is not a ResourceNotFound error.
|
132
|
+
def delete
|
133
|
+
begin
|
134
|
+
CouchDB.delete( uri )
|
135
|
+
rescue CouchDB::ResourceNotFound
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Deletes a database; use with caution as this isn't reversible. Similar to #delete,
|
141
|
+
# except that it will raise an error on failure to find the database.
|
142
|
+
#
|
143
|
+
# @return A JSON response on success.
|
144
|
+
# @raise Exception related to request failure or ResourceNotFound.
|
145
|
+
def delete!
|
146
|
+
CouchDB.delete( uri )
|
147
|
+
end
|
148
|
+
|
149
|
+
# # Query the <tt>documents</tt> view. Accepts all the same arguments as view.
|
150
|
+
def documents(params = {})
|
151
|
+
keys = params.delete(:keys)
|
152
|
+
url = CouchDB.paramify_url( "#{uri}/_all_docs", params )
|
153
|
+
if keys
|
154
|
+
CouchDB.post(url, {:keys => keys})
|
155
|
+
else
|
156
|
+
CouchDB.get url
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Deletes all the documents in a given database
|
161
|
+
def delete_all
|
162
|
+
documents['rows'].each do |doc|
|
163
|
+
CouchDB.delete( "#{uri}/#{CGI.escape( doc['id'])}?rev=#{doc['value']['rev']}" ) #rescue nil
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# BULK ACTIVITIES ------------------------------------------
|
168
|
+
def add_to_bulk_cache( doc )
|
169
|
+
if server.uuid_count/2.0 > bulk_cache.size
|
170
|
+
self.bulk_cache << doc
|
171
|
+
else
|
172
|
+
bulk_save
|
173
|
+
self.bulk_cache << doc
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def bulk_save
|
178
|
+
docs = bulk_cache
|
179
|
+
self.bulk_cache = []
|
180
|
+
CouchDB.post( "#{uri}/_bulk_docs", {:docs => docs} )
|
181
|
+
end
|
182
|
+
|
183
|
+
end # Database
|
184
|
+
end # CouchDB
|
185
|
+
end # Store
|
186
|
+
end # Aqua
|