glue 0.25.0 → 0.26.0

Sign up to get free protection for your applications and to get access to all the features.
data/ProjectInfo CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  TITLE : &title Glue
4
4
  NAME : &pkg glue
5
- VERSION : '0.25.0'
5
+ VERSION : '0.26.0'
6
6
  STATUS : beta
7
7
 
8
8
  AUTHOR : George Moschovitis
data/README CHANGED
@@ -1,4 +1,4 @@
1
- = Glue 0.24.0 README
1
+ = Glue 0.26.0 README
2
2
 
3
3
  Useful utilites and methods.
4
4
 
data/doc/RELEASES CHANGED
@@ -1,3 +1,8 @@
1
+ == Version 0.26.0
2
+
3
+ This is the release with the most community contributions. Many
4
+ fixes and minor improvements. Download now!
5
+
1
6
  == Version 0.25.0
2
7
 
3
8
  This is the first in a series of releases focused on stability
data/lib/glue.rb CHANGED
@@ -22,7 +22,7 @@ module Glue
22
22
 
23
23
  # The version.
24
24
 
25
- Version = '0.25.0'
25
+ Version = '0.26.0'
26
26
 
27
27
  # Library path.
28
28
 
@@ -0,0 +1,21 @@
1
+ require 'mega/functor'
2
+
3
+ module Enumerable
4
+
5
+ # Project has_many Group
6
+ # Group has_many User
7
+ # projects.groups.accumulate.users
8
+ #--
9
+ # gmosx: any idea for a better name?
10
+ #++
11
+
12
+ def accumulate
13
+ Functor.new do |op, *args|
14
+ self.inject([]) { |a, x| a << x.send(op, *args) }.flatten
15
+ end
16
+ end
17
+ alias_method :acc, :accumulate
18
+
19
+ end
20
+
21
+ # * George Moschovitis <gm@navel.gr>
@@ -27,6 +27,11 @@ module Expirable
27
27
  return false
28
28
  end
29
29
  end
30
+
31
+ # Update the expiration period. Override in your application.
32
+
33
+ def touch!
34
+ end
30
35
  end
31
36
 
32
37
  end
data/lib/glue/fixture.rb CHANGED
@@ -76,6 +76,10 @@ class Fixture < Hash
76
76
  if path = "#{root_dir}/#{@name}.yml" and File.exist?(path)
77
77
  parse_yaml(path)
78
78
  end
79
+
80
+ if path = "#{root_dir}/#{@name}.yaml" and File.exist?(path)
81
+ parse_yaml(path)
82
+ end
79
83
 
80
84
  if path = "#{root_dir}/#{@name}.csv" and File.exist?(path)
81
85
  parse_csv(path)
data/lib/glue/html.rb ADDED
@@ -0,0 +1,12 @@
1
+ module Glue
2
+ module Html
3
+ def self.cleanup(buf)
4
+ out = buf.dup
5
+ elements = "input|img|br|hr|link|style|render|include|inject|base|meta"
6
+ out.gsub! /<textarea ([^>]*)><\/textarea>/, '<textarea \1>#{}</textarea>'
7
+ out.gsub! /<(#{elements}) ([^>]*)><\/\1>/, '<\1 \2 />'
8
+ out.gsub! /<(#{elements})><\/\1>/, '<\1 />'
9
+ out
10
+ end
11
+ end
12
+ end
data/lib/glue/mailer.rb CHANGED
@@ -3,6 +3,8 @@ require 'glue/mailer/incoming'
3
3
  require 'glue/mailer/outgoing'
4
4
  require 'glue/configuration'
5
5
 
6
+ require 'socket'
7
+
6
8
  module Glue
7
9
 
8
10
  # Handles incoming and outgoing emails. Can be called from
@@ -17,7 +19,7 @@ class Mailer < Mail
17
19
  setting :server, :default => {
18
20
  :address => 'localhost',
19
21
  :port => 25,
20
- :domain => 'localhost.localdomain',
22
+ :domain => Socket.gethostname,
21
23
  :username => nil,
22
24
  :password => nil,
23
25
  :authentication => nil
data/lib/glue/markup.rb CHANGED
@@ -2,6 +2,8 @@ require 'singleton'
2
2
  require 'redcloth'
3
3
  require 'cgi'
4
4
 
5
+ require 'mega/class_inherit'
6
+
5
7
  require 'glue/sanitize'
6
8
 
7
9
  module Glue
@@ -49,7 +51,7 @@ module Markup
49
51
  xstr = str.dup
50
52
  xstr.gsub!(/</, '&lt;')
51
53
  xstr.gsub!(/>/, '&gt;')
52
- xstr.gsub!(/\n/m, '<br/>')
54
+ xstr.gsub!(/\r/, '<br />')
53
55
  return String.sanitize(xstr)
54
56
  end
55
57
  return nil
@@ -92,7 +94,7 @@ module Markup
92
94
  CGI.unescape(str.gsub(/_/, ' '))
93
95
  end
94
96
 
95
- class << self
97
+ class_inherit do
96
98
  # Helper method for manipulating the sanitize transformation.
97
99
 
98
100
  def setup_sanitize_transform(&block)
data/lib/glue/property.rb CHANGED
@@ -38,6 +38,7 @@ class Property
38
38
  def to_s
39
39
  @hash[:symbol].to_s
40
40
  end
41
+ alias_method :name, :to_s
41
42
  end
42
43
 
43
44
  # Property related utils and helpers.
@@ -57,65 +58,70 @@ class Property
57
58
  obj = obj.new if obj.is_a?(Class)
58
59
 
59
60
  for prop in obj.class.properties.values
61
+
60
62
  unless options[:all]
61
- next if :oid == prop.symbol or prop.editor == :none # TODO check for real key.
62
- end
63
+ next if prop.symbol == obj.class.primary_key.symbol or prop.control == :none
64
+ end
65
+
63
66
  prop_name = prop.symbol.to_s
64
67
 
65
68
  # See if there is an incoming request param for this prop.
66
69
 
67
70
  if values.has_key?(prop_name)
68
71
 
69
- # if incoming file then read contents into a string.
70
-
71
72
  prop_value = values[prop_name]
72
- #TODO remove this? prop_value.read if prop_value.respond_to?(:read)
73
- prop_value = prop_value.to_s unless prop_value.is_a?(Hash) or prop_value.is_a?(Array)
74
73
 
75
- # If property is a Blob dont overwrite current property's data if "".
74
+ # to_s must be called on the prop_value incase the request is IOString
75
+ prop_value = prop_value.to_s unless prop_value.is_a?(Hash) or prop_value.is_a?(Array)
76
76
 
77
+ # If property is a Blob dont overwrite current property's data if "".
77
78
  break if prop.klass == Og::Blob and prop_value.empty?
78
79
 
79
- # Pass to property force.
80
-
80
+ # if custom preprocessing is active then try and preprocess
81
+ prop_value = preprocess_value(obj, prop, prop_value) if options[:preprocess]
82
+
83
+ # assign using __force_ methods.
81
84
  obj.send("__force_#{prop_name}", prop_value)
82
- elsif options[:force_boolean] and (prop.klass == TrueClass or prop.klass == FalseClass)
83
- # Set a boolean property to false if it is not in the request.
84
- # Only enabled if force_boolean == true.
85
+
86
+ # Set a boolean property to false if it is not in the request.
87
+ # requires force_boolean == true.
85
88
 
89
+ elsif options[:force_boolean] and (prop.klass == TrueClass or prop.klass == FalseClass)
86
90
  obj.send("__force_#{prop_name}", 0)
87
-
88
91
  end
89
92
  end
90
93
 
91
94
  if options[:assign_relations]
92
95
  for rel in obj.class.relations
96
+
93
97
  unless options[:all]
94
- next if rel.options[:editor] == :none
98
+ next if rel.options[:control] == :none
95
99
  end
96
100
 
97
101
  rel_name = rel.name.to_s
98
102
 
99
103
  # Renew the relations from values
100
- # TODO, farms: custom callbacks for processing relations?
101
104
 
102
- if rel.kind_of? Og::RefersTo
105
+ if rel.kind_of?(Og::RefersTo)
103
106
  if foreign_oid = values[rel_name]
107
+ foreign_oid = foreign_oid.to_s
104
108
  foreign_oid = nil if foreign_oid == 'nil' or foreign_oid == 'none'
109
+ # if custom preprocessing is active then try and preprocess
110
+ foreign_oid = preprocess_value(obj, rel, foreign_oid) if options[:preprocess]
105
111
  end
106
- obj.send("__force_#{rel.foreign_key}", foreign_oid.to_s)
107
- elsif rel.kind_of? Og::JoinsMany or rel.kind_of? Og::HasMany
108
- # Empty current relationships.
109
-
112
+ obj.send("__force_#{rel.foreign_key}", foreign_oid)
113
+ elsif rel.kind_of?(Og::JoinsMany) || rel.kind_of?(Og::HasMany)
110
114
  collection = obj.send(rel_name)
111
115
  collection.remove_all
112
-
113
- # Set new.
114
-
115
116
  if values.has_key?(rel_name)
116
- values[rel_name].each do |v|
117
- child = rel.target_class[v.to_s.to_i]
118
- collection << child
117
+ primary_keys = values[rel_name]
118
+
119
+ # if custom preprocessing is active then try and preprocess
120
+ primary_keys = preprocess_value(obj, rel, primary_keys) if options[:preprocess]
121
+
122
+ primary_keys.each do |v|
123
+ next if v == 'nil' or v == 'none'
124
+ collection << rel.target_class[v.to_s.to_i]
119
125
  end
120
126
  end
121
127
  end
@@ -125,6 +131,31 @@ class Property
125
131
  return obj
126
132
  end
127
133
 
134
+ # This method will attempt to process [value] through the
135
+ # any on_populate preprocessors availible
136
+ #
137
+ # first looks if a method was specified on the property
138
+ #
139
+ # ie. property :this, String, :on_populate => :change_value
140
+ # ==> would mean #change_value(val) would get called
141
+ # set :on_populate to false to exclude it from any preprocessing
142
+ #
143
+ # next an #on_populate method will be looked for on any
144
+ # associated Nitro::Control (if controls is running)
145
+ #
146
+ # otherwise [value] is left untouched
147
+ def preprocess_value(obj, prop_or_rel, value)
148
+ if prop_or_rel.on_populate && obj.respond_to?(prop_or_rel.on_populate.to_sym)
149
+ return obj.send(prop_or_rel.on_populate.to_sym, value)
150
+ elsif Object.const_defined? :Nitro
151
+ if control = Control.fetch(obj, prop_or_rel, nil) and control.respond_to?(:on_populate)
152
+ return control.on_populate(value)
153
+ end
154
+ else
155
+ return value
156
+ end
157
+ end
158
+
128
159
  def eval_helpers(writer, m, sym, klass)
129
160
  return unless writer
130
161
 
@@ -139,7 +170,7 @@ class Property
139
170
  when Float.name: 'val.to_f'
140
171
  when Time.name: 'val.is_a?(Hash) ? Time.local(val["year"],val["month"],val["day"],val["hour"],val["min"]) : Time.parse(val.to_s)'
141
172
  when Date.name: 'val.is_a?(Hash) ? Time.local(val["year"],val["month"],val["day"]).to_date : Time.parse(val.to_s).to_date'
142
- when TrueClass.name, FalseClass.name: 'val == "on" or val == "true" ? true
173
+ when TrueClass.name, FalseClass.name: 'val == "on" or val == "true" ? true: val.to_i > 0'
143
174
  else 'val'
144
175
  end + %{)
145
176
  end
@@ -164,7 +195,7 @@ class Module
164
195
  writer = (m != :reader)
165
196
  code = %{
166
197
  def prop#{m} (*args)
167
- inheritor(:properties, {}, :merge) unless @properties
198
+ inheritor(:properties, Dictionary.new, :merge) unless @properties
168
199
 
169
200
  args = args.flatten
170
201
  harg = {}
@@ -225,3 +256,4 @@ end
225
256
  # * George Moschovitis <gm@navel.gr>
226
257
  # * Tom Sawyer <transfire@gmail.com>
227
258
  # * Chris Farmiloe <chris.farmiloe@farmiloe.com>
259
+ # * Bryan Soto <bryan.a.soto@gmail.com>
data/lib/glue/template.rb CHANGED
@@ -59,12 +59,8 @@ module TemplateMixin
59
59
  # xform render/inject instructions <render href="xxx" />
60
60
  # must be transformed before the processinc instructions.
61
61
 
62
- text.gsub!(/<inject href=["|'](.*?)["|'](.*)(.?)\/>/) do |match|
63
- "<?r render '#$1' ?>"
64
- end
65
-
66
- text.gsub!(/<render href=["|'](.*?)["|'](.*)(.?)\/>/) do |match|
67
- "<?r render '#$1' ?>"
62
+ text.gsub!(/<(render|inject) href=["|'](.*?)["|'](.*)(.?)\/>/) do |match|
63
+ "<?r render '#$2' ?>"
68
64
  end
69
65
 
70
66
  # Remove <root> elements. typically removed by xslt but lets
@@ -155,6 +151,10 @@ class Template
155
151
 
156
152
  setting :extension, :default => 'xhtml', :doc => 'The default template file extension'
157
153
 
154
+ # Strip xml comments from templates?
155
+
156
+ setting :strip_xml_comments, :default => false, :doc => 'Strip xml comments from templates?'
157
+
158
158
  class << self
159
159
  include TemplateMixin
160
160
  alias_method :compile, :compile_template
@@ -55,9 +55,8 @@ module Glue
55
55
  # else
56
56
  # p errors[:name]
57
57
  # end
58
- #
59
58
  #--
60
- # TODO: all validation methods should imply validate_value
59
+ # TODO: all validation methods should imply validate_value.
61
60
  #++
62
61
 
63
62
  module Validation
@@ -115,6 +114,25 @@ module Validation
115
114
  @errors.clear
116
115
  end
117
116
  end
117
+
118
+ # A Key is used to uniquely identify a validation rule.
119
+
120
+ class Key
121
+ attr_reader :validation
122
+ attr_reader :field
123
+
124
+ def initialize(validation, field)
125
+ @validation, @field = validation.to_s, field.to_s
126
+ end
127
+
128
+ def hash
129
+ "#{@validation}-#{@field}".hash
130
+ end
131
+
132
+ def ==(other)
133
+ self.validation == other.validation and self.field == other.field
134
+ end
135
+ end
118
136
 
119
137
  # If the validate method returns true, this
120
138
  # attributes holds the errors found.
@@ -127,40 +145,22 @@ module Validation
127
145
  # returns true.
128
146
 
129
147
  def valid?
130
- begin
131
- @errors = self.class.validate(self)
132
- return @errors.empty?
133
- rescue NoMethodError => e
134
- # gmosx: hmm this is potentially dangerous.
135
- Glue::Validation.eval_validate(self.class)
136
- retry
137
- end
148
+ validate
149
+ @errors.empty?
138
150
  end
139
151
 
140
- # Evaluate the 'validate' method for the calling
141
- # class.
142
- #
143
- # WARNING: for the moment only evaluates for
144
- # on == :save
145
-
146
- def self.eval_validate(klass)
147
- code = %{
148
- def self.validate(obj, on = :save)
149
- errors = Errors.new
150
- }
151
-
152
- for val, on in klass.validations
153
- code << %{
154
- #{val}
155
- }
156
- end
152
+ # Evaluate the class and see if it is valid.
153
+ # Can accept any parameter for 'on' event,
154
+ # and defaults to :save
157
155
 
158
- code << %{
159
- return errors
160
- end
161
- }
156
+ def validate(on = :save)
157
+ @errors = Errors.new
162
158
 
163
- klass.module_eval(code)
159
+ return if self.class.validations.length == 0
160
+
161
+ for event, block in self.class.validations
162
+ block.call(self) if event == on.to_sym
163
+ end
164
164
  end
165
165
 
166
166
  on_included %{
@@ -178,7 +178,6 @@ module Validation
178
178
  # Implements the Validation meta-language.
179
179
 
180
180
  module ClassMethods
181
-
182
181
  # Validates that the attributes have a values, ie they are
183
182
  # neither nil or empty.
184
183
  #
@@ -187,23 +186,13 @@ module Validation
187
186
  # validate_value :param, :msg => 'No confirmation'
188
187
 
189
188
  def validate_value(*params)
190
- c = {
191
- :msg => Glue::Validation::Errors.no_value,
192
- :on => :save
193
- }
194
- c.update(params.pop) if params.last.is_a?(Hash)
195
-
196
- idx = 0
197
- for name in params
198
- code = %{
199
- if obj.#{name}.nil?
200
- errors.add(:#{name}, '#{c[:msg]}')
201
- elsif obj.#{name}.respond_to?(:empty?)
202
- errors.add(:#{name}, '#{c[:msg]}') if obj.#{name}.empty?
203
- end
204
- }
205
-
206
- validations! << [code, c[:on]]
189
+ c = parse_config(params, :msg => Glue::Validation::Errors.no_value, :on => :save)
190
+
191
+ params.each do |field|
192
+ define_validation(:value, field, c[:on]) do |obj|
193
+ value = obj.send(field)
194
+ obj.errors.add(field, c[:msg]) if value.nil? || (value.respond_to?(:empty) && value.empty?)
195
+ end
207
196
  end
208
197
  end
209
198
 
@@ -214,25 +203,20 @@ module Validation
214
203
  # validate_confirmation :password, :msg => 'No confirmation'
215
204
 
216
205
  def validate_confirmation(*params)
217
- c = {
206
+ c = parse_config(params,
218
207
  :msg => Glue::Validation::Errors.no_confirmation,
219
208
  :postfix => Glue::Validation.confirmation_postfix,
220
209
  :on => :save
221
- }
222
- c.update(params.pop) if params.last.is_a?(Hash)
210
+ )
223
211
 
212
+ params.each do |field|
213
+ confirm_name = field.to_s + c[:postfix]
214
+ attr_accessor confirm_name.to_sym
224
215
 
225
- for name in params
226
- confirm_name = "#{name}#{c[:postfix]}"
227
- eval "attr_accessor :#{confirm_name}"
228
-
229
- code = %{
230
- if obj.#{confirm_name}.nil? or (obj.#{confirm_name} != obj.#{name})
231
- errors.add(:#{name}, '#{c[:msg]}')
232
- end
233
- }
234
-
235
- validations! << [code, c[:on]]
216
+ define_validation(:confirmation, field, c[:on]) do |obj|
217
+ value = obj.send(field)
218
+ obj.errors.add(field, c[:msg]) if value.nil? or obj.send(confirm_name) != value
219
+ end
236
220
  end
237
221
  end
238
222
 
@@ -246,33 +230,24 @@ module Validation
246
230
  #--
247
231
  # FIXME: how to get the Regexp options?
248
232
  #++
249
-
233
+
250
234
  def validate_format(*params)
251
- c = {
235
+ c = parse_config(params,
252
236
  :format => nil,
253
237
  :msg_no_value => Glue::Validation::Errors.no_value,
254
238
  :msg => Glue::Validation::Errors.invalid_format,
255
239
  :on => :save
256
- }
257
- c.update(params.pop) if params.last.is_a?(Hash)
258
-
240
+ )
241
+
259
242
  unless c[:format].is_a?(Regexp)
260
- raise(ArgumentError,
261
- 'A regular expression must be supplied as the :format option')
243
+ raise ArgumentError, "A regular expression must be supplied as the :format option for validate_format."
262
244
  end
263
245
 
264
- for name in params
265
- code = %{
266
- if obj.#{name}.nil?
267
- errors.add(:#{name}, '#{c[:msg_no_value]}')
268
- else
269
- unless obj.#{name}.to_s.match(/#{c[:format].source}/)
270
- errors.add(:#{name}, '#{c[:msg]}')
271
- end
272
- end;
273
- }
274
-
275
- validations! << [code, c[:on]]
246
+ params.each do |field|
247
+ define_validation(:format, field, c[:on]) do |obj|
248
+ value = obj.send(field)
249
+ obj.errors.add(field, c[:msg]) if value.nil? or not value.to_s.match(c[:format].source)
250
+ end
276
251
  end
277
252
  end
278
253
 
@@ -285,85 +260,62 @@ module Validation
285
260
  # validate_length :name, :range => 2..30
286
261
  # validate_length :name, :length => 15, :msg => 'Name should be %d chars long'
287
262
 
263
+ LENGTHS = [:min, :max, :range, :length].freeze
264
+
288
265
  def validate_length(*params)
289
- c = {
290
- :min => nil, :max => nil, :range => nil, :length => nil,
266
+ c = parse_config(params,
267
+ # :lengths => {:min => nil, :max => nil, :range => nil, :length => nil},
268
+ :min => nil, :max => nil, :range => nil, :length => nil,
291
269
  :msg => nil,
292
270
  :msg_no_value => Glue::Validation::Errors.no_value,
293
271
  :msg_short => Glue::Validation::Errors.too_short,
294
272
  :msg_long => Glue::Validation::Errors.too_long,
273
+ :msg_invalid => Glue::Validation::Errors.invalid_length,
295
274
  :on => :save
296
- }
297
- c.update(params.pop) if params.last.is_a?(Hash)
298
-
299
- min, max = c[:min], c[:max]
300
- range, length = c[:range], c[:length]
301
-
302
- count = 0
303
- [min, max, range, length].each { |o| count += 1 if o }
304
-
305
- if count == 0
306
- raise(ArgumentError,
307
- 'One of :min, :max, :range, :length must be provided!')
308
- end
309
-
310
- if count > 1
311
- raise(ArgumentError,
312
- 'The :min, :max, :range, :length options are mutually exclusive!')
275
+ )
276
+
277
+ length_params = c.reject {|k,v| !LENGTHS.include?(k) || v.nil? }
278
+ valid_count = length_params.reject{|k,v| v.nil?}.length
279
+
280
+ if valid_count == 0
281
+ raise ArgumentError, 'One of :min, :max, :range, or :length must be provided!'
282
+ elsif valid_count > 1
283
+ raise ArgumentError, 'You can only use one of :min, :max, :range, or :length options!'
313
284
  end
314
285
 
315
- for name in params
316
- if min
317
- c[:msg] ||= Glue::Validation::Errors.too_short
318
- code = %{
319
- if obj.#{name}.nil?
320
- errors.add(:#{name}, '#{c[:msg_no_value]}')
321
- elsif obj.#{name}.to_s.length < #{min}
322
- msg = '#{c[:msg]}'
323
- msg = (msg % #{min}) rescue msg
324
- errors.add(:#{name}, msg)
325
- end;
326
- }
327
- elsif max
328
- c[:msg] ||= Glue::Validation::Errors.too_long
329
- code = %{
330
- if obj.#{name}.nil?
331
- errors.add(:#{name}, '#{c[:msg_no_value]}')
332
- elsif obj.#{name}.to_s.length > #{max}
333
- msg = '#{c[:msg]}'
334
- msg = (msg % #{max}) rescue msg
335
- errors.add(:#{name}, msg)
336
- end;
337
- }
338
- elsif range
339
- code = %{
340
- if obj.#{name}.nil?
341
- errors.add(:#{name}, '#{c[:msg_no_value]}')
342
- elsif obj.#{name}.to_s.length < #{range.first}
343
- msg = '#{c[:msg_short]}'
344
- msg = (msg % #{range.first}) rescue msg
345
- errors.add(:#{name}, msg)
346
- elsif obj.#{name}.to_s.length > #{range.last}
347
- msg = '#{c[:msg_long]}'
348
- msg = (msg % #{range.last}) rescue msg
349
- errors.add(:#{name}, msg)
350
- end;
351
- }
352
- elsif length
353
- c[:msg] ||= Glue::Validation::Errors.invalid_length
354
- code = %{
355
- if obj.#{name}.nil?
356
- errors.add(:#{name}, '#{c[:msg_no_value]}')
357
- elsif obj.#{name}.to_s.length != #{length}
358
- msg = '#{c[:msg]}'
359
- msg = (msg % #{length}) rescue msg
360
- errors.add(:#{name}, msg)
361
- end;
362
- }
286
+ operation, required = length_params.keys[0], length_params.values[0]
287
+
288
+ params.each do |field|
289
+ define_validation(:length, field, c[:on]) do |obj|
290
+ msg = c[:msg]
291
+ value = obj.send(field)
292
+ if value.nil?
293
+ obj.errors.add(field, c[:msg_no_value])
294
+ else
295
+ case operation
296
+ when :min
297
+ msg ||= c[:msg_short]
298
+ obj.errors.add(field, msg % required) if value.length < required
299
+ when :max
300
+ msg ||= c[:msg_long]
301
+ obj.errors.add(field, msg % required) if value.length > required
302
+ when :range
303
+ min, max = required.first, required.last
304
+ if value.length < min
305
+ msg ||= c[:msg_short]
306
+ obj.errors.add(field, msg % min)
307
+ end
308
+ if value.length > max
309
+ msg ||= c[:msg_long]
310
+ obj.errors.add(field, msg % min)
311
+ end
312
+ when :length
313
+ msg ||= c[:msg_invalid]
314
+ obj.errors.add(field, msg % required) if value.length != required
315
+ end
316
+ end
363
317
  end
364
-
365
- validations! << [code, c[:on]]
366
- end
318
+ end
367
319
  end
368
320
 
369
321
  # Validates that the attributes are included in
@@ -375,35 +327,24 @@ module Validation
375
327
  # validate_inclusion :age, :in => 5..99
376
328
 
377
329
  def validate_inclusion(*params)
378
- c = {
330
+ c = parse_config(params,
379
331
  :in => nil,
380
332
  :msg => Glue::Validation::Errors.no_inclusion,
381
333
  :allow_nil => false,
382
334
  :on => :save
383
- }
384
- c.update(params.pop) if params.last.is_a?(Hash)
385
-
335
+ )
336
+
386
337
  unless c[:in].respond_to?('include?')
387
- raise(ArgumentError,
388
- 'An object that responds to #include? must be supplied as the :in option')
338
+ raise(ArgumentError, 'An object that responds to #include? must be supplied as the :in option')
389
339
  end
390
340
 
391
- for name in params
392
- if c[:allow_nil]
393
- code = %{
394
- unless obj.#{name}.nil? or (#{c[:in].inspect}).include?(obj.#{name})
395
- errors.add(:#{name}, '#{c[:msg]}')
396
- end;
397
- }
398
- else
399
- code = %{
400
- unless (#{c[:in].inspect}).include?(obj.#{name})
401
- errors.add(:#{name}, '#{c[:msg]}')
402
- end;
403
- }
341
+ params.each do |field|
342
+ define_validation(:inclusion, field, c[:on]) do |obj|
343
+ value = obj.send(field)
344
+ unless (c[:allow_nil] && value.nil?) or c[:in].include?(value)
345
+ obj.errors.add(field, c[:msg])
346
+ end
404
347
  end
405
-
406
- validations! << [code, c[:on]]
407
348
  end
408
349
  end
409
350
 
@@ -423,35 +364,67 @@ module Validation
423
364
  # validate_numeric :age, :msg => 'Must be an integer'
424
365
 
425
366
  def validate_numeric(*params)
426
- c = {
367
+ c = parse_config(params,
427
368
  :integer => false,
428
369
  :msg => Glue::Validation::Errors.no_numeric,
429
370
  :on => :save
430
- }
431
- c.update(params.pop) if params.last.is_a?(Hash)
432
-
433
- for name in params
434
- if c[:integer]
435
- code = %{
436
- unless obj.#{name}.to_s =~ /^[\\+\\-]?\\d+$/
437
- errors.add(:#{name}, '#{c[:msg]}')
438
- end;
439
- }
440
- else
441
- code = %{
371
+ )
372
+
373
+ params.each do |field|
374
+ define_validation(:numeric, field, c[:on]) do |obj|
375
+ value = obj.send(field).to_s
376
+ if c[:integer]
377
+ unless value =~ /^[\+\-]*\d+$/
378
+ obj.errors.add(field, c[:msg])
379
+ end
380
+ else
442
381
  begin
443
- Kernel.Float(obj.#{name})
382
+ Float(value)
444
383
  rescue ArgumentError, TypeError
445
- errors.add(:#{name}, '#{c[:msg]}')
446
- end;
447
- }
384
+ obj.errors.add(field, c[:msg])
385
+ end
386
+ end
448
387
  end
449
-
450
- validations! << [code, c[:on]]
451
- end
388
+ end
452
389
  end
453
390
  alias_method :validate_numericality, :validate_numeric
454
391
 
392
+ protected
393
+
394
+ # Parse the configuration for a validation by comparing
395
+ # the default options with the user-specified options,
396
+ # and returning the results.
397
+
398
+ def parse_config(params, defaults = {})
399
+ defaults.update(params.pop) if params.last.is_a?(Hash)
400
+ defaults
401
+ end
402
+
403
+ # Define a validation for this class on the specified event.
404
+ # Specify the on event for when this validation should be
405
+ # checked.
406
+ #
407
+ # An extra check is performed to avoid multiple validations.
408
+ #
409
+ # This example creates a validation for the 'save' event,
410
+ # and will add an error to the record if the 'name' property
411
+ # of the validating object is nil.
412
+ #
413
+ # === Example
414
+ #
415
+ # field = :name
416
+ #
417
+ # define_validation(:value, field, :save) do |object|
418
+ # object.errors.add(field, "You must set a value for #{field}") if object.send(field).nil?
419
+ # end
420
+
421
+ def define_validation(val, field, on = :save, &block)
422
+ vk = Validation::Key.new(val, field)
423
+ unless validations.find { |v| v[2] == vk }
424
+ validations! << [on, block, vk]
425
+ end
426
+ end
427
+
455
428
  end
456
429
 
457
430
  end
@@ -462,4 +435,6 @@ class Module # :nodoc: all
462
435
  include Glue::Validation::ClassMethods
463
436
  end
464
437
 
465
- # * George Moschovitis <gm@navel.gr>
438
+ # * George Moschovitis <gm@navel.gr>
439
+ # * Brian Bugh <brian@xsi-design.com>
440
+ # * Bryan Soto <bryan.a.soto@gmail.com>
@@ -0,0 +1,5 @@
1
+ class User
2
+ property :name, String
3
+ validate_length :name, :range => 2..6
4
+ validate_format :name, :format => /[a-z]*/, :msg => 'invalid format', :on => :create
5
+ end
@@ -0,0 +1,37 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
2
+
3
+ require 'test/unit'
4
+ require 'glue/accumulate'
5
+
6
+ class TC_Accumulate < Test::Unit::TestCase # :nodoc: all
7
+
8
+ class Project
9
+ attr_accessor :groups
10
+ end
11
+
12
+ class Group
13
+ attr_accessor :users
14
+ end
15
+
16
+ class User
17
+ end
18
+
19
+ def test_all
20
+ pr = Project.new
21
+ gr1 = Group.new
22
+ gr2 = Group.new
23
+ us1 = User.new
24
+ us2 = User.new
25
+ gr1.users = [ us1 ]
26
+ gr2.users = [ us2 ]
27
+ pr.groups = [ gr1, gr2 ]
28
+
29
+ users = pr.groups.accumulate.users
30
+ assert_equal 2, users.size
31
+ assert_equal us1, users[0]
32
+ assert_equal us2, users[1]
33
+ end
34
+
35
+ end
36
+
37
+ # * George Moschovitis <gm@navel.gr>
@@ -0,0 +1,19 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
2
+
3
+ require 'test/unit'
4
+
5
+ require 'glue'
6
+ require 'glue/property'
7
+ require 'glue/validation'
8
+
9
+ class TC_MultiValidation < Test::Unit::TestCase # :nodoc: all
10
+
11
+ def test_all
12
+ file = File.join(File.dirname($0), 'multi_validations_model.rb')
13
+ load file
14
+ assert_equal 2, User.validations.size
15
+ load file
16
+ assert_equal 2, User.validations.size
17
+ end
18
+
19
+ end
@@ -1,4 +1,5 @@
1
1
  $:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
2
+ $:.unshift File.join(File.dirname(__FILE__), '..', '..', '..', 'og', 'lib')
2
3
 
3
4
  require 'test/unit'
4
5
 
@@ -78,7 +79,7 @@ class TC_Validation < Test::Unit::TestCase # :nodoc: all
78
79
  include Mixin
79
80
  validate_length :value, :min => 3
80
81
  end
81
-
82
+
82
83
  def test_validate_value
83
84
  d = Dummy1.new
84
85
 
@@ -90,7 +91,7 @@ class TC_Validation < Test::Unit::TestCase # :nodoc: all
90
91
  d.str = 'yeah'
91
92
  assert d.valid?
92
93
  end
93
-
94
+
94
95
  def test_validate_confirmation
95
96
  d = Dummy2.new
96
97
 
@@ -212,4 +213,5 @@ class TC_Validation < Test::Unit::TestCase # :nodoc: all
212
213
  d.age = 123
213
214
  assert d.valid?
214
215
  end
216
+
215
217
  end
@@ -0,0 +1,208 @@
1
+ path = File.expand_path(File.dirname($0))
2
+ $: << path + '/og/lib'
3
+ $: << path + '/glue/lib'
4
+ require 'test/unit'
5
+ require 'og'
6
+
7
+ module UnitTestClasses
8
+ class NoValidation
9
+ property :prop, String
10
+
11
+ def initialize(prop)
12
+ @prop = prop
13
+ end
14
+ end
15
+
16
+ class UniqueValidation < NoValidation
17
+ property :slop, String
18
+ validate_unique :prop, :slop
19
+ end
20
+
21
+ class ValueValidation < NoValidation
22
+ validate_value :prop
23
+ end
24
+
25
+ class ConfirmValidation < NoValidation
26
+ validate_confirmation :prop
27
+ end
28
+
29
+ class FormatValidation < NoValidation
30
+ validate_format :prop, :format => /^\w\d\w-\d\w\d$/
31
+ end
32
+
33
+ class MinValidation < NoValidation
34
+ validate_length :prop, :min => 4
35
+ end
36
+
37
+ class MaxValidation < NoValidation
38
+ validate_length :prop, :max => 8
39
+ end
40
+
41
+ class RangeValidation < NoValidation
42
+ validate_length :prop, :range => 10..20
43
+ end
44
+
45
+ class LengthValidation < NoValidation
46
+ validate_length :prop, :length => 8
47
+ end
48
+
49
+ class InclusionValidation < NoValidation
50
+ validate_inclusion :prop, :in => %w{ Male Female }
51
+ end
52
+
53
+ class NumericValidation < NoValidation
54
+ property :prop, Fixnum
55
+ validate_numeric :prop, :integer => true
56
+
57
+ prop_accessor :prop2, Float
58
+ validate_numeric :prop2
59
+ end
60
+
61
+ class RelatedClass
62
+ prop_accessor :test, String
63
+ belongs_to :relation_validation, RelationValidation
64
+ end
65
+
66
+ class RelationValidation < NoValidation
67
+ has_many :relations, RelatedClass
68
+ validate_related :relations
69
+ end
70
+ end
71
+
72
+ config = {
73
+ :destroy => true,
74
+ :store => :sqlite,
75
+ :prop => 'dbtest_additions'
76
+ }
77
+
78
+ db = Og.setup(config)
79
+
80
+ require 'breakpoint'
81
+ module Test::Unit::Assertions
82
+ # Assert than an Og object is successfully saved and is error free.
83
+ def assert_saved_without_errors(obj)
84
+ assert_block(obj.class.to_s + ' was expected to save, but failed.') do
85
+ obj.save
86
+ obj.saved? && obj.errors.size == 0
87
+ end
88
+ end
89
+
90
+ # Assert that an Og object did not save and has errors.
91
+ def assert_save_failed(obj)
92
+ assert_block(obj.class.to_s + ' saving was expected to fail, but it succeeded.') do
93
+ obj.save
94
+ !obj.saved? && obj.errors.size > 0
95
+ end
96
+ end
97
+ end
98
+
99
+ # Unit tests for the Og validation system
100
+ class TestFixes < Test::Unit::TestCase
101
+ include UnitTestClasses
102
+
103
+ def test_no_validation
104
+ t = NoValidation.new('nochecking')
105
+ assert_saved_without_errors t
106
+ end
107
+
108
+ def test_validation_unique
109
+ assert_saved_without_errors UniqueValidation.new('test')
110
+ assert_save_failed UniqueValidation.new('test')
111
+ end
112
+
113
+ def test_value_validation
114
+ assert_saved_without_errors ValueValidation.new('value')
115
+ assert_save_failed ValueValidation.new(nil)
116
+ end
117
+
118
+ def test_confirmation
119
+ prop = 'confirm'
120
+ c = ConfirmValidation.new(prop)
121
+ c.prop_confirmation = prop
122
+ assert_saved_without_errors c
123
+
124
+ assert_save_failed ConfirmValidation.new(prop)
125
+ end
126
+
127
+ def test_format
128
+ assert_saved_without_errors FormatValidation.new('a1b-2c3')
129
+ assert_save_failed FormatValidation.new('banana_pudding')
130
+ end
131
+
132
+ def test_length_min
133
+ assert_saved_without_errors MinValidation.new('12345')
134
+ assert_save_failed MinValidation.new('1')
135
+ end
136
+
137
+ def test_length_max
138
+ assert_saved_without_errors MaxValidation.new('12345')
139
+ assert_save_failed MaxValidation.new('123456789012345')
140
+ end
141
+
142
+ def test_length_range
143
+ l = RangeValidation.new('12345678901234')
144
+ assert_saved_without_errors l
145
+
146
+ l = RangeValidation.new('12')
147
+ assert_save_failed l
148
+
149
+ l = RangeValidation.new('1234567890123456789012345')
150
+ assert_save_failed l
151
+ end
152
+
153
+ def test_length_length
154
+ assert_saved_without_errors LengthValidation.new('12345678')
155
+ assert_save_failed LengthValidation.new('1')
156
+ assert_save_failed LengthValidation.new('123456789012345')
157
+ end
158
+
159
+ def test_inclusion
160
+ assert_saved_without_errors InclusionValidation.new('Male')
161
+ assert_save_failed InclusionValidation.new('Shemale')
162
+ end
163
+
164
+ def test_numeric
165
+ # Both valid
166
+ n = NumericValidation.new(100)
167
+ n.prop2 = 1.52
168
+ assert_saved_without_errors n
169
+
170
+ # float for required integer
171
+ n = NumericValidation.new(1.52)
172
+ n.prop2 = 1.52
173
+ assert_save_failed n
174
+ assert_equal 1, n.errors.count
175
+
176
+ # string for required integer
177
+ n = NumericValidation.new('sausage')
178
+ n.prop2 = 1.52
179
+ assert_save_failed n
180
+ assert_equal 1, n.errors.count
181
+
182
+ # string for required integer and float
183
+ n = NumericValidation.new('sausage')
184
+ n.prop2 = 'cheese'
185
+ assert_save_failed n
186
+ assert_equal 2, n.errors.count
187
+
188
+ # string for float
189
+ n = NumericValidation.new(15)
190
+ n.prop2 = 'cheese'
191
+ assert_save_failed n
192
+ assert_equal 1, n.errors.count
193
+ end
194
+
195
+ def test_related
196
+ r = RelatedClass.new
197
+ test_prop = 'example string'
198
+ r.test = test_prop
199
+ rv = RelationValidation.new('relation')
200
+ rv.relations << r
201
+
202
+ assert_saved_without_errors rv
203
+ assert_equal test_prop, rv.relations.members[0].test
204
+
205
+ # Test bad
206
+ assert_save_failed RelationValidation.new('relation')
207
+ end
208
+ end
@@ -0,0 +1,36 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems'
4
+
5
+ require 'og'
6
+
7
+ class ManagedClass
8
+ prop_accessor :thing
9
+
10
+ validate_unique :thing
11
+ end
12
+
13
+ Og.start
14
+
15
+ class TC_Loop < Test::Unit::TestCase
16
+ def setup
17
+ @thing_1 = ManagedClass.new
18
+ @thing_1.thing = 'thing'
19
+ @thing_1.save
20
+ end
21
+
22
+ def test_loop
23
+ thing_2 = ManagedClass.new
24
+ thing_2.thing = @thing_1.thing
25
+ # assert_raises(NoMethodError) {
26
+ # thing_2.valid?
27
+ # assert thing_2.errors
28
+ # }
29
+ end
30
+
31
+ def teardown
32
+ ManagedClass.all.each { |i| i.delete }
33
+ end
34
+ end
35
+
36
+ # * Bryan Soto <bryan.a.soto@gmail.com>
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.10
3
3
  specification_version: 1
4
4
  name: glue
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.25.0
7
- date: 2005-11-17
6
+ version: 0.26.0
7
+ date: 2005-12-21
8
8
  summary: Utility methods and classes for Nitro.
9
9
  require_paths:
10
10
  - lib
@@ -42,6 +42,7 @@ files:
42
42
  - lib/glue
43
43
  - lib/glue.rb
44
44
  - lib/html
45
+ - lib/glue/accumulate.rb
45
46
  - lib/glue/aspects.rb
46
47
  - lib/glue/attribute.rb
47
48
  - lib/glue/builder
@@ -50,6 +51,7 @@ files:
50
51
  - lib/glue/expirable.rb
51
52
  - lib/glue/fixture.rb
52
53
  - lib/glue/flexob.rb
54
+ - lib/glue/html.rb
53
55
  - lib/glue/localization.rb
54
56
  - lib/glue/logger.rb
55
57
  - lib/glue/mail.rb
@@ -77,6 +79,8 @@ files:
77
79
  - test/fixture/article.yml
78
80
  - test/fixture/user.yml
79
81
  - test/glue/builder
82
+ - test/glue/multi_validations_model.rb
83
+ - test/glue/tc_accumulate.rb
80
84
  - test/glue/tc_aspects.rb
81
85
  - test/glue/tc_attribute.rb
82
86
  - test/glue/tc_builder.rb
@@ -86,12 +90,15 @@ files:
86
90
  - test/glue/tc_localization.rb
87
91
  - test/glue/tc_logger.rb
88
92
  - test/glue/tc_mail.rb
93
+ - test/glue/tc_multi_validations.rb
89
94
  - test/glue/tc_property.rb
90
95
  - test/glue/tc_property_mixins.rb
91
96
  - test/glue/tc_property_type_checking.rb
92
97
  - test/glue/tc_template.rb
93
98
  - test/glue/tc_uri.rb
94
99
  - test/glue/tc_validation.rb
100
+ - test/glue/tc_validation2.rb
101
+ - test/glue/tc_validation_loop.rb
95
102
  - test/glue/builder/tc_xml.rb
96
103
  - test/public/dummy_mailer
97
104
  - test/public/dummy_mailer/registration.xhtml