og 0.7.0 → 0.8.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.
Files changed (51) hide show
  1. data/AUTHORS +14 -4
  2. data/ChangeLog +192 -1
  3. data/README.og +2 -1
  4. data/RELEASES.og +35 -0
  5. data/Rakefile +1 -1
  6. data/examples/og/mock_example.rb +6 -9
  7. data/examples/og/mysql_to_psql.rb +100 -0
  8. data/examples/og/run.rb +8 -17
  9. data/lib/glue/array.rb +1 -1
  10. data/lib/glue/attribute.rb +86 -0
  11. data/lib/glue/cache.rb +1 -1
  12. data/lib/glue/hash.rb +1 -1
  13. data/lib/glue/inflector.rb +1 -1
  14. data/lib/glue/logger.rb +118 -18
  15. data/lib/glue/mixins.rb +1 -1
  16. data/lib/glue/number.rb +1 -1
  17. data/lib/glue/pool.rb +1 -1
  18. data/lib/glue/property.rb +48 -31
  19. data/lib/glue/string.rb +1 -1
  20. data/lib/glue/time.rb +2 -2
  21. data/lib/glue/validation.rb +400 -0
  22. data/lib/glue.rb +7 -8
  23. data/lib/og/backend.rb +47 -46
  24. data/lib/og/backends/mysql.rb +64 -63
  25. data/lib/og/backends/psql.rb +73 -72
  26. data/lib/og/connection.rb +7 -8
  27. data/lib/og/enchant.rb +80 -0
  28. data/lib/og/meta.rb +21 -21
  29. data/lib/og/mock.rb +31 -88
  30. data/lib/og/version.rb +6 -5
  31. data/lib/og.rb +95 -129
  32. data/test/tc_og.rb +3 -3
  33. data/vendor/extensions/_base.rb +153 -0
  34. data/vendor/extensions/_template.rb +36 -0
  35. data/vendor/extensions/all.rb +21 -0
  36. data/vendor/extensions/array.rb +68 -0
  37. data/vendor/extensions/binding.rb +224 -0
  38. data/vendor/extensions/class.rb +50 -0
  39. data/vendor/extensions/continuation.rb +71 -0
  40. data/vendor/extensions/enumerable.rb +250 -0
  41. data/vendor/extensions/hash.rb +23 -0
  42. data/vendor/extensions/io.rb +58 -0
  43. data/vendor/extensions/kernel.rb +42 -0
  44. data/vendor/extensions/module.rb +114 -0
  45. data/vendor/extensions/numeric.rb +230 -0
  46. data/vendor/extensions/object.rb +164 -0
  47. data/vendor/extensions/ostruct.rb +41 -0
  48. data/vendor/extensions/string.rb +316 -0
  49. data/vendor/extensions/symbol.rb +28 -0
  50. metadata +24 -4
  51. data/lib/glue/property.rb.old +0 -307
@@ -0,0 +1,400 @@
1
+ # George Moschovitis <gm@navel.gr>
2
+ # (c) 2005 Navel, all rights reserved.
3
+ # $Id$
4
+
5
+ module N
6
+
7
+ # = Validation
8
+ #
9
+ # Implements a meta-language for validating managed
10
+ # objects. Typically used in Validator objects but can be
11
+ # included in managed objects too.
12
+ #
13
+ # == Example
14
+ #
15
+ # class User
16
+ # prop_accessor :name, String
17
+ # prop_accessor :level, Fixnum
18
+ #
19
+ # validate_length :name, :range => 2..6
20
+ # validate_unique :name, :msg => :name_allready_exists
21
+ # validate_format :name, :format => /[a-z]*/, :msg => 'invalid format', :on => :create
22
+ # end
23
+ #
24
+ # class N::CustomUserValidator
25
+ # include N::Validation
26
+ # validate_length :name, :range => 2..6, :msg_short => :name_too_short, :msg_long => :name_too_long
27
+ # end
28
+ #
29
+ # user = @request.fill(User.new)
30
+ # user.level = 15
31
+ #
32
+ # unless user.valid?
33
+ # user.save
34
+ # else
35
+ # p user.errors[:name]
36
+ # end
37
+ #
38
+ # unless user.save
39
+ # p user.errors.on(:name)
40
+ # end
41
+ #
42
+ # unless errors = N::CustomUserValidator.errors(user)
43
+ # user.save
44
+ # else
45
+ # p errors[:name]
46
+ # end
47
+ #
48
+ #--
49
+ # TODO: all validation methods should imply validate_value
50
+ # TODO: add validate_unique
51
+ #++
52
+
53
+ module Validation
54
+
55
+ # = Errors
56
+ #
57
+ # Encapsulates a list of validation errors.
58
+
59
+ class Errors
60
+ attr_accessor :errors
61
+
62
+ cattr_accessor :no_value, 'No value provided'
63
+ cattr_accessor :no_confirmation, 'Invalid confirmation'
64
+ cattr_accessor :invalid_format, 'Invalid format'
65
+ cattr_accessor :too_short, 'Too short, must be more than %d characters long'
66
+ cattr_accessor :too_long, 'Too long, must be less than %d characters long'
67
+ cattr_accessor :invalid_length, 'Must be %d characters long'
68
+ cattr_accessor :no_inclusion, 'The value is invalid'
69
+
70
+ def initialize
71
+ @errors = {}
72
+ end
73
+
74
+ def add(attr, message)
75
+ (@errors[attr] ||= []) << message
76
+ end
77
+
78
+ def on(attr)
79
+ @errors[attr]
80
+ end
81
+ alias_method :[], :on
82
+
83
+ # Yields each attribute and associated message.
84
+
85
+ def each
86
+ @errors.each_key do |attr|
87
+ @errors[attr].each { |msg| yield attr, msg }
88
+ end
89
+ end
90
+
91
+ def size
92
+ @errors.size
93
+ end
94
+ alias_method :count, :size
95
+
96
+ def empty?
97
+ @errors.empty?
98
+ end
99
+
100
+ def clear
101
+ @errors.clear
102
+ end
103
+ end
104
+
105
+ # If the validate method returns true, this
106
+ # attributes holds the errors found.
107
+
108
+ attr_accessor :errors
109
+
110
+ # Call the #validate method for this object.
111
+ # If validation errors are found, sets the
112
+ # @errors attribute to the Errors object and
113
+ # returns true.
114
+
115
+ def valid?
116
+ begin
117
+ @errors = self.class.validate(self)
118
+ return @errors.empty?
119
+ rescue NoMethodError => e
120
+ # gmosx: hmm this is potentially dangerous.
121
+ N::Validation.eval_validate(self.class)
122
+ retry
123
+ end
124
+ end
125
+
126
+ # Evaluate the 'validate' method for the calling
127
+ # class.
128
+ #
129
+ # WARNING: for the moment only evaluates for
130
+ # on == :save
131
+
132
+ def self.eval_validate(klass)
133
+ code = %{
134
+ def self.validate(obj, on = :save)
135
+ errors = Errors.new
136
+ }
137
+
138
+ for val, on in klass.__meta[:validations]
139
+ code << %{
140
+ #{val}
141
+ }
142
+ end
143
+
144
+ code << %{
145
+ return errors
146
+ end
147
+ }
148
+
149
+ # puts '-->', code, '<--'
150
+
151
+ klass.module_eval(code)
152
+ end
153
+
154
+ def self.append_features(base)
155
+ super
156
+
157
+ base.module_eval <<-"end_eval", __FILE__, __LINE__
158
+ meta :validations, []
159
+ end_eval
160
+
161
+ base.extend(MetaLanguage)
162
+ end
163
+
164
+ # = MetaLanguage
165
+ #
166
+ # Implements the Validation meta-language.
167
+
168
+ module MetaLanguage
169
+
170
+ # the postfix attached to confirmation attributes.
171
+
172
+ mattr_accessor :confirmation_postfix, '_confirmation'
173
+
174
+ # Validates that the attributes have a values, ie they are
175
+ # neither nil or empty.
176
+ #
177
+ # == Example
178
+ #
179
+ # validate_value :, :msg => 'No confirmation'
180
+
181
+ def validate_value(*params)
182
+ c = {
183
+ :msg => N::Validation::Errors.no_value,
184
+ :on => :save
185
+ }
186
+ c.update(params.pop) if params.last.is_a?(Hash)
187
+
188
+ idx = 0
189
+ for name in params
190
+ code = %{
191
+ if obj.#{name}.nil?
192
+ errors.add(:#{name}, '#{c[:msg]}')
193
+ elsif obj.#{name}.respond_to?(:empty?)
194
+ errors.add(:#{name}, '#{c[:msg]}') if obj.#{name}.empty?
195
+ end
196
+ }
197
+
198
+ __meta[:validations] << [code, c[:on]]
199
+ end
200
+ end
201
+
202
+ # Validates the confirmation of +String+ attributes.
203
+ #
204
+ # == Example
205
+ #
206
+ # validate_confirmation :password, :msg => 'No confirmation'
207
+
208
+ def validate_confirmation(*params)
209
+ c = {
210
+ :msg => N::Validation::Errors.no_confirmation,
211
+ :postfix => N::Validation::MetaLanguage.confirmation_postfix,
212
+ :on => :save
213
+ }
214
+ c.update(params.pop) if params.last.is_a?(Hash)
215
+
216
+
217
+ for name in params
218
+ confirm_name = "#{name}#{c[:postfix]}"
219
+ attr_accessor :"#{confirm_name}"
220
+
221
+ code = %{
222
+ if obj.#{confirm_name}.nil? or (obj.#{confirm_name} != obj.#{name})
223
+ errors.add(:#{name}, '#{c[:msg]}')
224
+ end
225
+ }
226
+
227
+ __meta[:validations] << [code, c[:on]]
228
+ end
229
+ end
230
+
231
+ # Validates the format of +String+ attributes.
232
+ #
233
+ # == Example
234
+ #
235
+ # validate_format :name, :format => /$A*/, :msg => 'My error', :on => :create
236
+
237
+ def validate_format(*params)
238
+ c = {
239
+ :format => nil,
240
+ :msg_no_value => N::Validation::Errors.no_value,
241
+ :msg => N::Validation::Errors.invalid_format,
242
+ :on => :save
243
+ }
244
+ c.update(params.pop) if params.last.is_a?(Hash)
245
+
246
+ unless c[:format].is_a?(Regexp)
247
+ raise(ArgumentError,
248
+ 'A regular expression must be supplied as the :format option')
249
+ end
250
+
251
+ for name in params
252
+ code = %{
253
+ if obj.#{name}.nil?
254
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
255
+ else
256
+ unless obj.#{name}.to_s.match(/#{Regexp.quote(c[:format].source)}/)
257
+ errors.add(:#{name}, '#{c[:msg]}')
258
+ end
259
+ end;
260
+ }
261
+
262
+ __meta[:validations] << [code, c[:on]]
263
+ end
264
+ end
265
+
266
+ # Validates the length of +String+ attributes.
267
+ #
268
+ # == Example
269
+ #
270
+ # validate_length :name, :max => 30, :msg => 'Too long'
271
+ # validate_length :name, :min => 2, :msg => 'Too sort'
272
+ # validate_length :name, :range => 2..30
273
+ # validate_length :name, :length => 15, :msg => 'Name should be %d chars long'
274
+
275
+ def validate_length(*params)
276
+ c = {
277
+ :min => nil, :max => nil, :range => nil, :length => nil,
278
+ :msg => nil,
279
+ :msg_no_value => N::Validation::Errors.no_value,
280
+ :msg_short => N::Validation::Errors.too_short,
281
+ :msg_long => N::Validation::Errors.too_long,
282
+ :on => :save
283
+ }
284
+ c.update(params.pop) if params.last.is_a?(Hash)
285
+
286
+ min, max = c[:min], c[:max]
287
+ range, length = c[:range], c[:length]
288
+
289
+ count = 0
290
+ [min, max, range, length].each { |o| count += 1 if o }
291
+
292
+ if count == 0
293
+ raise(ArgumentError,
294
+ 'One of :min, :max, :range, :length must be provided!')
295
+ end
296
+
297
+ if count > 1
298
+ raise(ArgumentError,
299
+ 'The :min, :max, :range, :length options are mutually exclusive!')
300
+ end
301
+
302
+ for name in params
303
+ if min
304
+ c[:msg] ||= N::Validation::Errors.too_short
305
+ code = %{
306
+ if obj.#{name}.nil?
307
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
308
+ elsif obj.#{name}.to_s.length < #{min}
309
+ msg = '#{c[:msg]}'
310
+ msg = (msg % #{min}) rescue msg
311
+ errors.add(:#{name}, msg)
312
+ end;
313
+ }
314
+ elsif max
315
+ c[:msg] ||= N::Validation::Errors.too_long
316
+ code = %{
317
+ if obj.#{name}.nil?
318
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
319
+ elsif obj.#{name}.to_s.length > #{max}
320
+ msg = '#{c[:msg]}'
321
+ msg = (msg % #{max}) rescue msg
322
+ errors.add(:#{name}, msg)
323
+ end;
324
+ }
325
+ elsif range
326
+ code = %{
327
+ if obj.#{name}.nil?
328
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
329
+ elsif obj.#{name}.to_s.length < #{range.first}
330
+ msg = '#{c[:msg_short]}'
331
+ msg = (msg % #{range.first}) rescue msg
332
+ errors.add(:#{name}, msg)
333
+ elsif obj.#{name}.to_s.length > #{range.last}
334
+ msg = '#{c[:msg_long]}'
335
+ msg = (msg % #{range.last}) rescue msg
336
+ errors.add(:#{name}, msg)
337
+ end;
338
+ }
339
+ elsif length
340
+ c[:msg] ||= N::Validation::Errors.invalid_length
341
+ code = %{
342
+ if obj.#{name}.nil?
343
+ errors.add(:#{name}, '#{c[:msg_no_value]}')
344
+ elsif obj.#{name}.to_s.length != #{length}
345
+ msg = '#{c[:msg]}'
346
+ msg = (msg % #{length}) rescue msg
347
+ errors.add(:#{name}, msg)
348
+ end;
349
+ }
350
+ end
351
+
352
+ __meta[:validations] << [code, c[:on]]
353
+ end
354
+ end
355
+
356
+ # Validates that the attributes are included in
357
+ # an enumeration.
358
+ #
359
+ # == Example
360
+ #
361
+ # validate_inclusion :sex, :in => %w{ Male Female }, :msg => 'huh??'
362
+ # validate_inclusion :age, :in => 5..99
363
+
364
+ def validate_inclusion(*params)
365
+ c = {
366
+ :in => nil,
367
+ :msg => N::Validation::Errors.no_inclusion,
368
+ :allow_nil => false,
369
+ :on => :save
370
+ }
371
+ c.update(params.pop) if params.last.is_a?(Hash)
372
+
373
+ unless c[:in].respond_to?('include?')
374
+ raise(ArgumentError,
375
+ 'An object that responds to #include? must be supplied as the :in option')
376
+ end
377
+
378
+ for name in params
379
+ if c[:allow_nil]
380
+ code = %{
381
+ unless obj.#{name}.nil? or (#{c[:in].inspect}).include?(obj.#{name})
382
+ errors.add(:#{name}, '#{c[:msg]}')
383
+ end;
384
+ }
385
+ else
386
+ code = %{
387
+ unless (#{c[:in].inspect}).include?(obj.#{name})
388
+ errors.add(:#{name}, '#{c[:msg]}')
389
+ end;
390
+ }
391
+ end
392
+
393
+ __meta[:validations] << [code, c[:on]]
394
+ end
395
+ end
396
+
397
+ end
398
+ end
399
+
400
+ end
data/lib/glue.rb CHANGED
@@ -5,13 +5,14 @@
5
5
  # code:
6
6
  # * George Moschovitis <gm@navel.gr>
7
7
  #
8
- # (c) 2004 Navel, all rights reserved.
8
+ # (c) 2004-2005 Navel, all rights reserved.
9
9
  # $Id: glue.rb 175 2004-11-26 16:11:27Z gmosx $
10
10
 
11
- require "English"
12
- require "pp"
11
+ require 'English'
12
+ require 'pp'
13
13
 
14
14
  require 'glue/property'
15
+ require 'glue/attribute'
15
16
 
16
17
  class NilClass
17
18
  # quite usefull for error tolerant apps.
@@ -26,12 +27,13 @@ class Class
26
27
  #--
27
28
  # gmosx: is this really needed?
28
29
  #++
29
- def to_i()
30
- return self.hash()
30
+ def to_i
31
+ return self.hash
31
32
  end
32
33
  end
33
34
 
34
35
  module Kernel
36
+
35
37
  # pretty prints an exception/error object
36
38
  # usefull for helpfull debug messages
37
39
  #
@@ -47,6 +49,3 @@ module Kernel
47
49
 
48
50
  end
49
51
 
50
- # predefine some comonly used objects
51
-
52
- EMPTY_STRING = ""
data/lib/og/backend.rb CHANGED
@@ -8,33 +8,54 @@ require "yaml"
8
8
 
9
9
  require "og/connection"
10
10
 
11
- module Og
11
+ class Og
12
12
 
13
- # = OgUtils
13
+ # = Backend
14
14
  #
15
- # A collection of useful utilities.
15
+ # Abstract backend. A backend communicates with the RDBMS.
16
+ # This is the base class for the various backend implementations.
16
17
  #
17
- module Utils
18
+ class Backend
19
+
20
+ # The actual connection to the database
21
+ attr_accessor :conn
22
+
23
+ # Intitialize the connection to the RDBMS.
24
+ #
25
+ def initialize(config)
26
+ raise "Not implemented"
27
+ end
28
+
29
+ # Close the connection to the RDBMS.
30
+ #
31
+ def close()
32
+ @conn.close()
33
+ end
34
+
35
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
36
+ # Utilities
37
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
18
38
 
19
39
  # Encode the name of the klass as an sql safe string.
20
- # The Module separators are replaced with _ and NOT stripped out so
21
- # that we can convert back to the original notation if needed.
22
- # The leading module if available is removed.
40
+ # The Module separators are replaced with _ and NOT stripped
41
+ # out so that we can convert back to the original notation if
42
+ # needed. The leading module if available is removed.
23
43
  #
24
44
  def self.encode(klass)
25
45
  "#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase
26
46
  end
27
47
 
28
- # The name of the SQL table where objects of this class are stored.
48
+ # The name of the SQL table where objects of this class
49
+ # are stored.
29
50
  #
30
51
  def self.table(klass)
31
- "_#{$og_table_prefix}#{encode(klass)}"
52
+ "_#{Og.table_prefix}#{encode(klass)}"
32
53
  end
33
54
 
34
55
  # The name of the join table for the two given classes.
35
56
  #
36
57
  def self.join_table(klass1, klass2)
37
- "_#{$og_table_prefix}j_#{Og::Utils.encode(klass1)}_#{Og::Utils.encode(klass2)}"
58
+ "_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}"
38
59
  end
39
60
 
40
61
  # Returns the props that will be included in the insert query.
@@ -123,8 +144,8 @@ module Utils
123
144
  end
124
145
 
125
146
  # Precompile the code to read objects of the given class
126
- # from the backend. In order to allow for changing field/attribute
127
- # orders we have to use a field mapping hash.
147
+ # from the backend. In order to allow for changing
148
+ # field/attribute orders we have to use a field mapping hash.
128
149
  #
129
150
  def self.eval_og_deserialize(klass, og)
130
151
  calc_field_index(klass, og)
@@ -136,7 +157,7 @@ module Utils
136
157
  if idx = og.managed_classes[klass].field_index[p.name]
137
158
  # more fault tolerant if a new field is added and it
138
159
  # doesnt exist in the database.
139
- code << "@#{p.name} = #{Og::Utils.read_prop(p, idx)}"
160
+ code << "@#{p.name} = #{read_prop(p, idx)}"
140
161
  end
141
162
  end
142
163
 
@@ -147,40 +168,20 @@ module Utils
147
168
  }
148
169
  end
149
170
 
150
- end
151
-
152
- # = Backend
153
- #
154
- # Abstract backend. A backend communicates with the RDBMS.
155
- # This is the base class for the various backend implementations.
156
- #
157
- class Backend
158
-
159
- # The actual connection to the database
160
- attr_accessor :conn
161
-
162
- # Intitialize the connection to the RDBMS.
163
- #
164
- def initialize(config)
165
- raise "Not implemented"
166
- end
167
-
168
- # Close the connection to the RDBMS.
169
- #
170
- def close()
171
- @conn.close()
172
- end
171
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
172
+ # Connection methods.
173
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
173
174
 
174
175
  # Create the database.
175
176
  #
176
177
  def self.create_db(database, user = nil, password = nil)
177
- $log.info "Creating database '#{database}'."
178
+ Logger.info "Creating database '#{database}'."
178
179
  end
179
180
 
180
181
  # Drop the database.
181
182
  #
182
183
  def self.drop_db(database, user = nil, password = nil)
183
- $log.info "Dropping database '#{database}'."
184
+ Logger.info "Dropping database '#{database}'."
184
185
  end
185
186
 
186
187
  # Execute an SQL query and return the result.
@@ -195,15 +196,15 @@ class Backend
195
196
  raise "Not implemented"
196
197
  end
197
198
 
198
- # Execute an SQL query and return the result. Wrapped in a rescue
199
- # block.
199
+ # Execute an SQL query and return the result. Wrapped in a
200
+ # rescue block.
200
201
  #
201
202
  def safe_query(sql)
202
203
  raise "Not implemented"
203
204
  end
204
205
 
205
- # Execute an SQL query, no result returned. Wrapped in a rescue
206
- # block.
206
+ # Execute an SQL query, no result returned. Wrapped in a
207
+ # rescue block.
207
208
  #
208
209
  def safe_exec(sql)
209
210
  raise "Not implemented"
@@ -235,9 +236,9 @@ class Backend
235
236
 
236
237
  # Create the fields that correpsond to the klass properties.
237
238
  # The generated fields array is used in create_table.
238
- # If the property has an :sql metadata this overrides the default mapping.
239
- # If the property has an :extra_sql metadata the extra sql is appended
240
- # after the default mapping.
239
+ # If the property has an :sql metadata this overrides the
240
+ # default mapping. If the property has an :extra_sql metadata
241
+ # the extra sql is appended after the default mapping.
241
242
  #
242
243
  def create_fields(klass, typemap)
243
244
  fields = []
@@ -297,4 +298,4 @@ class Backend
297
298
 
298
299
  end
299
300
 
300
- end # module
301
+ end # namespace