og 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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