citizenship 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9e3144972c896bbb5b398689419c22a0a2b1a851
4
+ data.tar.gz: 5e104377c0d24d3df7eb08f0e9e6cf76f1cdaa61
5
+ SHA512:
6
+ metadata.gz: a1256fe4db0f000a8620cebbc32a2e7055b01e0c59ce3d1c51f361f2ff3eb67dbe900d0488ffc325dce56ae736db7e079894b51814bac5c2e345570e0328cca0
7
+ data.tar.gz: 33887abb5376484c9edeba657c8b5bc635ad1d067f3ed6ff7414bf397b939cd660439abf81062b04037483dd37105000bf0608ef0615473b3afe663eaabcb3aa
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .rvmrc
19
+ .rspec
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.2
6
+ - 2.2.0
7
+ - rbx-2
8
+ script: bundle exec rake test
9
+ notifications:
10
+ recipients:
11
+ - s.freire@runtime-revolution.com
data/CHANGELOG ADDED
@@ -0,0 +1,10 @@
1
+ 1.0.0
2
+
3
+ * Public version, published to Rubygems
4
+ * Updated tests in order to run with recent RSpec
5
+ * Updated documentation and added this CHANGELOG
6
+ * Updated gemspec
7
+
8
+ 0.0.1.beta
9
+
10
+ * First "internal" version (not tagged)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in citizenship.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Miguel Guinada
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # Citizenship
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/citizen.svg)](http://badge.fury.io/rb/citizen)
4
+ [![Build Status](https://travis-ci.org/runtimerevolution/citizenship.svg?branch=master)](https://travis-ci.org/runtimerevolution/citizenship)
5
+ [![Code Climate](https://codeclimate.com/github/runtimerevolution/citizenship/badges/gpa.svg)](https://codeclimate.com/github/runtimerevolution/citizenship)
6
+ [![Dependency Status](https://gemnasium.com/runtimerevolution/citizenship.svg)](https://gemnasium.com/runtimerevolution/citizenship)
7
+
8
+ Citizenship provides a set of easy validations on personal documents, addresses, bank account numbers and other personal information, of Portuguese citizens or of citizens living in Portugal ;)
9
+
10
+ It supports validation of:
11
+
12
+ - NIB (i.e. "banking account number")
13
+ - NIF (i.e. "fiscal number")
14
+ - local phone numbers
15
+ - identification card ('bilhetes de identidade'), were the old id Portuguese id documents. They are no longer issue, but many haven't yet expired, so are still used alongside the Citizen Card.
16
+ - citizen card ('cartão do cidadão'), are the new ID cards currently in use
17
+ - zip code (partial validation)
18
+ - e-mail (well, just because :) )
19
+
20
+
21
+ ## Installation
22
+
23
+ Add this line to your Rails application's Gemfile:
24
+
25
+ gem 'citizenship'
26
+
27
+ And then execute:
28
+
29
+ $ bundle
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install citizenship
34
+
35
+ and use it standalone.
36
+
37
+ ## Validations
38
+
39
+ ### NIB
40
+
41
+ ```ruby
42
+ result=Citizenship.valid_nib?("003503730000539151280") # returns true
43
+ result=Citizenship.valid_nib?('0035.03730000539.1512.80') # returns true
44
+ result=Citizenship.valid_nib?('38476129847') # returns false (since it fails proper size and CRC)
45
+ result=Citizenship.valid_nib?('0035.03730000539.1512.80', strict: true) # returns false
46
+ Citizenship.valid_nib!(nib) # raises Citizenship::Error if invalid
47
+ ```
48
+
49
+ ### NIF
50
+
51
+ ```ruby
52
+ result=Citizenship.valid_nif?("123456789") # returns true
53
+ Citizenship.valid_nif!(nif) # raises Citizenship::Error if invalid
54
+ nif=Citizenship.valid_nif!("123456789") # returns "123456789"
55
+ result=Citizenship.valid_nif?(' 123456789', strict: true) # returns false (strict validation disallows whitespaces and others)
56
+ ```
57
+
58
+ ### Phone Number
59
+
60
+ ```ruby
61
+ phone=Citizenship.valid_phone!('+351 96 933 2233', only_prefixes: ['93', '96']) # returns "+351 96 933 2233"
62
+ phone=Citizenship.valid_phone!('262-999-666') # returns "262-999-666"
63
+ result=Citizenship.valid_phone?('262999666', strict: true) # returns true
64
+ result=Citizenship.valid_phone?('+351 93 933 2233', allow_country_prefix: false) # returns false (since country prefix was used)
65
+ phone=Citizenship.valid_phone!('+351 93 933 2233') # returns "+351 93 933 2233"
66
+ ```
67
+
68
+ ### Identification Card
69
+
70
+ ```ruby
71
+ result=Citizenship.valid_identification_card?('156 944 80', '4') # returns true
72
+ id=Citizenship.valid_identification_card!('156 944 8', '8') # returns '156 944 8'
73
+ ```
74
+
75
+ ### Citizen Card
76
+
77
+ ```ruby
78
+ result=Citizenship.valid_citizen_card?('00000000 0 ZZ 4') # returns true
79
+ id=Citizenship.valid_citizen_card('00000000-0-ZZ-4') # returns '00000000-0-ZZ-4'
80
+ ```
81
+
82
+ ### Zip Code
83
+
84
+ ```ruby
85
+ result=Citizenship.valid_zip_code?('1000-100') # returns true
86
+ ```
87
+
88
+ ### E-mail
89
+
90
+ ```ruby
91
+ result=Citizenship.valid_email?("user@example.org") # returns true
92
+ result=Citizenship.valid_email?(" user@example.org ") # returns false (due to whitespaces)
93
+ result=Citizenship.valid_email?("@example.org") # returns false (due to missing user)
94
+ ```
95
+
96
+ ## Rails Validators
97
+
98
+ We provide a useful range of Rails validators that you can include in your models, namely:
99
+
100
+ * NifValidator
101
+ * NibValidator
102
+ * PhoneValidator
103
+ * EmailValidator
104
+ * ZipCodeValidor
105
+
106
+ ### Example
107
+
108
+
109
+ class User < ActiveRecord::Base
110
+ validates :fiscal_number, nif: true
111
+ ...
112
+ end
113
+
114
+
115
+ ## Future Developments
116
+
117
+ * Support full zip code validation
118
+ * Support IBAN
119
+ * Support validating documents for other EU countries
120
+
121
+ ## References
122
+
123
+ * [NIB](http://pt.wikipedia.org/wiki/N%C3%BAmero_de_Identifica%C3%A7%C3%A3o_Banc%C3%A1ria)
124
+ * [NIF](http://pt.wikipedia.org/wiki/N%C3%BAmero_de_identifica%C3%A7%C3%A3o_fiscal)
125
+ * [Citizen Card](http://www.cartaodecidadao.pt/images/stories/Algoritmo_Num_Documento_CC.pdf)
126
+ * [(old) Identification Card](http://geramat.blogs.sapo.pt/13528.html)
127
+ * [ZIP codes](http://pt.wikipedia.org/wiki/N%C3%BAmero_de_Identifica%C3%A7%C3%A3o_Banc%C3%A1ria)
128
+
129
+
130
+
131
+ ## Contributing
132
+
133
+ 1. Fork it
134
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
135
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
136
+ 4. Push to the branch (`git push origin my-new-feature`)
137
+ 5. Create new Pull Request
138
+
139
+ ## License
140
+ Copyright © 2015 [Runtime Revolution](http://www.runtime-revolution.com), released under the MIT license.
141
+
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rspec/core/rake_task'
6
+ require 'simplecov'
7
+
8
+ task :test => :spec
9
+ task :default => :spec
10
+
11
+ RSpec::Core::RakeTask.new do |task|
12
+ task.rspec_opts = ["-c", "-f documentation", "-r ./spec/helper.rb"]
13
+ task.pattern = 'spec/**/*_spec.rb'
14
+ end
15
+
16
+ desc "Open an interactive session preloaded with this gem's code"
17
+ task :console do
18
+ if gem_available?("pry")
19
+ sh "pry -I lib -r citizenship.rb"
20
+ else
21
+ sh "irb -rubygems -I lib -r citizenship.rb"
22
+ end
23
+ end
24
+
25
+ #
26
+ # Determins if a gem is available at the current runtime
27
+ #
28
+ def gem_available?(name)
29
+ Gem::Specification.find_by_name(name)
30
+ rescue Gem::LoadError
31
+ false
32
+ rescue
33
+ Gem.available?(name) #for backwards compatibility
34
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'citizenship/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "citizenship"
8
+ gem.version = Citizenship::VERSION
9
+ gem.platform = Gem::Platform::RUBY
10
+ gem.authors = ["Runtime Revolution"]
11
+ gem.description = %q{citizenship checks validity of civil id numbers and similar used in Portugal}
12
+ gem.summary = %q{citizenship checks validity of civil id numbers and similar used in Portugal}
13
+ gem.homepage = "https://github.com/runtimerevolution/citizenship"
14
+ gem.email = 'info@runtime-revolution.com'
15
+ gem.license = "MIT"
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ["lib"]
21
+
22
+ gem.add_dependency 'i18n', ['>= 0.6.4', '<= 0.7.0']
23
+
24
+ gem.add_development_dependency "bundler", "~> 1.3"
25
+ gem.add_development_dependency "rake"
26
+ gem.add_development_dependency "rspec", "~> 3.2.0"
27
+
28
+ gem.add_development_dependency 'simplecov'
29
+ gem.add_development_dependency 'pry'
30
+ end
@@ -0,0 +1,41 @@
1
+ require 'citizenship/version'
2
+ require 'citizenship/errors'
3
+ require 'citizenship/nif'
4
+ require 'citizenship/nib'
5
+ require 'citizenship/citizen_card'
6
+ require 'citizenship/identification_card'
7
+ require 'citizenship/phone'
8
+ require 'citizenship/email'
9
+ require 'citizenship/zip_code'
10
+ require 'citizenship/rails/validators' if defined?(ActiveModel)
11
+
12
+ module Citizenship
13
+ private
14
+ #Decimal check bit common processing
15
+ def self.decimal_check_digit(number, cover_digit_collision_flaw = true)
16
+ number = String(number)
17
+
18
+ multiplier = 1
19
+ result = number.delete(' ').split('').map(&:to_i).reverse.map do |digit|
20
+ digit * (multiplier += 1)
21
+ end.reduce(:+) || 0
22
+
23
+ #Cover for the digit collision flaw?
24
+ return 0 if (control_number = 11 - result % 11) > 9 and cover_digit_collision_flaw
25
+ control_number
26
+ end
27
+
28
+ def self.decimal_check_digit_match?(number, check_digit, cover_digit_collision_flaw = true)
29
+ decimal_check_digit(number, cover_digit_collision_flaw) == check_digit.to_i
30
+ end
31
+
32
+ def self.is_number?(i)
33
+ true if Float(i) rescue false
34
+ end
35
+
36
+ def self.remove_special_chars(str)
37
+ String(str).delete(' ').delete('-').delete('.')
38
+ end
39
+ end
40
+
41
+ I18n.load_path += Dir.glob( File.join(File.dirname(__FILE__), 'locales'.freeze, '*.yml'.freeze) )
@@ -0,0 +1,36 @@
1
+ module Citizenship
2
+ #http://www.cartaodecidadao.pt/images/stories/Algoritmo_Num_Documento_CC.pdf
3
+ def self.valid_citizen_card!(number)
4
+ id_number = remove_special_chars(number)
5
+
6
+ raise CitizenCardError, :size if id_number.size != 12
7
+
8
+ double_digit = false
9
+ conversion_table = ('a'..'z').to_a
10
+ check_digit = id_number.reverse.split('').map do |element|
11
+ #switch letters for the value in the conversion table (see 2.3. on Algoritmo_Num_Documento_CC.pdf)
12
+ is_number?(element) ? element : conversion_table.find_index(element.downcase) + 10
13
+ end.map(&:to_i).map do |digit|
14
+ begin
15
+ if double_digit #starting on the 2nd value double every value steping by 2
16
+ value = digit * 2
17
+ value >= 10 ? value - 9 : value #If the doubling result is 10 or more, subtract 9
18
+ else
19
+ digit
20
+ end
21
+ ensure
22
+ double_digit = not(double_digit)
23
+ end
24
+ end.reduce(:+) #sum everything
25
+
26
+ raise CitizenCardError, :invalid_check_digit if check_digit % 10 != 0
27
+ number
28
+ end
29
+
30
+ def self.valid_citizen_card?(number)
31
+ valid_citizen_card!(number)
32
+ true
33
+ rescue CitizenCardError
34
+ false
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ module Citizenship
2
+ EMAIL_REGEXP = /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/i
3
+
4
+ def self.valid_email!(email)
5
+ raise EmailError, :invalid_email unless email.match(EMAIL_REGEXP)
6
+ email
7
+ end
8
+
9
+ def self.valid_email?(email)
10
+ valid_email!(email)
11
+ true
12
+ rescue EmailError
13
+ false
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ require 'pathname'
2
+ require 'i18n'
3
+
4
+ module Citizenship
5
+ class Error < StandardError
6
+ def initialize(key, values = {}, scope)
7
+ super(I18n.t(key, values.merge(scope: "citizenship.#{scope}")))
8
+ end
9
+ end
10
+
11
+ class NIFError < Error
12
+ def initialize(key, values = {})
13
+ super(key, values, :nif)
14
+ end
15
+ end
16
+
17
+ class NIBError < Error
18
+ def initialize(key, values = {})
19
+ super(key, values, :nib)
20
+ end
21
+ end
22
+
23
+ class CitizenCardError < Error
24
+ def initialize(key, values = {})
25
+ super(key, values, :citizen_card)
26
+ end
27
+ end
28
+
29
+ class IdentificationCardError < Error
30
+ def initialize(key, values = {})
31
+ super(key, values, :identification_card)
32
+ end
33
+ end
34
+
35
+ class PhoneError < Error
36
+ def initialize(key, values = {})
37
+ super(key, values, :phone)
38
+ end
39
+ end
40
+
41
+ class EmailError < Error
42
+ def initialize(key, values = {})
43
+ super(key, values, :email)
44
+ end
45
+ end
46
+
47
+ class ZipCodeError < Error
48
+ def initialize(key, values = {})
49
+ super(key, values, :zip_code)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,17 @@
1
+ #see: http://geramat.blogs.sapo.pt/13528.html
2
+ module Citizenship
3
+ def self.valid_identification_card!(number, check_digit)
4
+ id_number, check_digit = String(number).delete(' '), String(check_digit)
5
+
6
+ raise IdentificationCardError, :size unless id_number.size == 7 or id_number.size == 8
7
+ raise IdentificationCardError, :invalid_check_digit unless decimal_check_digit_match?(id_number, check_digit)
8
+ number
9
+ end
10
+
11
+ def self.valid_identification_card?(number, check_digit)
12
+ valid_identification_card!(number, check_digit)
13
+ true
14
+ rescue IdentificationCardError
15
+ false
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ #see: http://pt.wikipedia.org/wiki/N%C3%BAmero_de_Identifica%C3%A7%C3%A3o_Banc%C3%A1ria
2
+ module Citizenship
3
+ def self.valid_nib!(nib, options = {})
4
+ strict = options.fetch(:strict, false)
5
+ bank_codes = ['0007', #BES
6
+ '0010', #BPI
7
+ '0018', #Santander
8
+ '0019', #BBVA
9
+ '0025', #Caixa BI
10
+ '0032', #Barclays
11
+ '0033', #BCP
12
+ '0034', #BNP Paribas
13
+ '0035', #CGD
14
+ '0036', #Montepio
15
+ '0038', #Banif
16
+ '0043', #Deutsche Bank
17
+ '0045', #CA Crédito Agrícola
18
+ '0046', #Banco Popular
19
+ '0061', #BiG
20
+ '0065', #Best
21
+ '0076', #Finibanco
22
+ '0079', #BPN
23
+ '0781', #Direcção Geral do Tesouro
24
+ '5180'] #Caixa Central de Crédito Agrícola Mútuo
25
+
26
+ escaped_nib = strict ? nib : remove_special_chars(nib)
27
+ raise NIBError.new(:size) unless escaped_nib.size == 21
28
+ raise NIBError.new(:invalid_bank_code, bank_code: escaped_nib[0..3]) unless bank_codes.include?(escaped_nib[0..3])
29
+
30
+ check_digit = escaped_nib[19..20].to_i
31
+ escaped_nib = escaped_nib[0..18]
32
+
33
+ conversion_table = [73, 17, 89, 38, 62, 45, 53, 15, 50, 5, 49, 34, 81, 76, 27, 90, 9, 30, 3]
34
+ sum = (0..18).map do |i|
35
+ escaped_nib.slice(i).to_i * conversion_table[i]
36
+ end.reduce(:+)
37
+
38
+ control_number = 98 - sum % 97
39
+ raise NIBError.new(:invalid_check_digit) unless check_digit == control_number
40
+ nib
41
+ end
42
+
43
+ def self.valid_nib?(nib, options = {})
44
+ valid_nib!(nib, options)
45
+ true
46
+ rescue NIBError
47
+ false
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ #see: http://pt.wikipedia.org/wiki/N%C3%BAmero_de_identifica%C3%A7%C3%A3o_fiscal
2
+ module Citizenship
3
+ def self.valid_nif!(number, options = {})
4
+ strict = options.fetch(:strict, false)
5
+ id_number = strict ? number : String(number).delete(' ')
6
+ first_digit_universe = [1, 2, 5, 6, 8, 9]
7
+
8
+ raise NIFError.new(:size) if id_number.size != 9
9
+ raise NIFError.new(:prefix, prefixes: first_digit_universe.join(', ')) unless first_digit_universe.include?(id_number[0].to_i)
10
+ raise NIFError.new(:invalid_check_digit) unless decimal_check_digit_match?(id_number[0..-2], id_number[-1])
11
+ number
12
+ end
13
+
14
+ def self.valid_nif?(number, options = {})
15
+ valid_nif!(number, options)
16
+ true
17
+ rescue NIFError
18
+ false
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ module Citizenship
2
+ def self.valid_phone!(number, opts = {})
3
+ allow_country_prefix = opts.fetch(:allow_country_prefix, true)
4
+ prefix_whitelist = network_prefix_white_list(opts[:only_prefixes])
5
+ strict = opts.fetch(:strict, false)
6
+
7
+ country_prefix = allow_country_prefix ? '((\+351|00351|351)?)' : ''
8
+ regexp_template = "^#{country_prefix}(#{prefix_whitelist}\\d{7})$"
9
+
10
+ phone_number = strict ? number : String(number).delete(' ').delete('-')
11
+ raise PhoneError, :invalid_phone_number unless phone_number.match(Regexp.new(regexp_template))
12
+ number
13
+ end
14
+
15
+ def self.valid_phone?(number, opts = {})
16
+ valid_phone!(number, opts)
17
+ true
18
+ rescue Error
19
+ false
20
+ end
21
+
22
+ private
23
+ def self.network_prefix_white_list(prefixes)
24
+ return '(2\d{1}|(9(3|6|2|1)))' if prefixes.nil?
25
+ prefixes.is_a?(Array) ? "(#{prefixes.map(&:to_s).join('|')})" : "(#{prefixes})"
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ module Citizenship
2
+ module Rails
3
+ module Validators
4
+ class NifValidator < ActiveModel::EachValidator
5
+ def validate_each(record, attribute, value)
6
+ Citizenship.valid_nif!(value, options) if value.present?
7
+ rescue Citizenship::NIFError => e
8
+ record.errors[attribute] << (options[:message] || e.message)
9
+ end
10
+ end
11
+
12
+ class NibValidator < ActiveModel::EachValidator
13
+ def validate_each(record, attribute, value)
14
+ Citizenship.valid_nib!(value, options) if value.present?
15
+ rescue Citizenship::NIBError => e
16
+ record.errors[attribute] << (options[:message] || e.message)
17
+ end
18
+ end
19
+
20
+ class PhoneValidator < ActiveModel::EachValidator
21
+ def validate_each(record, attribute, value)
22
+ Citizenship.valid_phone!(value, options) if value.present?
23
+ rescue Citizenship::PhoneError => e
24
+ record.errors[attribute] << (options[:message] || e.message)
25
+ end
26
+ end
27
+
28
+ class EmailValidator < ActiveModel::EachValidator
29
+ def validate_each(record, attribute, value)
30
+ Citizenship.valid_email!(value) if value.present?
31
+ rescue Citizenship::EmailError => e
32
+ record.errors[attribute] << (options[:message] || e.message)
33
+ end
34
+ end
35
+
36
+ class ZipCodeValidator < ActiveModel::EachValidator
37
+ def validate_each(record, attribute, value)
38
+ Citizenship.valid_zip_code!(value) if value.present?
39
+ rescue Citizenship::ZipCodeError => e
40
+ record.errors[attribute] << (options[:message] || e.message)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ ActiveRecord::Base.send(:include, Citizenship::Rails::Validators)
@@ -0,0 +1,3 @@
1
+ module Citizenship
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,14 @@
1
+ module Citizenship
2
+ def self.valid_zip_code!(zip_code)
3
+ regexp = /^(\d{4})-(\d{3})$/
4
+ raise ZipCodeError, :invalid_zip_code unless zip_code.match(regexp)
5
+ zip_code
6
+ end
7
+
8
+ def self.valid_zip_code?(zip_code)
9
+ valid_zip_code!(zip_code)
10
+ true
11
+ rescue ZipCodeError
12
+ false
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ en:
2
+ citizenship:
3
+ nif:
4
+ size: "must have 9 digits"
5
+ prefix: "first number has to be one of the following: %{prefixes}"
6
+ invalid_check_digit: "Invalid check digit"
7
+ nib:
8
+ size: "must have 21 digits"
9
+ invalid_bank_code: "Invalid bank code: %{bank_code}"
10
+ invalid_check_digit: "Invalid check digit"
11
+ citizen_card:
12
+ size: "Invalid size"
13
+ invalid_check_digit: "Invalid check digit"
14
+ identification_card:
15
+ size: "Invalid size"
16
+ invalid_check_digit: "Invalid check digit"
17
+ phone:
18
+ invalid_phone_number: "Invalid phone number"
19
+
20
+
21
+
22
+
23
+
24
+
@@ -0,0 +1,22 @@
1
+ "pt-PT":
2
+ citizenship:
3
+ nif:
4
+ size: "deve conter 9 digitos"
5
+ prefix: "o primeiro digito tem de ser um dos seguintes: %{prefixes}"
6
+ invalid_check_digit: "inválido"
7
+ nib:
8
+ size: "deve conter 21 digitos"
9
+ invalid_bank_code: "Código bancário inválido: %{bank_code}"
10
+ invalid_check_digit: "inválido"
11
+ citizen_card:
12
+ size: "Tamanho inválido"
13
+ invalid_check_digit: "Cartão de cidadão inválido"
14
+ identification_card:
15
+ size: "Tamanho inválido"
16
+ invalid_check_digit: "inválido"
17
+ phone:
18
+ invalid_phone_number: "inválido"
19
+ email:
20
+ invalid_email: "inválido"
21
+ zip_code:
22
+ invalid_zip_code: "inválido"
@@ -0,0 +1,22 @@
1
+ "pt":
2
+ citizenship:
3
+ nif:
4
+ size: "deve conter 9 digitos"
5
+ prefix: "o primeiro digito tem de ser um dos seguintes: %{prefixes}"
6
+ invalid_check_digit: "inválido"
7
+ nib:
8
+ size: "deve conter 21 digitos"
9
+ invalid_bank_code: "Código bancário inválido: %{bank_code}"
10
+ invalid_check_digit: "inválido"
11
+ citizen_card:
12
+ size: "Tamanho inválido"
13
+ invalid_check_digit: "Cartão de cidadão inválido"
14
+ identification_card:
15
+ size: "Tamanho inválido"
16
+ invalid_check_digit: "inválido"
17
+ phone:
18
+ invalid_phone_number: "inválido"
19
+ email:
20
+ invalid_email: "inválido"
21
+ zip_code:
22
+ invalid_zip_code: "inválido"
@@ -0,0 +1,16 @@
1
+ describe 'citizen card validation' do
2
+ it 'passes on valid citizen card numbers' do
3
+ cc_number = '00000000 0 ZZ 4'
4
+
5
+ expect(Citizenship.valid_citizen_card?(cc_number)).to be_truthy
6
+ expect(Citizenship.valid_citizen_card!(cc_number)).to eq(cc_number)
7
+ expect(Citizenship.valid_citizen_card!('00000000-0-ZZ-4')).to eq('00000000-0-ZZ-4')
8
+ expect(Citizenship.valid_citizen_card!('00000000.0.ZZ.4')).to eq('00000000.0.ZZ.4')
9
+ end
10
+
11
+ it 'fails on invalid citizen card numbers' do
12
+ expect(Citizenship.valid_citizen_card?('')).to be_falsy
13
+ expect { Citizenship.valid_citizen_card!(nil) }.to raise_error(Citizenship::Error)
14
+ expect { Citizenship.valid_citizen_card!('12345678 9 ZZ8') }.to raise_error(Citizenship::Error)
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ describe 'Email validation' do
2
+ it 'passes on valid emails' do
3
+ expect(Citizenship.valid_email?('user@example.org')).to be_truthy
4
+ end
5
+
6
+ it 'falid on invalid emails' do
7
+ expect {Citizenship.valid_email!(' user@example.org')}.to raise_error(Citizenship::EmailError)
8
+ expect(Citizenship.valid_email?('user@example.org ')).to be_falsy
9
+ expect(Citizenship.valid_email?('email@')).to be_falsy
10
+ expect(Citizenship.valid_email?('@example.com')).to be_falsy
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ describe 'identification card validation' do
2
+ it 'passes on valid identification card numbers' do
3
+ expect(Citizenship.valid_identification_card?('156 944 8', '8')).to be_truthy
4
+ expect(Citizenship.valid_identification_card?('156 944 80', '4')).to be_truthy
5
+ expect(Citizenship.valid_identification_card!('156 944 8', '8')).to eq('156 944 8')
6
+ end
7
+
8
+ it 'fails on invalid identification card numbers' do
9
+ expect(Citizenship.valid_identification_card?('156 944 8', '7')).to be_falsy
10
+ expect { Citizenship.valid_identification_card!('156 944 8', '7') }.to raise_error(Citizenship::Error)
11
+ expect { Citizenship.valid_identification_card!('', '0') }.to raise_error(Citizenship::Error)
12
+ expect { Citizenship.valid_identification_card!(nil, nil) }.to raise_error(Citizenship::Error)
13
+ end
14
+
15
+ it 'covers control digit collision flaw' do
16
+ #see http://dokatano.blogspot.pt/2008/10/o-mistrio-do-bilhete-de-identidade.html
17
+ expect(Citizenship.valid_identification_card?('6617080', 0)).to be_truthy
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ describe 'NIB validation' do
2
+ it 'passes for valid NIBs', focus: true do
3
+ nib = '003503730000539151280'
4
+
5
+ expect(Citizenship.valid_nib!(nib)).to eq(nib)
6
+ expect(Citizenship.valid_nib?(nib)).to be_truthy
7
+ expect(Citizenship.valid_nib?('0035-03730000539-1512-80')).to be_truthy
8
+ expect(Citizenship.valid_nib?('0035.03730000539.1512.80')).to be_truthy
9
+ end
10
+
11
+ it 'fails for invalid NIBs' do
12
+ nib = '003503730000539151200'
13
+
14
+ expect{ Citizenship.valid_nib!(nib) }.to raise_error(Citizenship::Error)
15
+ expect{ Citizenship.valid_nib!('000003730000539151252') }.to raise_error(Citizenship::Error) #invalid bank code
16
+ expect(Citizenship.valid_nib?(nib)).to be_falsy
17
+ expect(Citizenship.valid_nib?('')).to be_falsy
18
+ end
19
+
20
+ it 'strict validation disallow whitspaces, dashes and dots' do
21
+ expect(Citizenship.valid_nib?('0035-03730000539-1512-80', strict: true)).to be_falsy
22
+ expect(Citizenship.valid_nib?('0035.03730000539.1512.80', strict: true)).to be_falsy
23
+ expect(Citizenship.valid_nib?('0035 03730000539 1512 80', strict: true)).to be_falsy
24
+ expect(Citizenship.valid_nib?('0035 03730000539 1512 80 ', strict: true)).to be_falsy
25
+ expect(Citizenship.valid_nib?('003503730000539151280', strict: true)).to be_truthy
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ describe 'NIF validation' do
2
+ it 'passes for valid NIFs' do
3
+ expect(Citizenship.valid_nif?('123456789')).to be_truthy
4
+ expect(Citizenship.valid_nif!('123456789')).to eq('123456789')
5
+ expect(Citizenship.valid_nif!('123456789')).to eq('123456789')
6
+ end
7
+
8
+ it 'fails on invalid NIFs' do
9
+ expect(Citizenship.valid_nif?('223456789')).to be_falsy
10
+ expect(Citizenship.valid_nif?('12345678')).to be_falsy
11
+ expect { Citizenship.valid_nif!('223456789') }.to raise_error(Citizenship::Error)
12
+ expect(Citizenship.valid_nif?(nil)).to be_falsy
13
+ end
14
+
15
+ it 'strict validation disallow whitspaces and others' do
16
+ expect(Citizenship.valid_nif?(' 123456789', strict: true)).to be_falsy
17
+ expect(Citizenship.valid_nif?('123456789 ', strict: true)).to be_falsy
18
+ expect(Citizenship.valid_nif?('12345 6789', strict: true)).to be_falsy
19
+ expect(Citizenship.valid_nif?('123456789', strict: true)).to be_truthy
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ describe 'phone number validation' do
2
+ context 'passes for valid phone numbers' do
3
+ it 'land lines' do
4
+ expect(Citizenship.valid_phone!('262-999-666')).to eq('262-999-666')
5
+ end
6
+
7
+ it 'mobile' do
8
+ expect(Citizenship.valid_phone!('939332233')).to eq('939332233')
9
+ expect(Citizenship.valid_phone!('969332233')).to eq('969332233')
10
+ expect(Citizenship.valid_phone!('929332233')).to eq('929332233')
11
+ expect(Citizenship.valid_phone?('919332233')).to be_truthy
12
+ end
13
+
14
+ it 'with country prefix' do
15
+ expect(Citizenship.valid_phone!('+351 93 933 2233')).to eq('+351 93 933 2233')
16
+
17
+ end
18
+
19
+ it 'without countryprefix' do
20
+ expect(Citizenship.valid_phone?('+351 93 933 2233', allow_country_prefix: false)).to be_falsy
21
+ end
22
+
23
+ it 'by designated prefix', focus: true do
24
+ expect(Citizenship.valid_phone!('+351 93 933 2233', only_prefixes: ['93', '96'])).to eq('+351 93 933 2233')
25
+ expect(Citizenship.valid_phone!('+351 96 933 2233', only_prefixes: ['93', '96'])).to eq('+351 96 933 2233')
26
+ end
27
+
28
+ it 'strict validation disallow whitspaces, dashes and dots' do
29
+ expect(Citizenship.valid_phone?('262999666', strict: true)).to be_truthy
30
+ end
31
+ end
32
+
33
+ context 'fails for invalid phone numbers' do
34
+ it 'land lines' do
35
+ expect { Citizenship.valid_phone!('662-999-666') }.to raise_error(Citizenship::Error)
36
+ end
37
+
38
+ it 'mobile' do
39
+ expect(Citizenship.valid_phone?('999332233')).to be_falsy
40
+ end
41
+
42
+ it 'with country prefix' do
43
+ expect(Citizenship.valid_phone?('+350 93 933 2233')).to be_falsy
44
+ end
45
+
46
+ it 'by designated prefix' do
47
+ expect(Citizenship.valid_phone?('96 933 2233', only_prefixes: '93')).to be_falsy
48
+ expect(Citizenship.valid_phone?('96 933 2233', only_prefixes: ['91', '93'])).to be_falsy
49
+ end
50
+
51
+
52
+ it 'strict validation disallow whitspaces, dashes and dots' do
53
+ expect(Citizenship.valid_phone?('262-999-666', strict: true)).to be_falsy
54
+ expect(Citizenship.valid_phone?('262 999 666', strict: true)).to be_falsy
55
+ expect(Citizenship.valid_phone?(' 262999666', strict: true)).to be_falsy
56
+ expect(Citizenship.valid_phone?(' 262999666 ', strict: true)).to be_falsy
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ describe 'Zip code validation' do
2
+ it 'passes for valid Zip codes' do
3
+ expect(Citizenship.valid_zip_code?('1000-100')).to be_truthy
4
+ end
5
+
6
+ it 'fails for invalid Zip codes' do
7
+ expect {Citizenship.valid_zip_code!('1000')}.to raise_error(Citizenship::ZipCodeError)
8
+ expect(Citizenship.valid_zip_code?('1000-00')).to be_falsy
9
+ expect(Citizenship.valid_zip_code?(' 1000-000')).to be_falsy
10
+ expect(Citizenship.valid_zip_code?('1000-100 ')).to be_falsy
11
+ expect(Citizenship.valid_zip_code?('1000 - 100')).to be_falsy
12
+ end
13
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'simplecov'
2
+ require 'pry'
3
+
4
+ #setup simplecov
5
+ SimpleCov.start do
6
+ add_filter "/spec"
7
+ end
8
+
9
+ require File.expand_path('../../lib/citizenship', __FILE__)
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: citizenship
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Runtime Revolution
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: i18n
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.6.4
20
+ - - "<="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.7.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.6.4
30
+ - - "<="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.7.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.3'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.3'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 3.2.0
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 3.2.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: simplecov
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: pry
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ description: citizenship checks validity of civil id numbers and similar used in Portugal
104
+ email: info@runtime-revolution.com
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files: []
108
+ files:
109
+ - ".gitignore"
110
+ - ".travis.yml"
111
+ - CHANGELOG
112
+ - Gemfile
113
+ - LICENSE.txt
114
+ - README.md
115
+ - Rakefile
116
+ - citizenship.gemspec
117
+ - lib/citizenship.rb
118
+ - lib/citizenship/citizen_card.rb
119
+ - lib/citizenship/email.rb
120
+ - lib/citizenship/errors.rb
121
+ - lib/citizenship/identification_card.rb
122
+ - lib/citizenship/nib.rb
123
+ - lib/citizenship/nif.rb
124
+ - lib/citizenship/phone.rb
125
+ - lib/citizenship/rails/validators.rb
126
+ - lib/citizenship/version.rb
127
+ - lib/citizenship/zip_code.rb
128
+ - lib/locales/en.yml
129
+ - lib/locales/pt-PT.yml
130
+ - lib/locales/pt.yml
131
+ - spec/citizenship/citizen_card_spec.rb
132
+ - spec/citizenship/email_spec.rb
133
+ - spec/citizenship/identification_card_spec.rb
134
+ - spec/citizenship/nib_spec.rb
135
+ - spec/citizenship/nif_spec.rb
136
+ - spec/citizenship/phone_spec.rb
137
+ - spec/citizenship/zip_code_spec.rb
138
+ - spec/helper.rb
139
+ homepage: https://github.com/runtimerevolution/citizenship
140
+ licenses:
141
+ - MIT
142
+ metadata: {}
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 2.4.5
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: citizenship checks validity of civil id numbers and similar used in Portugal
163
+ test_files:
164
+ - spec/citizenship/citizen_card_spec.rb
165
+ - spec/citizenship/email_spec.rb
166
+ - spec/citizenship/identification_card_spec.rb
167
+ - spec/citizenship/nib_spec.rb
168
+ - spec/citizenship/nif_spec.rb
169
+ - spec/citizenship/phone_spec.rb
170
+ - spec/citizenship/zip_code_spec.rb
171
+ - spec/helper.rb