activevalidators 1.5.1 → 1.6.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.
@@ -0,0 +1,10 @@
1
+ # Offical support for MRIs + REE
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - ruby-head
7
+ - ree
8
+ - rbx
9
+ - rbx-2.0
10
+ - jruby
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ActiveValidators
1
+ ActiveValidators [![Travis](http://travis-ci.org/cesario/activevalidators.png)](http://travis-ci.org/cesario/activevalidators)
2
2
  ================
3
3
 
4
4
  Collection of ActiveModel/ActiveRecord validations
@@ -49,17 +49,18 @@ In your models, the gem provides new validators like `email`, or `url`:
49
49
 
50
50
  Exhaustive list of supported validators and their implementation:
51
51
 
52
- * `email` : based on the `mail` gem
53
- * `url` : based on a regular expression
54
- * `phone` : based on a set of predefined masks
55
- * `twitter` : based on a regular expression
56
- * `slug` : based on `ActiveSupport::String#parameterize`
57
- * `ip` : based on `Resolv::IPv[4|6]::Regex`
58
52
  * `credit_card` : based on the `Luhn` algorithm
59
53
  * `date` : based on the `DateValidator` gem
54
+ * `email` : based on the `mail` gem
55
+ * `ip` : based on `Resolv::IPv[4|6]::Regex`
60
56
  * `password` : based on a set of regular expressions
57
+ * `phone` : based on a set of predefined masks
61
58
  * `postal_code`: based on a set of predefined masks
59
+ * `respond_to`
60
+ * `slug` : based on `ActiveSupport::String#parameterize`
62
61
  * `tracking_number`: based on a set of predefined masks
62
+ * `twitter` : based on a regular expression
63
+ * `url` : based on a regular expression
63
64
 
64
65
  Todo
65
66
  ----
@@ -89,6 +90,8 @@ Contributors
89
90
  * Garrett Bjerkhoel
90
91
  * Renato Riccieri Santos Zannon
91
92
  * Brian Moseley
93
+ * Travis Vachon
94
+ * Rob Zuber
92
95
 
93
96
  Copyright
94
97
  ---------
data/Rakefile CHANGED
@@ -1,8 +1,21 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.unshift File.expand_path("../lib", __FILE__)
3
-
4
1
  require 'rubygems'
5
2
  require 'rubygems/specification'
3
+
4
+ require 'bundler'
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ require 'rake/testtask'
8
+ Rake::TestTask.new do |t|
9
+ t.libs << "test"
10
+ t.pattern = "test/**/*_test.rb"
11
+ t.verbose = true
12
+ t.warning = true
13
+ end
14
+
15
+ require 'turn'
16
+
17
+ # -*- encoding: utf-8 -*-
18
+ $:.unshift File.expand_path("../lib", __FILE__)
6
19
  require 'activevalidators'
7
20
 
8
21
  def gemspec
@@ -12,31 +25,15 @@ def gemspec
12
25
  end
13
26
  end
14
27
 
15
- begin
16
- require 'rspec/core/rake_task'
17
-
18
- desc "Run specs"
19
- RSpec::Core::RakeTask.new do |t|
20
- t.rspec_opts = %w(-fs --color)
21
- t.ruby_opts = %w(-w)
22
- end
23
-
24
- namespace :spec do
25
- task :clean do
26
- rm_rf 'tmp'
27
- rm_rf 'pkg'
28
- end
29
-
30
- desc "Run the full spec suite"
31
- task :full => ["clean", "spec"]
32
- end
33
-
34
- rescue LoadError
35
- task :spec do
36
- abort "Run `gem install rspec` to be able to run specs"
37
- end
28
+ desc "Clean the current directory"
29
+ task :clean do
30
+ rm_rf 'tmp'
31
+ rm_rf 'pkg'
38
32
  end
39
33
 
34
+ desc "Run the full spec suite"
35
+ task :full => ["clean", "test"]
36
+
40
37
  desc "install the gem locally"
41
38
  task :install => :package do
42
39
  sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}}
@@ -59,4 +56,4 @@ task :install => :gem do
59
56
  sh "gem install pkg/#{gemspec.full_name}.gem"
60
57
  end
61
58
 
62
- task :default => :spec
59
+ task :default => :full
@@ -1,10 +1,11 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  lib = File.expand_path('../lib/', __FILE__)
3
3
  $:.unshift lib unless $:.include?(lib)
4
+ require 'active_validators/version'
4
5
 
5
6
  Gem::Specification.new do |s|
6
7
  s.name = "activevalidators"
7
- s.version = '1.5.1'
8
+ s.version = ActiveValidators::VERSION
8
9
  s.platform = Gem::Platform::RUBY
9
10
  s.authors = ["Franck Verrot", "Paco Guzmán", "Oriol Gual", "Garrett Bjerkhoel", "Renato Riccieri Santos Zannon", "Brian Moseley"]
10
11
  s.email = ["franck@verrot.fr"]
@@ -13,14 +14,13 @@ Gem::Specification.new do |s|
13
14
  s.description = %q{ActiveValidators is a collection of ActiveModel/ActiveRecord validations}
14
15
 
15
16
  s.add_development_dependency "bundler"
16
- s.add_development_dependency "rspec"
17
- s.add_development_dependency "rspec-core"
18
- s.add_development_dependency "rspec-expectations"
19
- s.add_dependency 'activerecord' , '~> 3.0.0'
20
- s.add_dependency 'activemodel' , '~> 3.0.0'
21
- s.add_dependency 'mail' , '~> 2.2.15'
22
- s.add_dependency 'date_validator', '~> 0.6.1'
23
- s.add_dependency 'date_validator', '~> 0.6.1'
17
+ s.add_development_dependency "minitest"
18
+ s.add_development_dependency "turn"
19
+ s.add_dependency 'rake' , '>= 0.8.7'
20
+ s.add_dependency 'activerecord' , '>= 3.0.0'
21
+ s.add_dependency 'activemodel' , '>= 3.0.0'
22
+ s.add_dependency 'mail'
23
+ s.add_dependency 'date_validator'
24
24
 
25
25
  s.files = `git ls-files`.split("\n")
26
26
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -2,30 +2,67 @@ module ActiveModel
2
2
  module Validations
3
3
  class TrackingNumberValidator < EachValidator
4
4
  def validate_each(record, attribute, value)
5
- @value = value
6
- carrier = options[:carrier]
5
+ carrier = options[:carrier] || (options[:carrier_field] && record.send(options[:carrier_field]))
7
6
  raise "Carrier option required" unless carrier
8
- @formats = TrackingNumberValidator.known_formats[carrier]
9
- raise "No known tracking number formats for carrier #{carrier}" unless @formats
10
- record.errors.add(attribute) unless matches_any?
7
+ method = "valid_#{carrier.to_s}?"
8
+ raise "Tracking number validation not supported for carrier #{carrier}" unless self.respond_to?(method)
9
+ record.errors.add(attribute) unless self.send(method, value)
11
10
  end
12
11
 
13
- def self.known_formats
14
- @@known_formats ||= {
15
- # see https://www.ups.com/content/us/en/tracking/help/tracking/tnh.html
16
- :ups => ['1Z................', '............', 'T..........', '.........'],
17
- }
12
+ # UPS:
13
+ # ups tracking codes are validated solely on their format
14
+ # see https://www.ups.com/content/us/en/tracking/help/tracking/tnh.html
15
+ UPS_REGEXES = [ /^1Z[a-zA-Z0-9]{16}$/, /^[a-zA-Z0-9]{12}$/, /^[a-zA-Z0-9]{9}$/, /^T[a-zA-Z0-9]{10}$/ ]
16
+ def valid_ups?(value)
17
+ !!UPS_REGEXES.detect { |fmt| value.match(fmt) }
18
18
  end
19
19
 
20
- def matches_any?
21
- false if @formats.nil? or not @formats.respond_to?(:detect)
22
- @formats.detect { |format| @value.match(TrackingNumberValidator.regexp_from format) }
20
+ # USPS:
21
+ # usps tracking codes are validated based on format (one of USS228 or USS39)
22
+ # and a check digit (using either of the USPS's MOD10 or MOD11 algorithms)
23
+ # see USPS Publications:
24
+ # - #91 (05/2008) pp. 38
25
+ # - #97 (05/2002) pp. 62-63
26
+ # - #109 (09/2007) pp. 19-21
27
+ def valid_usps?(value)
28
+ uss228?(value) || uss39?(value)
23
29
  end
24
30
 
25
- private
31
+ USS128_REGEX = /^(\d{19,21})(\d)$/
32
+ def uss228?(value)
33
+ m = value.match(USS128_REGEX)
34
+ m.present? && (m[2].to_i == usps_mod10(m[1]))
35
+ end
36
+
37
+ USS39_REGEX = /^[a-zA-Z0-9]{2}(\d{8})(\d)US$/
38
+ def uss39?(value)
39
+ m = value.match(USS39_REGEX)
40
+ # it appears to be valid for a USS39 barcode's checkdigit to be calculated with either the usps mod 10
41
+ # algorithm or the usps mod 11.
42
+ m.present? && (m[2].to_i == usps_mod10(m[1]) || m[2].to_i == usps_mod11(m[1]))
43
+ end
44
+
45
+ MOD10_WEIGHTS = [3,1]
46
+ def usps_mod10(chars)
47
+ 10 - weighted_sum(chars.reverse, MOD10_WEIGHTS) % 10
48
+ end
49
+
50
+ MOD11_WEIGHTS = [8,6,4,2,3,5,9,7]
51
+ def usps_mod11(chars)
52
+ mod = weighted_sum(chars, MOD11_WEIGHTS) % 11
53
+ case mod
54
+ when 0 then 5
55
+ when 1 then 0
56
+ else 11 - mod
57
+ end
58
+ end
26
59
 
27
- def self.regexp_from(format)
28
- Regexp.new "^"+(Regexp.escape format).gsub('\#','\d').gsub('\.','[a-zA-Z0-9]')+"$"
60
+ # takes a string containing digits and calculates a checksum using the provided weight array
61
+ # cycles the weight array if it's not long enough
62
+ def weighted_sum(value, weights)
63
+ digits = value.split('').map { |d| d.to_i }
64
+ weights = weights.cycle.take(digits.count) if weights.count < digits.count
65
+ digits.zip(weights).inject(0) { |s,p| s + p[0] * p[1] }
29
66
  end
30
67
  end
31
68
  end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module ActiveValidators
3
+ VERSION = '1.6.0'
4
+ end
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+
3
+ # silence warnings
4
+ old_w, $-w = $-w, false
5
+
6
+ begin; require 'turn'; rescue LoadError; end
7
+ require 'minitest/spec'
8
+ require 'minitest/mock'
9
+ require 'minitest/autorun'
10
+
11
+ # unsilence warnings
12
+ $-w = old_w
13
+
14
+ require 'activevalidators'
15
+
16
+ %w(models).each do |directory|
17
+ Dir["#{File.dirname(__FILE__)}/#{directory}/*.rb"].each {|f| require f}
18
+ end
19
+
20
+ class TestRecord
21
+ include ActiveModel::Validations
22
+ attr_accessor :ip, :url, :slug, :responder, :global_condition,
23
+ :local_condition, :phone, :email, :card, :password, :twitter_username,
24
+ :postal_code, :carrier, :tracking_number
25
+
26
+ def initialize(attrs = {})
27
+ attrs.each_pair { |k,v| send("#{k}=", v) }
28
+ end
29
+ end
@@ -1,13 +1,6 @@
1
- require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
1
+ require 'test_helper.rb'
2
2
 
3
3
  describe "Credit Card Validation" do
4
- before(:each) do
5
- TestRecord.reset_callbacks(:validate)
6
- TestRecord.validates :card, :credit_card => { :type => :any }
7
- end
8
-
9
- subject { TestRecord.new }
10
-
11
4
  # Here are some valid credit cards
12
5
  VALID_CARDS =
13
6
  {
@@ -37,26 +30,25 @@ describe "Credit Card Validation" do
37
30
 
38
31
  VALID_CARDS.each_pair do |card, number|
39
32
  it "accepts #{card} valid cards" do
40
- subject.card = number
41
- subject.should be_valid
42
- subject.should have(0).errors
33
+ subject = build_card_record :card => number
34
+ subject.valid?.must_equal true
35
+ subject.errors.size.must_equal 0
43
36
  end
44
37
  end
45
38
 
46
39
  describe "for invalid cards" do
40
+ it "rejects invalid cards and generates an error message of type invalid" do
41
+ subject = build_card_record :card => '99999'
42
+ subject.valid?.must_equal false
43
+ subject.errors.size.must_equal 1
47
44
 
48
- before :each do
49
- subject.card = '99999'
50
- end
51
-
52
- it "rejects invalid cards" do
53
- subject.should_not be_valid
54
- subject.should have(1).error
45
+ subject.errors[:card].include?(subject.errors.generate_message(:card, :invalid)).must_equal true
55
46
  end
47
+ end
56
48
 
57
- it "generates an error message of type invalid" do
58
- subject.should_not be_valid
59
- subject.errors[:card].should include subject.errors.generate_message(:card, :invalid)
60
- end
49
+ def build_card_record(attrs = {})
50
+ TestRecord.reset_callbacks(:validate)
51
+ TestRecord.validates :card, :credit_card => { :type => :any }
52
+ TestRecord.new attrs
61
53
  end
62
54
  end
@@ -0,0 +1,42 @@
1
+ require 'test_helper.rb'
2
+
3
+ describe "Email Validation" do
4
+ it "accepts valid emails" do
5
+ subject = build_email_record :email => 'franck@verrot.fr'
6
+ subject.valid?.must_equal true
7
+ subject.errors.size.must_equal 0
8
+ end
9
+
10
+ it "accepts complete emails" do
11
+ subject = build_email_record :email => 'Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>'
12
+ subject.valid?.must_equal true
13
+ subject.errors.size.must_equal 0
14
+ end
15
+
16
+ describe "for invalid emails" do
17
+ it "rejects invalid emails" do
18
+ subject = build_email_record :email => 'franck.fr'
19
+ subject.valid?.must_equal false
20
+ subject.errors.size.must_equal 1
21
+ end
22
+
23
+ it 'rejects local emails' do
24
+ subject = build_email_record :email => 'franck.fr'
25
+ subject.email = 'franck'
26
+ subject.valid?.must_equal false
27
+ subject.errors.size.must_equal 1
28
+ end
29
+
30
+ it "generates an error message of type invalid" do
31
+ subject = build_email_record :email => 'franck.fr'
32
+ subject.valid?.must_equal false
33
+ subject.errors[:email].include?(subject.errors.generate_message(:email, :invalid)).must_equal true
34
+ end
35
+ end
36
+
37
+ def build_email_record(attrs = {})
38
+ TestRecord.reset_callbacks(:validate)
39
+ TestRecord.validates :email, :email => true
40
+ TestRecord.new attrs
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ require 'test_helper.rb'
2
+
3
+ describe "IP Validation" do
4
+ describe "IPv4 Validation" do
5
+ it "accepts valid IPs" do
6
+ subject = build_ip_record :v4, :ip => '192.168.1.1'
7
+ subject.valid?.must_equal true
8
+ subject.errors.size.must_equal 0
9
+ end
10
+
11
+ describe "for invalid IPs" do
12
+ it "rejects invalid IPs" do
13
+ subject = build_ip_record :v4, :ip => '267.34.56.3'
14
+ subject.valid?.must_equal false
15
+ subject.errors.size.must_equal 1
16
+ end
17
+
18
+ it "generates an error message of type invalid" do
19
+ subject = build_ip_record :v4, :ip => '267.34.56.3'
20
+ subject.valid?.must_equal false
21
+ subject.errors[:ip].include?(subject.errors.generate_message(:ip, :invalid)).must_equal true
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "IPv6 Validation" do
27
+ it "accepts valid IPs" do
28
+ subject = build_ip_record :v6, :ip => '::1'
29
+ subject.valid?.must_equal true
30
+ subject.errors.size.must_equal 0
31
+ end
32
+
33
+ describe "for invalid IPs" do
34
+ it "rejects invalid IPs" do
35
+ subject = build_ip_record :v6, :ip => '192.168.1.1'
36
+ subject.valid?.must_equal false
37
+ subject.errors.size.must_equal 1
38
+ end
39
+
40
+ it "generates an error message of type invalid" do
41
+ subject = build_ip_record :v6, :ip => '192.168.1.1'
42
+ subject.valid?.must_equal false
43
+ subject.errors[:ip].include?(subject.errors.generate_message(:ip, :invalid)).must_equal true
44
+ end
45
+ end
46
+ end
47
+
48
+ it "checks validity of the arguments" do
49
+ [3, "foo", 1..6].each do |wrong_argument|
50
+ assert_raises(ArgumentError,"Unknown IP validator format #{wrong_argument.inspect}") do
51
+ TestRecord.validates :ip, :ip => { :format => wrong_argument }
52
+ end
53
+ end
54
+ end
55
+
56
+ def build_ip_record(version, attrs = {})
57
+ TestRecord.reset_callbacks(:validate)
58
+ TestRecord.validates :ip, :ip => { :format => version }
59
+ TestRecord.new attrs
60
+ end
61
+ end
@@ -0,0 +1,40 @@
1
+ require 'test_helper.rb'
2
+
3
+ describe "Password Validation" do
4
+ STRENGTHS = {
5
+ :weak => { :valid => 'sixchr', :invalid => 'foo' },
6
+ :medium => { :valid => 'chrs123', :invalid => 'sixchr' },
7
+ :strong => { :valid => 'HQSij2323#$%', :invalid => 'chrs123' }
8
+ }
9
+
10
+ STRENGTHS.each_pair do |strength, passwords|
11
+ describe "#{strength} mode" do
12
+ describe "valid passwords" do
13
+ it "accepts a #{strength} password like #{passwords[:valid]}" do
14
+ subject = build_password_record strength, :password => passwords[:valid]
15
+ subject.valid?.must_equal true
16
+ subject.errors.size.must_equal 0
17
+ end
18
+ end
19
+
20
+ describe "invalid passwords" do
21
+ it "rejects invalid passwords like #{passwords[:invalid]}" do
22
+ subject = build_password_record strength, :password => passwords[:invalid]
23
+ subject.valid?.must_equal false
24
+ subject.errors.size.must_equal 1
25
+ end
26
+
27
+ it "generates an error message of type invalid" do
28
+ subject = build_password_record strength, :password => passwords[:invalid]
29
+ subject.valid?.must_equal false
30
+ subject.errors[:password].include?(subject.errors.generate_message(:password, :invalid)).must_equal true
31
+ end
32
+ end
33
+ end
34
+ end
35
+ def build_password_record(strength, attrs = {})
36
+ TestRecord.reset_callbacks(:validate)
37
+ TestRecord.validates :password, :password => { :strength => strength }
38
+ TestRecord.new attrs
39
+ end
40
+ end