aqua 0.1.6 → 0.2.0
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.
- data/.gitignore +2 -1
- data/Aqua.gemspec +14 -11
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/aqua.rb +5 -7
- data/lib/aqua/object/config.rb +2 -3
- data/lib/aqua/object/initializers.rb +309 -0
- data/lib/aqua/object/pack.rb +56 -132
- data/lib/aqua/object/query.rb +30 -2
- data/lib/aqua/object/stub.rb +60 -95
- data/lib/aqua/object/tank.rb +1 -0
- data/lib/aqua/object/translator.rb +313 -0
- data/lib/aqua/object/unpack.rb +26 -227
- data/lib/aqua/store/couch_db/couch_db.rb +1 -0
- data/lib/aqua/store/couch_db/database.rb +1 -1
- data/lib/aqua/store/couch_db/design_document.rb +126 -2
- data/lib/aqua/store/couch_db/result_set.rb +36 -0
- data/lib/aqua/store/couch_db/storage_methods.rb +182 -17
- data/lib/aqua/store/storage.rb +4 -48
- data/lib/aqua/support/mash.rb +2 -3
- data/lib/aqua/support/set.rb +4 -16
- data/spec/object/object_fixtures/array_udder.rb +1 -1
- data/spec/object/object_fixtures/persistent.rb +0 -2
- data/spec/object/pack_spec.rb +137 -517
- data/spec/object/query_spec.rb +36 -6
- data/spec/object/stub_spec.rb +10 -9
- data/spec/object/translator_packing_spec.rb +402 -0
- data/spec/object/translator_unpacking_spec.rb +262 -0
- data/spec/object/unpack_spec.rb +162 -320
- data/spec/spec_helper.rb +18 -0
- data/spec/store/couchdb/design_document_spec.rb +148 -7
- data/spec/store/couchdb/result_set_spec.rb +95 -0
- data/spec/store/couchdb/storage_methods_spec.rb +150 -10
- metadata +13 -9
- data/lib/aqua/support/initializers.rb +0 -216
- data/spec/object/object_fixtures/grounded.rb +0 -13
- data/spec/object/object_fixtures/sugar.rb +0 -4
@@ -3,6 +3,7 @@ require File.dirname(__FILE__) + '/server'
|
|
3
3
|
require File.dirname(__FILE__) + '/database'
|
4
4
|
require File.dirname(__FILE__) + '/attachments'
|
5
5
|
require File.dirname(__FILE__) + '/storage_methods'
|
6
|
+
require File.dirname(__FILE__) + '/result_set'
|
6
7
|
require File.dirname(__FILE__) + '/design_document'
|
7
8
|
|
8
9
|
module Aqua
|
@@ -146,7 +146,7 @@ module Aqua
|
|
146
146
|
CouchDB.delete( uri )
|
147
147
|
end
|
148
148
|
|
149
|
-
#
|
149
|
+
# Query the <tt>documents</tt> view. Accepts all the same arguments as view.
|
150
150
|
def documents(params = {})
|
151
151
|
keys = params.delete(:keys)
|
152
152
|
url = CouchDB.paramify_url( "#{uri}/_all_docs", params )
|
@@ -31,8 +31,16 @@ module Aqua
|
|
31
31
|
def initialize( hash={} )
|
32
32
|
hash = Mash.new( hash ) unless hash.empty?
|
33
33
|
self.id = hash.delete(:name) if hash[:name]
|
34
|
-
document_initialize( hash )
|
35
|
-
end
|
34
|
+
document_initialize( hash ) # TODO: can't this just be a call to super?
|
35
|
+
end
|
36
|
+
|
37
|
+
def do_rev( hash )
|
38
|
+
# TODO: This is a temp hack to deal with loading the right revision number so a design doc
|
39
|
+
# can be updated from the document. Without this hack, the rev is nil, and there is a conflict.
|
40
|
+
|
41
|
+
hash.delete(:rev) # This is omited to aleviate confusion
|
42
|
+
# hash.delete(:_rev) # CouchDB determines _rev attribute
|
43
|
+
end
|
36
44
|
|
37
45
|
# couchdb database url for the design document
|
38
46
|
# @return [String] representing CouchDB uri for document
|
@@ -49,6 +57,122 @@ module Aqua
|
|
49
57
|
def update_version( result )
|
50
58
|
self.id = result['id'].gsub(/\A_design\//, '')
|
51
59
|
self.rev = result['rev']
|
60
|
+
end
|
61
|
+
|
62
|
+
# Gets a design document by name.
|
63
|
+
# @param [String] Id/Name of design document
|
64
|
+
# @return [Aqua::Store::CouchDB::DesignDocument]
|
65
|
+
# @api public
|
66
|
+
def self.get( name )
|
67
|
+
design = CouchDB.get( "#{database.uri}/_design/#{CGI.escape(name)}" )
|
68
|
+
new( design )
|
69
|
+
end
|
70
|
+
|
71
|
+
# VIEWS --------------------
|
72
|
+
|
73
|
+
# An array of indexed views for the design document.
|
74
|
+
# @return [Array]
|
75
|
+
# @api public
|
76
|
+
def views
|
77
|
+
self[:views] ||= Mash.new
|
78
|
+
end
|
79
|
+
|
80
|
+
# Adds or updates a view with the given options
|
81
|
+
#
|
82
|
+
# @param [String, Hash] Name of the view, or options hash
|
83
|
+
# @option arg [String] :name The view name, required
|
84
|
+
# @option arg [String] :map Javascript map function, optional
|
85
|
+
# @option arg [String] :reduce Javascript reduce function, optional
|
86
|
+
#
|
87
|
+
# @return [Mash] Map/Reduce mash of javascript functions
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# design_doc << 'attribute_name'
|
91
|
+
# design_doc << {:name => 'attribute_name', :map => 'function(doc){ ... }'}
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def <<( arg )
|
95
|
+
# handle different argument options
|
96
|
+
if [String, Symbol].include?( arg.class )
|
97
|
+
view_name = arg
|
98
|
+
opts = {}
|
99
|
+
elsif arg.class.ancestors.include?( Hash )
|
100
|
+
opts = Mash.new( arg )
|
101
|
+
view_name = opts.delete( :name )
|
102
|
+
raise ArgumentError, 'Option must include a :name that is the view\'s name' unless view_name
|
103
|
+
else
|
104
|
+
raise ArgumentError, "Must be a string or Hash like object of options"
|
105
|
+
end
|
106
|
+
|
107
|
+
# build the map/reduce query
|
108
|
+
map = opts[:map]
|
109
|
+
reduce = opts[:reduce]
|
110
|
+
views # to initialize self[:views]
|
111
|
+
self[:views][view_name] = {
|
112
|
+
:map => map || build_map( view_name, opts[:class_constraint] ),
|
113
|
+
}
|
114
|
+
self[:views][view_name][:reduce] = reduce if reduce
|
115
|
+
self[:views][view_name]
|
116
|
+
end
|
117
|
+
|
118
|
+
alias :add :<<
|
119
|
+
|
120
|
+
def add!( arg )
|
121
|
+
self << arg
|
122
|
+
save!
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
# Builds a generic map assuming that the view_name is the name of a document attribute.
|
127
|
+
# @param [String, Symbol] Name of document attribute
|
128
|
+
# @param [Class, String] Optional constraint on to limit view to a given class
|
129
|
+
# @return [String] Javascript map function
|
130
|
+
#
|
131
|
+
# @api private
|
132
|
+
def build_map( view_name, class_constraint=nil )
|
133
|
+
class_constraint = if class_constraint.class == Class
|
134
|
+
" && doc['type'] == '#{class_constraint}'"
|
135
|
+
elsif class_constraint.class == String
|
136
|
+
" && #{class_constraint}"
|
137
|
+
end
|
138
|
+
"function(doc) {
|
139
|
+
if( doc['#{view_name}'] #{class_constraint}){
|
140
|
+
emit( doc['#{view_name}'], 1 );
|
141
|
+
}
|
142
|
+
}"
|
143
|
+
end
|
144
|
+
public
|
145
|
+
|
146
|
+
# group=true Version 0.8.0 and forward
|
147
|
+
# group_level=int
|
148
|
+
# reduce=false Trunk only (0.9)
|
149
|
+
|
150
|
+
def query( view_name, opts={} )
|
151
|
+
opts = Mash.new( opts ) unless opts.empty?
|
152
|
+
doc_class = opts[:document_class]
|
153
|
+
|
154
|
+
params = []
|
155
|
+
params << 'include_docs=true' unless (opts[:select] && opts[:select] != 'all')
|
156
|
+
# TODO: this is according to couchdb really inefficent with large sets of data.
|
157
|
+
# A better way would involve, using start and end keys with limit. But this
|
158
|
+
# is a really hard one to figure with jumping around to different pages
|
159
|
+
params << "skip=#{opts[:offset]}" if opts[:offset]
|
160
|
+
params << "limit=#{opts[:limit]}" if opts[:limit]
|
161
|
+
params << "key=#{opts[:equals]}" if opts[:equals]
|
162
|
+
if opts[:order].to_s == 'desc' || opts[:order].to_s == 'descending'
|
163
|
+
desc = true
|
164
|
+
params << "descending=true"
|
165
|
+
end
|
166
|
+
if opts[:range] && opts[:range].size == 2
|
167
|
+
params << "startkey=#{opts[:range][desc == true ? 1 : 0 ]}"
|
168
|
+
params << "endkey=#{opts[:range][desc == true ? 0 : 1]}"
|
169
|
+
end
|
170
|
+
|
171
|
+
query_uri = "#{uri}/_view/#{CGI.escape(view_name.to_s)}?"
|
172
|
+
query_uri << params.join('&')
|
173
|
+
|
174
|
+
result = CouchDB.get( query_uri )
|
175
|
+
opts[:reduced] ? result['rows'].first['value'] : ResultSet.new( result, doc_class )
|
52
176
|
end
|
53
177
|
|
54
178
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Aqua
|
2
|
+
module Store
|
3
|
+
module CouchDB
|
4
|
+
class ResultSet < Array
|
5
|
+
|
6
|
+
def self.document_class
|
7
|
+
@document_class
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.document_class=( klass )
|
11
|
+
@document_class = klass
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :offset, :total, :rows, :document_class
|
15
|
+
|
16
|
+
def initialize( response, doc_class=nil )
|
17
|
+
self.document_class = doc_class || self.class.document_class
|
18
|
+
self.total = response['total_rows']
|
19
|
+
self.offset = response['offset']
|
20
|
+
self.rows = response['rows']
|
21
|
+
results = if rows && rows.first && rows.first['doc']
|
22
|
+
if document_class
|
23
|
+
rows.collect{ |h| document_class.new( h['doc'] ) }
|
24
|
+
else
|
25
|
+
rows.collect{ |h| h['doc'] }
|
26
|
+
end
|
27
|
+
else
|
28
|
+
rows.collect{ |h| h['key'] }
|
29
|
+
end
|
30
|
+
super( results )
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -64,14 +64,33 @@ module Aqua
|
|
64
64
|
@database = db
|
65
65
|
end
|
66
66
|
|
67
|
-
#
|
67
|
+
# Gets a document from the database based on id
|
68
68
|
# @param [String] id
|
69
69
|
# @return [Hash] representing the CouchDB data
|
70
70
|
# @api public
|
71
71
|
def get( id )
|
72
|
-
|
72
|
+
resource = begin # this is just in case the developer has already escaped the name
|
73
|
+
CouchDB.get( "#{database.uri}/#{CGI.escape(id)}" )
|
74
|
+
rescue
|
75
|
+
CouchDB.get( "#{database.uri}/#{id}" )
|
76
|
+
end
|
77
|
+
new( resource )
|
73
78
|
end
|
74
79
|
|
80
|
+
# Will find a document by id, or create it if it doesn't exist. Alias is :get!
|
81
|
+
# @param [String] id
|
82
|
+
# @return [Hash] representing the CouchDB resource
|
83
|
+
# @api public
|
84
|
+
def find_or_create( id )
|
85
|
+
begin
|
86
|
+
get( id )
|
87
|
+
rescue
|
88
|
+
create!( :id => id )
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
alias :get! :find_or_create
|
93
|
+
|
75
94
|
# Retrieves an attachment when provided the document id and attachment id, or the combined id
|
76
95
|
#
|
77
96
|
# @return [Tempfile]
|
@@ -81,10 +100,149 @@ module Aqua
|
|
81
100
|
new( :id => document_id ).attachments.get!( attachment_id )
|
82
101
|
end
|
83
102
|
|
84
|
-
#
|
103
|
+
# Accessor for maintaining connection to aquatic class
|
104
|
+
# @api private
|
105
|
+
attr_accessor :parent_class
|
106
|
+
alias :design_name= :parent_class=
|
107
|
+
alias :design_name :parent_class
|
108
|
+
|
109
|
+
# Finds or creates design document based on aqua parent class name
|
110
|
+
# @api semi-private
|
111
|
+
def design_document( reload=false )
|
112
|
+
@design_document = nil if reload
|
113
|
+
@design_document ||= design_name ? DesignDocument.find_or_create( design_name ) : nil
|
114
|
+
end
|
115
|
+
|
116
|
+
# Stores stores a map name for a given index, allowing the same map
|
117
|
+
# to be used for various reduce functions. This means only one index is created.
|
118
|
+
# @param [String] field being indexed, or the sub field
|
119
|
+
# @param [Hash, Mash] Hash of functions used to create views
|
120
|
+
# @option opts [String] :map Javascript/CouchDB map function
|
121
|
+
# @option opts [String] :reduce Javascript/CouchDB reduce function
|
122
|
+
#
|
123
|
+
# @api public
|
85
124
|
def index_on( field, opts={} )
|
86
|
-
|
87
|
-
|
125
|
+
opts = Mash.new( opts )
|
126
|
+
design_document(true).add!( opts.merge!(:name => field) )
|
127
|
+
unless indexes.include?( field )
|
128
|
+
indexes << field.to_sym
|
129
|
+
indexes << field.to_s
|
130
|
+
end
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
# This is an aqua specific indexer whereas index_on is a more generic CouchDB indexer.
|
135
|
+
# This method seeks out the designated ivar in an Aqua structured document. Future iterations
|
136
|
+
# should go deeper into the ivar to reduce the overall size of the index, and make for more
|
137
|
+
# usable searches.
|
138
|
+
#
|
139
|
+
# @api private
|
140
|
+
def index_on_ivar( field )
|
141
|
+
index_on( field,
|
142
|
+
:map => "
|
143
|
+
function(doc) {
|
144
|
+
if( doc['class'] == '#{parent_class}' &&
|
145
|
+
doc['ivars'] && doc['ivars']['@#{field}'] ){
|
146
|
+
emit( doc['ivars']['@#{field}'], 1 );
|
147
|
+
}
|
148
|
+
}
|
149
|
+
"
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
# A list of index names that can be used to build other reduce functions.
|
154
|
+
# @api semi-private
|
155
|
+
def indexes
|
156
|
+
@indexes ||= []
|
157
|
+
end
|
158
|
+
|
159
|
+
# @api public
|
160
|
+
def query( index, opts={} )
|
161
|
+
raise ArgumentError, 'Index not found' unless views.include?( index.to_s )
|
162
|
+
opts = Mash.new(opts)
|
163
|
+
opts.merge!(:document_class => self) unless opts[:document_class]
|
164
|
+
opts.merge!(:reduced => design_document.views[index][:reduce] ? true : false )
|
165
|
+
design_document.query( index, opts )
|
166
|
+
end
|
167
|
+
|
168
|
+
def reduced_query( reduce_type, index, opts)
|
169
|
+
view = "#{index}_#{reduce_type}"
|
170
|
+
unless views.include?( view )
|
171
|
+
design_document(true).add!(
|
172
|
+
:name => view,
|
173
|
+
:map => design_document.views[ index.to_s ][:map],
|
174
|
+
:reduce => opts[:reduce]
|
175
|
+
)
|
176
|
+
end
|
177
|
+
query( view, opts.merge!( :select => "index only" ) )
|
178
|
+
end
|
179
|
+
|
180
|
+
# @api semi-private
|
181
|
+
def views
|
182
|
+
design_document.views.keys
|
183
|
+
end
|
184
|
+
|
185
|
+
# @api public
|
186
|
+
def count( index, opts={} )
|
187
|
+
opts = Mash.new(opts)
|
188
|
+
opts[:reduce] = "
|
189
|
+
function (key, values, rereduce) {
|
190
|
+
return sum(values);
|
191
|
+
}" unless opts[:reduce]
|
192
|
+
reduced_query(:count, index, opts)
|
193
|
+
end
|
194
|
+
|
195
|
+
# @api public
|
196
|
+
def sum( index, opts={} )
|
197
|
+
opts = Mash.new(opts)
|
198
|
+
opts[:reduce] = "
|
199
|
+
function (keys, values, rereduce) {
|
200
|
+
var key_values = []
|
201
|
+
keys.forEach( function(key) {
|
202
|
+
key_values[key_values.length] = key[0]
|
203
|
+
});
|
204
|
+
return sum( key_values );
|
205
|
+
}" unless opts[:reduce]
|
206
|
+
reduced_query(:sum, index, opts)
|
207
|
+
end
|
208
|
+
|
209
|
+
def average( index, opts={} )
|
210
|
+
sum(index, opts) / count(index, opts).to_f
|
211
|
+
end
|
212
|
+
|
213
|
+
alias :avg :average
|
214
|
+
|
215
|
+
def min( index, opts={} )
|
216
|
+
opts = Mash.new(opts)
|
217
|
+
opts[:reduce] = "
|
218
|
+
function (keys, values, rereduce) {
|
219
|
+
var key_values = []
|
220
|
+
keys.forEach( function(key) {
|
221
|
+
key_values[key_values.length] = key[0]
|
222
|
+
});
|
223
|
+
return Math.min.apply( Math, key_values ); ;
|
224
|
+
}" unless opts[:reduce]
|
225
|
+
reduced_query(:min, index, opts)
|
226
|
+
end
|
227
|
+
|
228
|
+
alias :minimum :min
|
229
|
+
|
230
|
+
def max( index, opts={} )
|
231
|
+
opts = Mash.new(opts)
|
232
|
+
opts[:reduce] = "
|
233
|
+
function (keys, values, rereduce) {
|
234
|
+
var key_values = []
|
235
|
+
keys.forEach( function(key) {
|
236
|
+
key_values[key_values.length] = key[0]
|
237
|
+
});
|
238
|
+
return Math.max.apply( Math, key_values ); ;
|
239
|
+
}" unless opts[:reduce]
|
240
|
+
reduced_query(:max, index, opts)
|
241
|
+
end
|
242
|
+
|
243
|
+
alias :maximum :max
|
244
|
+
|
245
|
+
|
88
246
|
end
|
89
247
|
|
90
248
|
module InstanceMethods
|
@@ -96,17 +254,23 @@ module Aqua
|
|
96
254
|
# @api public
|
97
255
|
def initialize( hash={} )
|
98
256
|
hash = Mash.new( hash ) unless hash.empty?
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
257
|
+
|
258
|
+
hashed_id = hash.delete(:id) || hash.delete(:_id)
|
259
|
+
self.id = hashed_id if hashed_id
|
106
260
|
|
261
|
+
do_rev( hash )
|
262
|
+
|
107
263
|
# feed the rest of the hash to the super
|
108
264
|
super( hash )
|
109
|
-
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Temporary hack to allow design document refresh from within a doc.
|
268
|
+
# @todo The get method has to handle rev better!!!
|
269
|
+
def do_rev( hash, override=false )
|
270
|
+
hash.delete(:rev) # This is omited to aleviate confusion
|
271
|
+
# CouchDB determines _rev attribute on saving, but when #new is loading json passed from the
|
272
|
+
# database rev needs to be added to the class. So, the :_rev param is not being deleted anymore
|
273
|
+
end
|
110
274
|
|
111
275
|
# Saves an Aqua::Storage instance to CouchDB as a document. Save can be deferred for bulk saving.
|
112
276
|
#
|
@@ -191,8 +355,10 @@ module Aqua
|
|
191
355
|
# @return [Hash] representing the CouchDB data
|
192
356
|
# @api public
|
193
357
|
def retrieve
|
194
|
-
self.class.
|
195
|
-
end
|
358
|
+
self.class.get( id )
|
359
|
+
end
|
360
|
+
|
361
|
+
alias :reload :retrieve
|
196
362
|
|
197
363
|
# reloads self from CouchDB database
|
198
364
|
# @return [Hash] representing CouchDB data
|
@@ -395,8 +561,7 @@ module Aqua
|
|
395
561
|
# @api public
|
396
562
|
def attachments
|
397
563
|
@attachments ||= Attachments.new( self )
|
398
|
-
end
|
399
|
-
|
564
|
+
end
|
400
565
|
end # InstanceMethods
|
401
566
|
|
402
567
|
end # StoreMethods
|
data/lib/aqua/store/storage.rb
CHANGED
@@ -1,58 +1,14 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# module should minimally provide this interface to the object:
|
4
|
-
#
|
5
|
-
# InstanceMethods
|
6
|
-
# @inteface_level optional If no initialization is included. Then the Mash initialization will be used.
|
7
|
-
# initialize( hash )
|
8
|
-
# @param [optional Hash]
|
9
|
-
# @return [Aqua::Storage] the new storage instance
|
10
|
-
#
|
11
|
-
# @interface_level mandatory
|
12
|
-
# commit
|
13
|
-
# @return [Aqua::Storage] saved storage object
|
14
|
-
# @raise [Aqua::ObjectNotFound] if another error occurs in the engine, that should be raised instead
|
15
|
-
# any of the Aqua Exceptions
|
16
|
-
#
|
17
|
-
# @interface_level mandatory
|
18
|
-
# id
|
19
|
-
# @param none
|
20
|
-
# @return id object, whether String, Fixnum or other object as the store chooses
|
21
|
-
#
|
22
|
-
# @interface_level mandatory
|
23
|
-
# id=( custom_id )
|
24
|
-
# The library expects to save an object with a custom id. Id= method can set limits on the
|
25
|
-
# types of objects that can be used as an id. Minimally it should support Strings.
|
26
|
-
# @param String, Fixnum, or any other reasonable class
|
27
|
-
#
|
28
|
-
# @interface_level mandatory
|
29
|
-
# new?
|
30
|
-
# The equivalent of AR's new_record? and can be used to set create hooks or determine how to handle
|
31
|
-
# object queries about whether it has changed.
|
32
|
-
# @return [true, false]
|
33
|
-
#
|
34
|
-
# ClassMethods
|
35
|
-
# @interface_level mandatory
|
36
|
-
# load( id, class )
|
37
|
-
# The'load'
|
38
|
-
# @param [String, Fixnum] The id used by the system to
|
39
|
-
# @return [Aqua::Storage]
|
40
|
-
# @raise [Aqua::ResourceNotFound] if another error occurs in the engine, that should be raised instead
|
41
|
-
# any of the Aqua Exceptions
|
42
|
-
#
|
43
|
-
# Other methods used for the storage engine can be added as needed by the engine.
|
44
|
-
#
|
45
|
-
# If no storage engine is configured before this class is used, CouchDB will automatically be used
|
46
|
-
# as an engine.
|
1
|
+
# When the api using CouchDB is stable, an interface and tests need to be created so that aqua can be extended
|
2
|
+
# to other storage engines.
|
47
3
|
module Aqua
|
48
4
|
class Storage < Mash
|
49
5
|
# auto loads the default store to CouchDB if Store is used without Aqua configuration of a store
|
50
6
|
def method_missing( method, *args )
|
51
7
|
if respond_to?( :commit )
|
52
|
-
raise NoMethodError
|
8
|
+
raise NoMethodError, "#{method} undefined for #{self.inspect}"
|
53
9
|
else
|
54
10
|
Aqua.set_storage_engine # to default, currently CouchDB
|
55
|
-
send( method.to_sym,
|
11
|
+
send( method.to_sym, *args ) # resend!
|
56
12
|
end
|
57
13
|
end
|
58
14
|
end # Storage
|