codice-fiscale 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/codice_fiscale.gemspec +3 -0
- data/lib/codice_fiscale.rb +12 -99
- data/lib/codice_fiscale/codes.rb +7 -8
- data/lib/codice_fiscale/configuration.rb +12 -1
- data/lib/codice_fiscale/fiscal_code.rb +58 -0
- data/lib/codice_fiscale/helpers.rb +33 -0
- data/lib/codice_fiscale/italian_citizen.rb +48 -0
- data/lib/codice_fiscale/version.rb +1 -1
- data/spec/lib/codice_fiscale/fiscal_code_spec.rb +147 -0
- data/spec/lib/codice_fiscale/helpers_spec.rb +4 -0
- data/spec/lib/codice_fiscale/italian_citizen_spec.rb +65 -0
- data/spec/lib/codice_fiscale_spec.rb +10 -200
- metadata +43 -2
data/codice_fiscale.gemspec
CHANGED
@@ -21,4 +21,7 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.add_development_dependency 'rb-fsevent', '~> 0.9.1'
|
22
22
|
# The growl app must be separately downloaded and installed
|
23
23
|
gem.add_development_dependency 'growl'
|
24
|
+
|
25
|
+
gem.add_dependency 'active_support'
|
26
|
+
gem.add_dependency 'activemodel'
|
24
27
|
end
|
data/lib/codice_fiscale.rb
CHANGED
@@ -1,108 +1,21 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'date'
|
3
3
|
require 'csv'
|
4
|
+
require 'active_model'
|
5
|
+
require 'active_support/core_ext/object/blank'
|
6
|
+
require 'active_support/core_ext/module/delegation'
|
4
7
|
require 'codice_fiscale/version'
|
5
|
-
require 'codice_fiscale/alphabet'
|
6
|
-
require 'codice_fiscale/codes'
|
7
8
|
require 'codice_fiscale/configuration'
|
9
|
+
require 'codice_fiscale/helpers'
|
10
|
+
require 'codice_fiscale/codes'
|
11
|
+
require 'codice_fiscale/alphabet'
|
12
|
+
require 'codice_fiscale/italian_citizen'
|
13
|
+
require 'codice_fiscale/fiscal_code'
|
8
14
|
|
9
15
|
module CodiceFiscale
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
code = birthplace_part (params[:country_name] || Codes::ITALY), params[:city_name], params[:province_code]
|
15
|
-
raise "Missing city/country code. Mispelled?" unless code
|
16
|
-
code = surname_part(params[:surname]) + name_part(params[:name]) + birthdate_part(params[:birthdate], params[:gender]) + code
|
17
|
-
code + check_character(code)
|
18
|
-
end
|
19
|
-
|
20
|
-
def validate_calculate_params params
|
21
|
-
[:name, :surname, :gender, :birthdate].each do |param_name|
|
22
|
-
raise "Missing #{param_name} parameter" unless params[param_name]
|
23
|
-
end
|
24
|
-
raise "Invalid birthdate: #{params[:birthdate]}" unless params[:birthdate].respond_to? :year
|
25
|
-
unless Codes::GENDERS.include? params[:gender]
|
26
|
-
raise "Invalid gender. Possible values are #{Codes::GENDERS}"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
# Methods to generate each part of the code
|
32
|
-
|
33
|
-
def surname_part surname
|
34
|
-
surname.upcase!
|
35
|
-
code = first_three_consonants surname
|
36
|
-
code << first_three_vowels(surname)
|
37
|
-
truncate_and_right_pad_with_three_x code
|
38
|
-
end
|
39
|
-
|
40
|
-
def name_part name
|
41
|
-
name.upcase!
|
42
|
-
consonants_of_name = consonants name
|
43
|
-
return consonants_of_name[0]+consonants_of_name[2..3] if consonants_of_name.size >= 4
|
44
|
-
code = first_three_consonants name
|
45
|
-
code << first_three_vowels(name)
|
46
|
-
truncate_and_right_pad_with_three_x code
|
47
|
-
end
|
48
|
-
|
49
|
-
def birthdate_part birthdate, gender
|
50
|
-
code = birthdate.year.to_s[2..3]
|
51
|
-
code << Codes.month_letter(birthdate.month)
|
52
|
-
day_part = gender.to_s.downcase[0] == 'f' ? birthdate.day + 40 : birthdate.day
|
53
|
-
code << "#{day_part}".rjust(2, '0')
|
54
|
-
end
|
55
|
-
|
56
|
-
def city_code city_name, province_code
|
57
|
-
return config.city_code.call(city_name, province_code) if config.city_code
|
58
|
-
Codes.city city_name, province_code
|
59
|
-
end
|
60
|
-
|
61
|
-
def country_code country_name
|
62
|
-
return config.country_code.call(country_name) if config.country_code
|
63
|
-
Codes.country country_name
|
64
|
-
end
|
65
|
-
|
66
|
-
def birthplace_part country_name, city_name = nil, province_code = nil
|
67
|
-
if Codes.italy? country_name
|
68
|
-
city_code city_name, province_code
|
69
|
-
else
|
70
|
-
country_code country_name
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def check_character partial_fiscal_code
|
75
|
-
numeric_value = 0
|
76
|
-
partial_fiscal_code.split('').each_with_index do |chr, index|
|
77
|
-
numeric_value += ((index+1) % 2 == 0) ? Codes.even_character(chr) : Codes.odd_character(chr)
|
78
|
-
end
|
79
|
-
Codes.check_character numeric_value % 26
|
80
|
-
end
|
81
|
-
|
82
|
-
|
83
|
-
# Helper methods
|
84
|
-
|
85
|
-
# Intersect two strings or a string and an array of characters.
|
86
|
-
def intersects string_a, string_or_array_b
|
87
|
-
letters_a = string_a.split ''
|
88
|
-
letters_b = string_or_array_b.respond_to?(:split) ? string_or_array_b.split('') : string_or_array_b
|
89
|
-
selected_letters = letters_a.select { |letter| letters_b.include? letter }
|
90
|
-
selected_letters.join ''
|
91
|
-
end
|
92
|
-
|
93
|
-
def consonants string
|
94
|
-
intersects string, Alphabet.consonants
|
95
|
-
end
|
96
|
-
|
97
|
-
def first_three_consonants string
|
98
|
-
intersects(string, Alphabet.consonants)[0..2]
|
99
|
-
end
|
100
|
-
|
101
|
-
def first_three_vowels string
|
102
|
-
intersects(string, Alphabet.vowels)[0..2]
|
103
|
-
end
|
104
|
-
|
105
|
-
def truncate_and_right_pad_with_three_x string
|
106
|
-
string[0..2].ljust 3, 'X'
|
16
|
+
def self.calculate params
|
17
|
+
citizen = ItalianCitizen.new params
|
18
|
+
raise ArgumentError.new("#{citizen.errors}") unless citizen.valid?
|
19
|
+
citizen.fiscal_code
|
107
20
|
end
|
108
21
|
end
|
data/lib/codice_fiscale/codes.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module CodiceFiscale
|
2
2
|
module Codes
|
3
|
+
include Configurable
|
3
4
|
extend self
|
4
5
|
|
5
6
|
ITALY = 'Italia'
|
@@ -16,17 +17,14 @@ module CodiceFiscale
|
|
16
17
|
|
17
18
|
GENDERS = [:male, :female]
|
18
19
|
|
19
|
-
def config
|
20
|
-
CodiceFiscale.config
|
21
|
-
end
|
22
|
-
|
23
20
|
def month_letter month_number
|
24
21
|
month_number <= 0 ? nil : MONTH_CODES[month_number-1]
|
25
22
|
end
|
26
23
|
|
27
24
|
def city city_name, province_code
|
25
|
+
return config.city_code.call(city_name, province_code) if config.city_code
|
28
26
|
CSV.foreach config.city_codes_csv_path do |row|
|
29
|
-
if city_name.casecmp(row[3].strip)
|
27
|
+
if city_name.casecmp(row[3].strip).zero? and province_code.casecmp(row[2].strip).zero?
|
30
28
|
return row[0].strip.upcase
|
31
29
|
end
|
32
30
|
end
|
@@ -34,8 +32,9 @@ module CodiceFiscale
|
|
34
32
|
end
|
35
33
|
|
36
34
|
def country country_name
|
35
|
+
return config.country_code.call(country_name) if config.country_code
|
37
36
|
CSV.foreach config.country_codes_csv_path do |row|
|
38
|
-
return row[3].strip.upcase if country_name.casecmp(row[2].strip)
|
37
|
+
return row[3].strip.upcase if country_name.casecmp(row[2].strip).zero?
|
39
38
|
end
|
40
39
|
nil
|
41
40
|
end
|
@@ -48,12 +47,12 @@ module CodiceFiscale
|
|
48
47
|
EVEN_CODES[character.upcase]
|
49
48
|
end
|
50
49
|
|
51
|
-
def
|
50
|
+
def control_character number
|
52
51
|
Alphabet.letters[number]
|
53
52
|
end
|
54
53
|
|
55
54
|
def italy? country_name
|
56
|
-
ITALY.casecmp(country_name.strip)
|
55
|
+
ITALY.casecmp(country_name.strip).zero?
|
57
56
|
end
|
58
57
|
end
|
59
58
|
end
|
@@ -30,7 +30,18 @@ module CodiceFiscale
|
|
30
30
|
end
|
31
31
|
|
32
32
|
|
33
|
-
|
33
|
+
module Configurable
|
34
|
+
def self.config
|
35
|
+
CodiceFiscale.config
|
36
|
+
end
|
37
|
+
|
38
|
+
def config
|
39
|
+
Configurable.config
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def self.config
|
34
45
|
@config ||= Configuration.new
|
35
46
|
end
|
36
47
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module CodiceFiscale
|
2
|
+
class FiscalCode
|
3
|
+
include Helpers
|
4
|
+
include Configurable
|
5
|
+
|
6
|
+
attr_accessor :italian_citizen
|
7
|
+
alias :citizen :italian_citizen
|
8
|
+
|
9
|
+
delegate :name, :surname, :birthdate, :city_name, :province_code, :country_name, :to => :citizen
|
10
|
+
|
11
|
+
def initialize italian_citizen
|
12
|
+
@italian_citizen = italian_citizen
|
13
|
+
end
|
14
|
+
|
15
|
+
def calculate
|
16
|
+
code = surname_part + name_part + birthdate_part + birthplace_part
|
17
|
+
code + control_character(code)
|
18
|
+
end
|
19
|
+
|
20
|
+
def surname_part
|
21
|
+
first_three_consonants_than_vowels surname
|
22
|
+
end
|
23
|
+
|
24
|
+
def name_part
|
25
|
+
return "#{consonants_of_name[0]}#{consonants_of_name[2..3]}" if consonants_of_name.size >= 4
|
26
|
+
first_three_consonants_than_vowels name
|
27
|
+
end
|
28
|
+
|
29
|
+
def birthdate_part
|
30
|
+
code = birthdate.year.to_s[2..3]
|
31
|
+
code << Codes.month_letter(birthdate.month)
|
32
|
+
code << day_part
|
33
|
+
end
|
34
|
+
|
35
|
+
def day_part
|
36
|
+
number = citizen.female? ? birthdate.day + 40 : birthdate.day
|
37
|
+
"#{number}".rjust(2, '0')
|
38
|
+
end
|
39
|
+
|
40
|
+
def birthplace_part
|
41
|
+
code = citizen.born_in_italy? && Codes.city(city_name, province_code) || Codes.country(country_name)
|
42
|
+
raise "Cannot find a valid code for #{[country_name, city_name, province_code].compact.join ', '}" unless code
|
43
|
+
code
|
44
|
+
end
|
45
|
+
|
46
|
+
def control_character partial_fiscal_code
|
47
|
+
numeric_value = 0
|
48
|
+
partial_fiscal_code.split('').each_with_index do |chr, index|
|
49
|
+
numeric_value += (index+1).even? ? Codes.even_character(chr) : Codes.odd_character(chr)
|
50
|
+
end
|
51
|
+
Codes.control_character numeric_value % 26
|
52
|
+
end
|
53
|
+
|
54
|
+
def consonants_of_name
|
55
|
+
consonants name.upcase
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module CodiceFiscale
|
2
|
+
module Helpers
|
3
|
+
def intersection string_a, string_or_array_b
|
4
|
+
letters_a = string_a.split ''
|
5
|
+
letters_b = string_or_array_b.respond_to?(:split) ? string_or_array_b.split('') : string_or_array_b
|
6
|
+
selected_letters = letters_a.select { |letter| letters_b.include? letter }
|
7
|
+
selected_letters.join ''
|
8
|
+
end
|
9
|
+
|
10
|
+
def consonants string
|
11
|
+
intersection string, Alphabet.consonants
|
12
|
+
end
|
13
|
+
|
14
|
+
def first_three_consonants string
|
15
|
+
intersection(string, Alphabet.consonants)[0..2]
|
16
|
+
end
|
17
|
+
|
18
|
+
def first_three_vowels string
|
19
|
+
intersection(string, Alphabet.vowels)[0..2]
|
20
|
+
end
|
21
|
+
|
22
|
+
def truncate_and_right_pad_with_three_x string
|
23
|
+
string[0..2].ljust 3, 'X'
|
24
|
+
end
|
25
|
+
|
26
|
+
def first_three_consonants_than_vowels string
|
27
|
+
string.upcase!
|
28
|
+
code = first_three_consonants string
|
29
|
+
code << first_three_vowels(string)
|
30
|
+
truncate_and_right_pad_with_three_x code
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module CodiceFiscale
|
2
|
+
class ItalianCitizen
|
3
|
+
include ActiveModel::Validations
|
4
|
+
|
5
|
+
MANDATORY_ATTRIBUTES = [:name, :surname, :birthdate, :gender]
|
6
|
+
|
7
|
+
attr_accessor :city_name, :country_name, :province_code, *MANDATORY_ATTRIBUTES
|
8
|
+
|
9
|
+
# Validations
|
10
|
+
|
11
|
+
validates_presence_of *MANDATORY_ATTRIBUTES
|
12
|
+
validates_inclusion_of :gender, :in => [:male, :female]
|
13
|
+
validates_length_of :province_code, :is => 2, :allow_blank => true
|
14
|
+
validates_presence_of :city_name, :province_code, :if => lambda{ |obj| obj.born_in_italy? }
|
15
|
+
validate do
|
16
|
+
errors.add(:birthdate, :invalid) unless birthdate.respond_to? :year
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Instance methods
|
21
|
+
|
22
|
+
def initialize attributes
|
23
|
+
@attributes = default_attributes.merge attributes
|
24
|
+
@attributes.each { |name, value| send("#{name}=", value) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_attributes
|
28
|
+
{:country_name => Codes::ITALY}
|
29
|
+
end
|
30
|
+
|
31
|
+
def born_in_italy?
|
32
|
+
Codes.italy? country_name
|
33
|
+
end
|
34
|
+
|
35
|
+
def female?
|
36
|
+
gender == :female
|
37
|
+
end
|
38
|
+
|
39
|
+
def fiscal_code
|
40
|
+
FiscalCode.new(self).calculate
|
41
|
+
end
|
42
|
+
|
43
|
+
# This method exists to support ActiveModel::Validations integration
|
44
|
+
def read_attribute_for_validation key
|
45
|
+
@attributes[key]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CodiceFiscale::FiscalCode do
|
4
|
+
let(:citizen_marco_galli) { CodiceFiscale::ItalianCitizen.new :name => 'Marco', :surname => 'Galli', :gender => :male, :birthdate => Date.new(1983, 5, 3), :city_name => 'Oggiono', :province_code => 'LC' }
|
5
|
+
let(:fiscal_code) { described_class.new citizen_marco_galli }
|
6
|
+
|
7
|
+
describe '#surname_part' do
|
8
|
+
it 'takes the first 3 consonants' do
|
9
|
+
fiscal_code.surname_part.should == 'GLL'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'is 3 chrs long' do
|
13
|
+
fiscal_code.surname_part.size.should == 3
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when surname has only 1 consonant' do
|
17
|
+
before { fiscal_code.citizen.surname = 'oof' }
|
18
|
+
|
19
|
+
it 'puts the vowels after the consonants' do
|
20
|
+
fiscal_code.surname_part.should == 'FOO'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when surname is less than 3 chrs long' do
|
25
|
+
before { fiscal_code.citizen.surname = 'm' }
|
26
|
+
|
27
|
+
it 'pads with the "X" character' do
|
28
|
+
fiscal_code.surname_part.should == 'MXX'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#name_part' do
|
34
|
+
it 'is 3 chrs long' do
|
35
|
+
fiscal_code.name_part.size.should == 3
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when name has 4 or more consonants' do
|
39
|
+
before { fiscal_code.citizen.name = 'danielino' }
|
40
|
+
|
41
|
+
it 'takes the 1st the 3rd and the 4th' do
|
42
|
+
fiscal_code.name_part.should == 'DLN'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when name has 3 or less consonants" do
|
47
|
+
before { fiscal_code.citizen.name = 'daniele' }
|
48
|
+
|
49
|
+
it 'takes the first 3 consonants' do
|
50
|
+
fiscal_code.name_part.should == 'DNL'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when name has 2 consonants' do
|
55
|
+
before { fiscal_code.citizen.name = 'bar' }
|
56
|
+
|
57
|
+
it 'puts the vowels after the consonants' do
|
58
|
+
fiscal_code.name_part.should == 'BRA'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'name is less than 3 chrs long' do
|
63
|
+
before { fiscal_code.citizen.name = 'd' }
|
64
|
+
|
65
|
+
it 'pad with the "X" character' do
|
66
|
+
fiscal_code.name_part.should == 'DXX'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
describe '#birthdate_part' do
|
73
|
+
it 'start with the last 2 digit of the year' do
|
74
|
+
fiscal_code.birthdate_part.should start_with '83'
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'the 3rd character' do
|
78
|
+
before { CodiceFiscale::Codes.stub(:month_letter).and_return('X') }
|
79
|
+
|
80
|
+
it 'is the month code' do
|
81
|
+
fiscal_code.birthdate_part[2].should == 'X'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'the last 2 character' do
|
86
|
+
context 'gender is male' do
|
87
|
+
before { fiscal_code.citizen.gender = :male }
|
88
|
+
|
89
|
+
it('is the birth day') { fiscal_code.birthdate_part[3..5].should == '03' }
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'gender is female' do
|
93
|
+
before { fiscal_code.citizen.gender = :female }
|
94
|
+
|
95
|
+
it('is the birth day + 40') { fiscal_code.birthdate_part[3..5].should == '43' }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#birthplace_part' do
|
101
|
+
context 'when the country is Italy' do
|
102
|
+
before { fiscal_code.citizen.country_name = 'Italia' }
|
103
|
+
|
104
|
+
context 'when codes are fetched using a proc' do
|
105
|
+
before { fiscal_code.config.city_code { 'Winterfell' } }
|
106
|
+
|
107
|
+
it 'returns the result of the city-block execution' do
|
108
|
+
fiscal_code.birthplace_part.should == 'Winterfell'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when codes are fetched using csv' do
|
113
|
+
before { CodiceFiscale::Codes.stub!(:city).and_return('hello') }
|
114
|
+
|
115
|
+
it 'returns the city code' do
|
116
|
+
fiscal_code.birthplace_part.should == 'hello'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'when the country is not Italy' do
|
122
|
+
before { fiscal_code.citizen.country_name = 'Francia' }
|
123
|
+
|
124
|
+
context 'when codes are fetched using a proc' do
|
125
|
+
before { fiscal_code.config.country_code { 'The North' } }
|
126
|
+
|
127
|
+
it 'returns the result of the country-block execution' do
|
128
|
+
fiscal_code.birthplace_part.should == 'The North'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'when codes are fetched using csv' do
|
133
|
+
before { CodiceFiscale::Codes.stub!(:country).and_return('Middle-Earth') }
|
134
|
+
|
135
|
+
it 'returns the country code' do
|
136
|
+
fiscal_code.birthplace_part.should == 'Middle-Earth'
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe '#control_character' do
|
143
|
+
it 'returns the expected letter' do
|
144
|
+
fiscal_code.control_character('RSSMRA87A01A005').should == 'V'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CodiceFiscale::ItalianCitizen do
|
4
|
+
let(:marco_attributes) { {:name => 'Marco', :surname => 'Galli', :gender => :male, :birthdate => Date.new(1983, 5, 2), :city_name => 'Oggiono', :province_code => 'LC'} }
|
5
|
+
let(:john_attributes) { {:name => 'John', :surname => 'Snow', :gender => :male, :birthdate => Date.new(1971, 9, 12), :country_name => 'Canada'} }
|
6
|
+
let(:marco) { described_class.new marco_attributes }
|
7
|
+
let(:john) { described_class.new john_attributes }
|
8
|
+
|
9
|
+
|
10
|
+
describe '#initialize' do
|
11
|
+
it 'accepts 1 attribute' do
|
12
|
+
lambda { marco }.should_not raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'do not accepts 0 attribute' do
|
16
|
+
lambda { described_class.new }.should raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
describe '#validations' do
|
22
|
+
describe 'when all attributes are valid' do
|
23
|
+
it 'is valid' do
|
24
|
+
marco.should be_valid
|
25
|
+
john.should be_valid
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'when name is not present' do
|
30
|
+
let(:citizen_without_name) { described_class.new marco_attributes.reject {|n| n == :name } }
|
31
|
+
|
32
|
+
it 'is not valid' do
|
33
|
+
citizen_without_name.valid?
|
34
|
+
citizen_without_name.errors[:name].should_not be_empty
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'when gender is not valid' do
|
39
|
+
let(:citizen_with_strange_gender) { described_class.new marco_attributes.merge(:gender => :strange) }
|
40
|
+
|
41
|
+
it 'is not valid' do
|
42
|
+
citizen_with_strange_gender.valid?
|
43
|
+
citizen_with_strange_gender.errors[:gender].should_not be_empty
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'when the province code is not 2 characters long' do
|
48
|
+
let(:citizen_with_invalid_province) { described_class.new marco_attributes.merge(:province_code => 'LECCO') }
|
49
|
+
|
50
|
+
it 'is not valid' do
|
51
|
+
citizen_with_invalid_province.valid?
|
52
|
+
citizen_with_invalid_province.errors[:province_code].should_not be_empty
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'when the bitrhdate is not valid' do
|
57
|
+
let(:citizen_with_invalid_date) { described_class.new marco_attributes.merge(:birthdate => 'xxx') }
|
58
|
+
|
59
|
+
it 'is not valid' do
|
60
|
+
citizen_with_invalid_date.valid?
|
61
|
+
citizen_with_invalid_date.errors[:birthdate].should_not be_empty
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,214 +1,24 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe CodiceFiscale do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
describe '#surname_part' do
|
10
|
-
it 'takes the first 3 consonants' do
|
11
|
-
subject.surname_part('molteni').should == 'MLT'
|
12
|
-
end
|
13
|
-
|
14
|
-
it 'is 3 chrs long' do
|
15
|
-
subject.surname_part('').size.should == 3
|
16
|
-
subject.surname_part('foobar').size.should == 3
|
17
|
-
end
|
18
|
-
|
19
|
-
context 'surname has only 1 consonant' do
|
20
|
-
it 'put the vocals after the consonants' do
|
21
|
-
subject.surname_part('oof').should == 'FOO'
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
context 'surname is less than 3 chrs long' do
|
26
|
-
it 'pad with the "X" character' do
|
27
|
-
subject.surname_part('m').should == 'MXX'
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
describe '#name_part' do
|
34
|
-
it 'is 3 chrs long' do
|
35
|
-
subject.name_part('').size.should == 3
|
36
|
-
subject.name_part('foobar').size.should == 3
|
37
|
-
end
|
38
|
-
|
39
|
-
context 'name has 4 or more consonants' do
|
40
|
-
it 'take the 1st the 3rd and the 4th' do
|
41
|
-
subject.name_part('danielino').should == 'DLN'
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
context "name has 3 or less consonants" do
|
46
|
-
it 'take the first 3 consonants' do
|
47
|
-
subject.name_part('daniele').should == 'DNL'
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
context 'name has 2 consonants' do
|
52
|
-
it 'put the vocals after the consonants' do
|
53
|
-
subject.name_part('bar').should == 'BRA'
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
context 'name is less than 3 chrs long' do
|
58
|
-
it 'pad with the "X" character' do
|
59
|
-
subject.name_part('d').should == 'DXX'
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
|
65
|
-
describe '#birthdate_part' do
|
66
|
-
let(:birthdate) { Date.new 1987, 12, 3 }
|
67
|
-
let(:male) { 'm' }
|
68
|
-
let(:female) { 'f' }
|
69
|
-
|
70
|
-
it 'start with the last 2 digit of the year' do
|
71
|
-
subject.birthdate_part(birthdate, male).should start_with '87'
|
72
|
-
end
|
73
|
-
|
74
|
-
describe 'the 3rd character' do
|
75
|
-
before { subject::Codes.stub(:month_letter).and_return('X') }
|
76
|
-
|
77
|
-
it('is the month code') { subject.birthdate_part(birthdate, male)[2].should == 'X' }
|
78
|
-
end
|
79
|
-
|
80
|
-
describe 'the last 2 character' do
|
81
|
-
context 'gender is male' do
|
82
|
-
it('is the birth day') { subject.birthdate_part(birthdate, male)[3..5].should == '03' }
|
83
|
-
end
|
84
|
-
|
85
|
-
context 'gender is female' do
|
86
|
-
it('is the birth day + 40') { subject.birthdate_part(birthdate, female)[3..5].should == '43' }
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
describe '#city_code' do
|
93
|
-
context 'the city and the provice are founded' do
|
94
|
-
it 'return the associated code' do
|
95
|
-
subject.city_code('Abbadia Lariana', 'LC').should == 'A005'
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
context 'the city and the provice are not founded' do
|
100
|
-
it 'return nil' do
|
101
|
-
subject.city_code('Winterfell', 'SO').should be_nil
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
context 'a block is configured to be called' do
|
106
|
-
before { subject.config.city_code { "foo" } }
|
107
|
-
|
108
|
-
it 'return the result of the block execution' do
|
109
|
-
subject.city_code('Lecco', 'LC').should == 'foo'
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
|
115
|
-
describe '#country_code' do
|
116
|
-
context 'the country is founded' do
|
117
|
-
it 'return the associated code' do
|
118
|
-
subject.country_code('francia').should == 'Z110'
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
context 'a block is configured to be called' do
|
123
|
-
before { subject.config.country_code { "bar" } }
|
124
|
-
|
125
|
-
it 'return the result of the block execution' do
|
126
|
-
subject.country_code('francia').should == 'bar'
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
4
|
+
describe '#calculate' do
|
5
|
+
let(:mario_attributes) { {:name => 'mario', :surname => 'rossi', :gender => :male, :birthdate => Date.new(1987, 1, 1),
|
6
|
+
:province_code => 'lc', :city_name => 'Abbadia Lariana'} }
|
131
7
|
|
132
|
-
describe '#birthplace_part' do
|
133
8
|
before do
|
134
|
-
subject.config.country_code = nil
|
135
9
|
subject.config.city_code = nil
|
10
|
+
subject.config.country_code = nil
|
136
11
|
end
|
137
12
|
|
138
|
-
|
139
|
-
|
140
|
-
subject.birthplace_part('Italia', 'Abbadia Lariana', 'LC').should == 'A005'
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
|
146
|
-
describe '#check_character' do
|
147
|
-
it 'call #Codes.check_character and return its result' do
|
148
|
-
subject::Codes.should_receive :check_character
|
149
|
-
subject.check_character 'ABC'
|
150
|
-
end
|
151
|
-
|
152
|
-
it 'call #Codes.odd_character for odd positioned chars' do
|
153
|
-
subject::Codes.should_receive(:odd_character).exactly(2).and_return 1
|
154
|
-
subject.check_character 'ABC'
|
155
|
-
end
|
156
|
-
|
157
|
-
it 'call #Codes.even_character for odd positioned chars' do
|
158
|
-
subject::Codes.should_receive(:even_character).exactly(1).and_return 1
|
159
|
-
subject.check_character 'ABC'
|
160
|
-
end
|
161
|
-
|
162
|
-
it 'works!' do
|
163
|
-
subject.check_character('RSSMRA87A01A005').should == 'V'
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
|
168
|
-
describe '#calculate' do
|
169
|
-
context 'italian citizen' do
|
170
|
-
it 'return the expected code' do
|
171
|
-
params = {:name => 'mario', :surname => 'rossi', :gender => :male, :birthdate => Date.new(1987, 1, 1),
|
172
|
-
:province_code => 'lc', :city_name => 'Abbadia Lariana'}
|
173
|
-
subject.calculate(params).should == 'RSSMRA87A01A005V'
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
context 'return the expected code' do
|
178
|
-
it 'work' do
|
179
|
-
params = {:name => 'mario', :surname => 'rossi', :gender => :male, :birthdate => Date.new(1987, 1, 1), :country_name => 'Albania'}
|
180
|
-
subject.calculate(params).should == 'RSSMRA87A01Z100H'
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
describe '#validate_calculate_params' do
|
186
|
-
let(:mandatory_params) { {:name => 'mario', :surname => 'rossi', :gender => :male, :birthdate => Date.new(1987, 1, 1)} }
|
187
|
-
|
188
|
-
context 'All mandatory params are correct' do
|
189
|
-
it 'do not raise any arror' do
|
190
|
-
lambda { subject.validate_calculate_params(mandatory_params) }.should_not raise_error
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
context 'A mandatory parameter is missing' do
|
195
|
-
it 'raise an error' do
|
196
|
-
without_name = mandatory_params.reject {|p| p == :name }
|
197
|
-
lambda { subject.validate_calculate_params(without_name) }.should raise_error
|
198
|
-
end
|
13
|
+
it 'returns the expected code' do
|
14
|
+
subject.calculate(mario_attributes).should == 'RSSMRA87A01A005V'
|
199
15
|
end
|
200
16
|
|
201
|
-
context '
|
202
|
-
|
203
|
-
params = mandatory_params.merge :gender => :lol
|
204
|
-
lambda { subject.validate_calculate_params(params) }.should raise_error
|
205
|
-
end
|
206
|
-
end
|
17
|
+
context 'when params are not valid' do
|
18
|
+
let(:invalid_attributes) { mario_attributes.merge(:name => '') }
|
207
19
|
|
208
|
-
|
209
|
-
|
210
|
-
params = mandatory_params.merge :birthdate => '2000/01/01'
|
211
|
-
lambda { subject.validate_calculate_params(params) }.should raise_error
|
20
|
+
it 'raises an error' do
|
21
|
+
lambda { subject.calculate(invalid_attributes) }.should raise_error(ArgumentError)
|
212
22
|
end
|
213
23
|
end
|
214
24
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: codice-fiscale
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -75,6 +75,38 @@ dependencies:
|
|
75
75
|
- - ! '>='
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: active_support
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: activemodel
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
78
110
|
description: Calculate the italian fiscal code
|
79
111
|
email:
|
80
112
|
- dani.m.mobile@gmail.com
|
@@ -95,11 +127,17 @@ files:
|
|
95
127
|
- lib/codice_fiscale/codes/city_codes.csv
|
96
128
|
- lib/codice_fiscale/codes/country_codes.csv
|
97
129
|
- lib/codice_fiscale/configuration.rb
|
130
|
+
- lib/codice_fiscale/fiscal_code.rb
|
131
|
+
- lib/codice_fiscale/helpers.rb
|
132
|
+
- lib/codice_fiscale/italian_citizen.rb
|
98
133
|
- lib/codice_fiscale/version.rb
|
99
134
|
- spec/fixtures/city_codes.csv
|
100
135
|
- spec/fixtures/country_codes.csv
|
101
136
|
- spec/lib/codice_fiscale/alphabet_spec.rb
|
102
137
|
- spec/lib/codice_fiscale/codes_spec.rb
|
138
|
+
- spec/lib/codice_fiscale/fiscal_code_spec.rb
|
139
|
+
- spec/lib/codice_fiscale/helpers_spec.rb
|
140
|
+
- spec/lib/codice_fiscale/italian_citizen_spec.rb
|
103
141
|
- spec/lib/codice_fiscale_spec.rb
|
104
142
|
- spec/spec_helper.rb
|
105
143
|
homepage: https://github.com/daniele-m/codice_fiscale
|
@@ -131,5 +169,8 @@ test_files:
|
|
131
169
|
- spec/fixtures/country_codes.csv
|
132
170
|
- spec/lib/codice_fiscale/alphabet_spec.rb
|
133
171
|
- spec/lib/codice_fiscale/codes_spec.rb
|
172
|
+
- spec/lib/codice_fiscale/fiscal_code_spec.rb
|
173
|
+
- spec/lib/codice_fiscale/helpers_spec.rb
|
174
|
+
- spec/lib/codice_fiscale/italian_citizen_spec.rb
|
134
175
|
- spec/lib/codice_fiscale_spec.rb
|
135
176
|
- spec/spec_helper.rb
|