glue 0.25.0 → 0.26.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.
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