formeze 1.3.0 → 1.4.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/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: