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,59 @@
1
+ # This is the interface between an individual object and the Storage representation.
2
+ # The storage engine should provide a module that gets included into the Mash. The
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.
47
+ module Aqua
48
+ class Storage < Mash
49
+ # auto loads the default store to CouchDB if Store is used without Aqua configuration of a store
50
+ def method_missing( method, *args )
51
+ if respond_to?( :commit )
52
+ raise NoMethodError
53
+ else
54
+ Aqua.set_storage_engine # to default, currently CouchDB
55
+ send( method.to_sym, eval(args.map{|value| "'#{value}'"}.join(', ')) ) # resend!
56
+ end
57
+ end
58
+ end # Storage
59
+ end # Aqua
@@ -0,0 +1,216 @@
1
+ # AQUA INITIALIZATION
2
+ # Some object store state in a fundamental way, not in instance variables, that needs to be initialized.
3
+ # Examples: Array, Numeric types, Hashes, Time ...
4
+ # You can make any object requiring this initialization savable to aqua by
5
+ # * including the Aqua::To module and extending the Aqua::From module
6
+ # * building your own methods for #to_aqua, #to_aqua_init, MyClass.aqua_init
7
+ # See set.rb in this file for more an example
8
+ module Aqua
9
+ module Initializers
10
+ def self.included( klass )
11
+ klass.class_eval do
12
+ include InstanceMethods
13
+ extend ClassMethods
14
+
15
+ unless methods.include?( :hide_attributes )
16
+ include Aqua::Pack::HiddenAttributes
17
+ end
18
+ end
19
+ end
20
+
21
+ module InstanceMethods
22
+ def to_aqua( base_object )
23
+ hash = {
24
+ 'class' => to_aqua_class,
25
+ 'init' => to_aqua_init( base_object )
26
+ }
27
+ ivars = _pack_instance_vars( base_object )
28
+ hash.merge!( ivars ) if ivars
29
+ hash
30
+ end
31
+
32
+ def to_aqua_class
33
+ self.class.to_s
34
+ end
35
+
36
+ def _pack_instance_vars( base_object )
37
+ { 'ivars' => base_object._pack_ivars( self ) } if instance_variables.size > 0
38
+ end
39
+
40
+ def to_aqua_init( base_object )
41
+ self.to_s
42
+ end
43
+ end # InstanceMethods
44
+
45
+ module ClassMethods
46
+ def aqua_init( init )
47
+ new( init )
48
+ end
49
+ end # ClassMethods
50
+
51
+ end # Initializers
52
+
53
+ end
54
+
55
+ [ TrueClass, FalseClass, Time, Date, Fixnum, Bignum, Float, Rational, Hash, Array, OpenStruct, Range, File, Tempfile].each do |klass|
56
+ klass.class_eval { include Aqua::Initializers }
57
+ end
58
+
59
+ class TrueClass
60
+ def self.aqua_init( init )
61
+ true
62
+ end
63
+
64
+ def to_aqua( base_object )
65
+ true
66
+ end
67
+ end
68
+
69
+ class FalseClass
70
+ def self.aqua_init( init )
71
+ false
72
+ end
73
+
74
+ def to_aqua( base_object )
75
+ false
76
+ end
77
+ end
78
+
79
+ class Date
80
+ hide_attributes :sg, :of, :ajd
81
+
82
+ def self.aqua_init( init )
83
+ parse( init )
84
+ end
85
+ end
86
+
87
+ class Time
88
+ def self.aqua_init( init )
89
+ parse( init )
90
+ end
91
+ end
92
+
93
+ class Fixnum
94
+ def self.aqua_init( init )
95
+ init.to_i
96
+ end
97
+ end
98
+
99
+ class Bignum
100
+ def self.aqua_init( init )
101
+ init.to_i
102
+ end
103
+ end
104
+
105
+ class Float
106
+ def self.aqua_init( init )
107
+ init.to_f
108
+ end
109
+ end
110
+
111
+ class Range
112
+ def self.aqua_init( init )
113
+ eval( init )
114
+ end
115
+ end
116
+
117
+ class Rational
118
+ def to_aqua_init( base_object )
119
+ self.to_s.match(/(\d*)\/(\d*)/).to_a.slice(1,2)
120
+ end
121
+
122
+ def self.aqua_init( init )
123
+ Rational( init[0].to_i, init[1].to_i )
124
+ end
125
+ end
126
+
127
+ class Hash
128
+ def to_aqua_init( base_object )
129
+ return_hash = {}
130
+ self.each do |raw_key, value|
131
+ key_class = raw_key.class
132
+ if key_class == Symbol
133
+ key = ":#{raw_key.to_s}"
134
+ elsif key_class == String
135
+ key = raw_key
136
+ else
137
+ index = base_object._build_object_key( raw_key )
138
+ key = "/OBJECT_#{index}"
139
+ end
140
+ return_hash[key] = base_object._pack_object( value )
141
+ end
142
+ return_hash
143
+ end
144
+
145
+ def self.aqua_init( init )
146
+ new.replace( init )
147
+ end
148
+ end
149
+
150
+ class Array
151
+ def to_aqua_init( base_object )
152
+ return_arr = []
153
+ self.each do |obj|
154
+ return_arr << base_object._pack_object( obj )
155
+ end
156
+ return_arr
157
+ end
158
+
159
+ def self.aqua_init( init )
160
+ new.replace( init )
161
+ end
162
+ end
163
+
164
+ class OpenStruct
165
+ hide_attributes :table
166
+
167
+ def to_aqua_init( base_object )
168
+ instance_variable_get("@table").to_aqua_init( base_object )
169
+ end
170
+ end
171
+
172
+ module Aqua
173
+ module FileInitializations
174
+ def to_aqua( base_object )
175
+ hash = {
176
+ 'class' => to_aqua_class,
177
+ 'init' => to_aqua_init( base_object ),
178
+ 'methods' => {
179
+ 'content_type' => MIME::Types.type_for( path ).first,
180
+ 'content_length' => stat.size
181
+ }
182
+ }
183
+ ivars = _pack_instance_vars( base_object )
184
+ hash.merge!( ivars ) if ivars
185
+ hash
186
+ end
187
+
188
+ def to_aqua_class
189
+ 'Aqua::FileStub'
190
+ end
191
+
192
+ def filename
193
+ path.match(/([^\/]*)\z/).to_s
194
+ end
195
+
196
+ def to_aqua_init( base_object )
197
+ name = filename
198
+ base_object._pack_file(name, self)
199
+ "/FILE_#{name}"
200
+ end
201
+ end # FileInitializations
202
+ end # Aqua
203
+
204
+ class File
205
+ include Aqua::FileInitializations
206
+ end
207
+
208
+ class Tempfile
209
+ include Aqua::FileInitializations
210
+
211
+ hide_attributes :clean_proc, :data, :tmpname, :tmpfile, :_dc_obj
212
+
213
+ def filename
214
+ path.match(/([^\/]*)\.\d*\.\d*\z/).captures.first
215
+ end
216
+ end
@@ -0,0 +1,144 @@
1
+ # Taken from RAILS, see RAIL'S LICENSE for usage
2
+
3
+ # This class has dubious semantics and we only have it so that
4
+ # people can write params[:key] instead of params['key']
5
+ # and they get the same value for both keys.
6
+ unless defined?(HashWithIndifferentAccess)
7
+ class Hash
8
+ def with_indifferent_access
9
+ hash = HashWithIndifferentAccess.new(self)
10
+ hash.default = self.default
11
+ hash
12
+ end
13
+ end
14
+
15
+ class HashWithIndifferentAccess < Hash
16
+ def initialize(constructor = {})
17
+ if constructor.is_a?(Hash)
18
+ super()
19
+ update(constructor)
20
+ else
21
+ super(constructor)
22
+ end
23
+ end
24
+
25
+ def default(key = nil)
26
+ if key.is_a?(Symbol) && include?(key = key.to_s)
27
+ self[key]
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
34
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
35
+
36
+ # Assigns a new value to the hash:
37
+ #
38
+ # hash = HashWithIndifferentAccess.new
39
+ # hash[:key] = "value"
40
+ #
41
+ def []=(key, value)
42
+ regular_writer(convert_key(key), convert_value(value))
43
+ end
44
+
45
+ # Updates the instantized hash with values from the second:
46
+ #
47
+ # hash_1 = HashWithIndifferentAccess.new
48
+ # hash_1[:key] = "value"
49
+ #
50
+ # hash_2 = HashWithIndifferentAccess.new
51
+ # hash_2[:key] = "New Value!"
52
+ #
53
+ # hash_1.update(hash_2) # => {"key"=>"New Value!"}
54
+ #
55
+ def update(other_hash)
56
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
57
+ self
58
+ end
59
+
60
+ alias_method :merge!, :update
61
+
62
+ # Checks the hash for a key matching the argument passed in:
63
+ #
64
+ # hash = HashWithIndifferentAccess.new
65
+ # hash["key"] = "value"
66
+ # hash.key? :key # => true
67
+ # hash.key? "key" # => true
68
+ #
69
+ def key?(key)
70
+ super(convert_key(key))
71
+ end
72
+
73
+ alias_method :include?, :key?
74
+ alias_method :has_key?, :key?
75
+ alias_method :member?, :key?
76
+
77
+ # Fetches the value for the specified key, same as doing hash[key]
78
+ def fetch(key, *extras)
79
+ super(convert_key(key), *extras)
80
+ end
81
+
82
+ # Returns an array of the values at the specified indices:
83
+ #
84
+ # hash = HashWithIndifferentAccess.new
85
+ # hash[:a] = "x"
86
+ # hash[:b] = "y"
87
+ # hash.values_at("a", "b") # => ["x", "y"]
88
+ #
89
+ def values_at(*indices)
90
+ indices.collect {|key| self[convert_key(key)]}
91
+ end
92
+
93
+ # Returns an exact copy of the hash.
94
+ def dup
95
+ HashWithIndifferentAccess.new(self)
96
+ end
97
+
98
+ # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
99
+ # Does not overwrite the existing hash.
100
+ def merge(hash)
101
+ self.dup.update(hash)
102
+ end
103
+
104
+ # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
105
+ # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
106
+ def reverse_merge(other_hash)
107
+ super other_hash.with_indifferent_access
108
+ end
109
+
110
+ # Removes a specified key from the hash.
111
+ def delete(key)
112
+ super(convert_key(key))
113
+ end
114
+
115
+ def stringify_keys!; self end
116
+ def symbolize_keys!; self end
117
+ def to_options!; self end
118
+
119
+ # Convert to a Hash with String keys.
120
+ def to_hash
121
+ Hash.new(default).merge(self)
122
+ end
123
+
124
+ protected
125
+ def convert_key(key)
126
+ key.kind_of?(Symbol) ? key.to_s : key
127
+ end
128
+
129
+ def convert_value(value)
130
+ case value
131
+ when Hash
132
+ value.with_indifferent_access
133
+ when Array
134
+ value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
135
+ else
136
+ value
137
+ end
138
+ end
139
+ end
140
+
141
+ Mash = HashWithIndifferentAccess unless defined?( Mash ) # because Mash is easier to write, thanks Merb!
142
+
143
+ end
144
+
@@ -0,0 +1,23 @@
1
+ # require this file if you want to use sets with your Aquatic objects
2
+ require 'set'
3
+
4
+ class Set
5
+ include Aqua::Initializers
6
+
7
+ def to_aqua( base_object )
8
+ hash = {
9
+ 'class' => self.class.to_s,
10
+ 'init' => to_aqua_init( base_object )
11
+ }
12
+ if (instance_variables - ['@hash']).size > 0
13
+ hash.merge!({ 'ivars' => base_object._pack_ivars( self ) })
14
+ end
15
+ hash
16
+ end
17
+
18
+ def to_aqua_init( base_object )
19
+ # keys returns an array
20
+ # to_aqua_init will ensure that each of the objects is unpacked to aqua
21
+ instance_variable_get("@hash").keys.to_aqua_init( base_object )
22
+ end
23
+ end
@@ -0,0 +1,121 @@
1
+ # This is taken from Extlib. Their license applies:
2
+ # Copyright (c) 2008 Sam Smoot.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+ # These couple of methods are added to the String class
24
+ # for easy file negotiation and metaprogramming.
25
+ # Module and methods are only loaded when ActiveSupport
26
+ # and Extlib is not detected
27
+ unless defined?( ActiveSupport ) || defined?( Extlib )
28
+ module Extlib
29
+
30
+ # = English Nouns Number Inflection.
31
+ #
32
+ # This module provides english singular <-> plural noun inflections.
33
+ module Inflection
34
+
35
+ class << self
36
+ # Take an underscored name and make it into a camelized name
37
+ #
38
+ # @example
39
+ # "egg_and_hams".classify #=> "EggAndHam"
40
+ # "post".classify #=> "Post"
41
+ #
42
+ def classify(name)
43
+ camelize(singularize(name.to_s.sub(/.*\./, '')))
44
+ end
45
+
46
+ # By default, camelize converts strings to UpperCamelCase.
47
+ #
48
+ # camelize will also convert '/' to '::' which is useful for converting paths to namespaces
49
+ #
50
+ # @example
51
+ # "active_record".camelize #=> "ActiveRecord"
52
+ # "active_record/errors".camelize #=> "ActiveRecord::Errors"
53
+ #
54
+ def camelize(lower_case_and_underscored_word, *args)
55
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
56
+ end
57
+
58
+
59
+ # The reverse of +camelize+. Makes an underscored form from the expression in the string.
60
+ #
61
+ # Changes '::' to '/' to convert namespaces to paths.
62
+ #
63
+ # @example
64
+ # "ActiveRecord".underscore #=> "active_record"
65
+ # "ActiveRecord::Errors".underscore #=> active_record/errors
66
+ #
67
+ def underscore(camel_cased_word)
68
+ camel_cased_word.to_s.gsub(/::/, '/').
69
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
70
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
71
+ tr("-", "_").
72
+ downcase
73
+ end
74
+
75
+ # Capitalizes the first word and turns underscores into spaces and strips _id.
76
+ # Like titleize, this is meant for creating pretty output.
77
+ #
78
+ # @example
79
+ # "employee_salary" #=> "Employee salary"
80
+ # "author_id" #=> "Author"
81
+ def humanize(lower_case_and_underscored_word)
82
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
83
+ end
84
+
85
+ # Constantize tries to find a declared constant with the name specified
86
+ # in the string. It raises a NameError when the name is not in CamelCase
87
+ # or is not initialized.
88
+ #
89
+ # @example
90
+ # "Module".constantize #=> Module
91
+ # "Class".constantize #=> Class
92
+ def constantize(camel_cased_word)
93
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
94
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
95
+ end
96
+
97
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+ class String
105
+ def constantize
106
+ Extlib::Inflection.constantize( self )
107
+ end
108
+
109
+ def humanize
110
+ Extlib::Inflection.humanize( self )
111
+ end
112
+
113
+ def underscore
114
+ Extlib::Inflection.underscore( self )
115
+ end
116
+
117
+ def classify
118
+ Extlib::Inflection.classify( self )
119
+ end
120
+ end
121
+ end