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,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