codice-fiscale 0.0.5 → 0.0.6
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/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
|