formeze 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -150,6 +150,22 @@ end
150
150
  In this example, the `billing_address_line_one` field will only be defined
151
151
  and validated if the `same_address` checkbox is checked.
152
152
 
153
+ Validation errors can be a frustrating experience for end users, so ideally
154
+ we want to [be liberal in what we accept](http://en.wikipedia.org/wiki/Jon_Postel#Postel.27s_Law),
155
+ but at the same time ensuring that data is consistently formatted to make it
156
+ easy for us to process. Meet the `scrub` option, which can be used to specify
157
+ methods for "cleaning" input data before validation. For example:
158
+
159
+ ```ruby
160
+ field :postcode, scrub: [:strip, :squeeze, :upcase]
161
+ ```
162
+
163
+ The input for this field will have leading/trailing whitespace stripped,
164
+ double (or more) spaces squeezed, and the result upcased automatically.
165
+
166
+ In order to define a custom scrub method just add a symbol/proc entry to
167
+ the `Formeze.scrub_methods` hash.
168
+
153
169
 
154
170
  Rails usage
155
171
  -----------
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'formeze'
3
- s.version = '1.3.0'
3
+ s.version = '1.4.0'
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.authors = ['Tim Craft']
6
6
  s.email = ['mail@timcraft.com']
@@ -18,19 +18,23 @@ module Formeze
18
18
  @name, @options = name, options
19
19
  end
20
20
 
21
- def validate(value, &block)
22
- if value !~ /\S/ # blank
23
- block.call(error(:required, 'is required')) if required?
24
- else
25
- block.call(error(:not_multiline, 'cannot contain newlines')) if !multiline? && value.lines.count > 1
21
+ def scrub(value)
22
+ Array(@options[:scrub]).inject(value) do |tmp, scrub_method|
23
+ Formeze.scrub_methods.fetch(scrub_method).call(tmp)
24
+ end
25
+ end
26
26
 
27
- block.call(error(:too_long, 'is too long')) if value.chars.count > char_limit
27
+ def validate(value)
28
+ if blank?(value)
29
+ yield error(:required, 'is required') if required?
30
+ else
31
+ yield error(:not_multiline, 'cannot contain newlines') if !multiline? && value.lines.count > 1
28
32
 
29
- block.call(error(:too_long, 'is too long')) if word_limit? && value.scan(/\w+/).length > word_limit
33
+ yield error(:too_long, 'is too long') if too_long?(value)
30
34
 
31
- block.call(error(:no_match, 'is invalid')) if pattern? && value !~ pattern
35
+ yield error(:no_match, 'is invalid') if no_match?(value)
32
36
 
33
- block.call(error(:bad_value, 'is invalid')) if values? && !values.include?(value)
37
+ yield error(:bad_value, 'is invalid') if values? && !values.include?(value)
34
38
  end
35
39
  end
36
40
 
@@ -47,11 +51,7 @@ module Formeze
47
51
  end
48
52
 
49
53
  def label
50
- if @options.has_key?(:label)
51
- @options[:label]
52
- else
53
- translate(name, scope: [:formeze, :labels], default: Label.new(name))
54
- end
54
+ @options.fetch(:label) { translate(name, scope: [:formeze, :labels], default: Label.new(name)) }
55
55
  end
56
56
 
57
57
  def required?
@@ -66,24 +66,24 @@ module Formeze
66
66
  @options.fetch(:multiple) { false }
67
67
  end
68
68
 
69
- def char_limit
70
- @options.fetch(:char_limit) { 64 }
69
+ def too_long?(value)
70
+ too_many_characters?(value) || too_many_words?(value)
71
71
  end
72
72
 
73
- def word_limit?
74
- @options.has_key?(:word_limit)
73
+ def too_many_characters?(value)
74
+ value.chars.count > @options.fetch(:char_limit) { 64 }
75
75
  end
76
76
 
77
- def word_limit
78
- @options.fetch(:word_limit)
77
+ def too_many_words?(value)
78
+ @options.has_key?(:word_limit) && value.scan(/\w+/).length > @options[:word_limit]
79
79
  end
80
80
 
81
- def pattern?
82
- @options.has_key?(:pattern)
81
+ def no_match?(value)
82
+ @options.has_key?(:pattern) && value !~ @options[:pattern]
83
83
  end
84
84
 
85
- def pattern
86
- @options.fetch(:pattern)
85
+ def blank?(value)
86
+ value !~ /\S/
87
87
  end
88
88
 
89
89
  def values?
@@ -218,11 +218,13 @@ module Formeze
218
218
  end
219
219
 
220
220
  values.each do |value|
221
- field.validate(value) do |error|
221
+ scrubbed_value = field.scrub(value)
222
+
223
+ field.validate(scrubbed_value) do |error|
222
224
  errors << UserError.new("#{field.label} #{error}")
223
225
  end
224
226
 
225
- send(:"#{field.name}=", value)
227
+ send(:"#{field.name}=", scrubbed_value)
226
228
  end
227
229
  end
228
230
 
@@ -252,6 +254,16 @@ module Formeze
252
254
  end
253
255
  end
254
256
 
257
+ def self.scrub_methods
258
+ @scrub_methods ||= {
259
+ :strip => :strip.to_proc,
260
+ :upcase => :upcase.to_proc,
261
+ :downcase => :downcase.to_proc,
262
+ :squeeze => proc { |string| string.squeeze(' ') },
263
+ :squeeze_lines => proc { |string| string.gsub(/(\r?\n)(\r?\n)(\r?\n)+/, '\\1\\2') }
264
+ }
265
+ end
266
+
255
267
  def self.setup(form)
256
268
  form.send :include, InstanceMethods
257
269
 
@@ -507,3 +507,22 @@ describe 'I18n integration' do
507
507
  form.errors.first.to_s.must_equal('TITLE is required')
508
508
  end
509
509
  end
510
+
511
+ class FormWithScrubbedFields
512
+ Formeze.setup(self)
513
+
514
+ field :postcode, scrub: [:strip, :squeeze, :upcase], pattern: /\A[A-Z0-9]{2,4} [A-Z0-9]{3}\z/
515
+ field :bio, scrub: [:strip, :squeeze_lines], multiline: true
516
+ end
517
+
518
+ describe 'FormWithScrubbedFields' do
519
+ describe 'parse method' do
520
+ it 'should apply the scrub methods to the input before validation' do
521
+ form = FormWithScrubbedFields.new
522
+ form.parse('postcode=++sw1a+++1aa&bio=My+name+is+Cookie+Monster.%0A%0A%0A%0AI+LOVE+COOKIES!!!!%0A%0A%0A%0A')
523
+ form.postcode.must_equal('SW1A 1AA')
524
+ form.bio.count(?\n).must_equal(2)
525
+ form.valid?.must_equal(true)
526
+ end
527
+ end
528
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: formeze
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: