Skip to content

Commit

Permalink
Validators: Handle custom attribute names
Browse files Browse the repository at this point in the history
  • Loading branch information
ledermann committed Jan 30, 2014
1 parent a157525 commit 90cc4a6
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 55 deletions.
27 changes: 15 additions & 12 deletions README.md
Expand Up @@ -198,30 +198,33 @@ xml_string = sct.to_xml('pain.001.002.03') # Use former schema pain.001.002.03

You can rely on our internal validations, raising errors when needed, during
message creation.
To validate models holding SEPA related information e.g.: users bank
information, invoice mandate id; you can use our validator classes or rely on
our validation regex and constants (in case your fields are named different).
To validate your models holding SEPA related information (e.g. BIC, IBAN,
mandate_id) you can use our validator classes or rely on some constants.

Examples:

```ruby
class User < ActiveRecord::Base
# implies user.iban (btw. we are using IbanTools to do the heavy lifting, no regex available)
validates_with SEPA::IBANValidator
class BankAccount < ActiveRecord::Base
# IBAN validation, by default it validates the attribute named "iban"
validates_with SEPA::IBANValidator, field_name: :iban_the_terrible

# implies user.bic
validates_with SEPA::BICValidator
# custom named user.bank_bic
validates_format_of :bank_bic, with: SEPA::BICValidator::REGEX
# BIC validation, by default it validates the attribute named "bic"
validates_with SEPA::BICValidator, field_name: :bank_bic
end

# direct debit transaction validation constants
class Payment < ActiveRecord::Base
validates_inclusion_of :sepa_sequence_type, in: SEPA::DirectDebitTransaction::SEQUENCE_TYPES
validates_format_of :mandate_id, with: SEPA::DirectDebitTransaction::MANDATE_ID_REGEX

# Mandate ID validation, by default it validates the attribute named "mandate_id"
validates_with SEPA::MandateIdentifierValidator, field_name: :mandate_id
end
```

Also see:
* [lib/sepa_king/validator.rb](https://github.com/salesking/sepa_king/blob/master/lib/sepa_king/validator.rb)
* [lib/sepa_king/transaction/direct_debit_transaction.rb](https://github.com/salesking/sepa_king/blob/master/lib/sepa_king/transaction/direct_debit_transaction.rb)


## Changelog

https://github.com/salesking/sepa_king/releases
Expand Down
3 changes: 1 addition & 2 deletions lib/sepa_king/transaction/direct_debit_transaction.rb
Expand Up @@ -3,11 +3,10 @@ module SEPA
class DirectDebitTransaction < Transaction
SEQUENCE_TYPES = %w(FRST OOFF RCUR FNAL)
LOCAL_INSTRUMENTS = %w(CORE COR1 B2B)
MANDATE_ID_REGEX = /\A([A-Za-z0-9]|[\+|\?|\/|\-|\:|\(|\)|\.|\,|\']){1,35}\z/

attr_accessor :mandate_id, :mandate_date_of_signature, :local_instrument, :sequence_type, :creditor_account

validates_format_of :mandate_id, with: MANDATE_ID_REGEX
validates_with MandateIdentifierValidator, field_name: :mandate_id
validates_presence_of :mandate_date_of_signature
validates_inclusion_of :local_instrument, in: LOCAL_INSTRUMENTS
validates_inclusion_of :sequence_type, in: SEQUENCE_TYPES
Expand Down
38 changes: 31 additions & 7 deletions lib/sepa_king/validator.rb
Expand Up @@ -2,28 +2,39 @@
module SEPA
class IBANValidator < ActiveModel::Validator
def validate(record)
unless IBANTools::IBAN.valid?(record.iban.to_s)
record.errors.add(:iban, :invalid)
field_name = options[:field_name] || :iban
value = record.send(field_name)

unless IBANTools::IBAN.valid?(value.to_s)
record.errors.add(field_name, :invalid)
end
end
end

class BICValidator < ActiveModel::Validator
REGEX = /\A[A-Z]{6,6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3,3}){0,1}\z/

def validate(record)
if record.bic
unless record.bic.to_s.match(REGEX)
record.errors.add(:bic, :invalid)
field_name = options[:field_name] || :bic
value = record.send(field_name)

if value
unless value.to_s.match(REGEX)
record.errors.add(field_name, :invalid)
end
end
end
end

class CreditorIdentifierValidator < ActiveModel::Validator
REGEX = /\A[a-zA-Z]{2,2}[0-9]{2,2}([A-Za-z0-9]|[\+|\?|\/|\-|\:|\(|\)|\.|,|']){3,3}([A-Za-z0-9]|[\+|\?|\/|\-|:|\(|\)|\.|,|']){1,28}\z/

def validate(record)
unless valid?(record.creditor_identifier)
record.errors.add(:creditor_identifier, :invalid)
field_name = options[:field_name] || :creditor_identifier
value = record.send(field_name)

unless valid?(value)
record.errors.add(field_name, :invalid)
end
end

Expand All @@ -37,4 +48,17 @@ def valid?(creditor_identifier)
ok
end
end

class MandateIdentifierValidator < ActiveModel::Validator
REGEX = /\A([A-Za-z0-9]|[\+|\?|\/|\-|\:|\(|\)|\.|\,|\']){1,35}\z/

def validate(record)
field_name = options[:field_name] || :mandate_id
value = record.send(field_name)

unless value.to_s.match(REGEX)
record.errors.add(field_name, :invalid)
end
end
end
end
10 changes: 0 additions & 10 deletions spec/direct_debit_transaction_spec.rb
Expand Up @@ -46,16 +46,6 @@
end
end

context 'Mandate ID' do
it 'should allow valid value' do
SEPA::DirectDebitTransaction.should accept('XYZ-123', "+?/-:().,'", 'X' * 35, for: :mandate_id)
end

it 'should not allow invalid value' do
SEPA::DirectDebitTransaction.should_not accept(nil, '', 'X' * 36, 'ABC 123', '#/*', 'Ümläüt', for: :mandate_id)
end
end

context 'Requested date' do
it 'should allow valid value' do
SEPA::DirectDebitTransaction.should accept(nil, Date.today.next, Date.today + 2, for: :requested_date)
Expand Down
32 changes: 18 additions & 14 deletions spec/support/custom_matcher.rb
Expand Up @@ -36,21 +36,25 @@
end

RSpec::Matchers.define :accept do |*values, options|
attribute = options[:for]

match_for_should do |actual|
values.all? { |value|
expect(
actual.new(attribute => value)
).to have(:no).errors_on(attribute)
}
attributes = Array(options[:for])

attributes.each do |attribute|
match_for_should do |actual|
values.all? { |value|
expect(
actual.new(attribute => value)
).to have(:no).errors_on(attribute)
}
end
end

match_for_should_not do |actual|
values.all? { |value|
expect(
actual.new(attribute => value)
).to have_at_least(1).errors_on(attribute)
}
attributes.each do |attribute|
match_for_should_not do |actual|
values.all? { |value|
expect(
actual.new(attribute => value)
).to have_at_least(1).errors_on(attribute)
}
end
end
end
38 changes: 28 additions & 10 deletions spec/validator_spec.rb
Expand Up @@ -4,16 +4,17 @@
describe SEPA::IBANValidator do
class Validatable
include ActiveModel::Model
attr_accessor :iban
attr_accessor :iban, :iban_the_terrible
validates_with SEPA::IBANValidator
validates_with SEPA::IBANValidator, field_name: :iban_the_terrible
end

it 'should accept valid IBAN' do
Validatable.should accept('DE21500500009876543210', 'DE87200500001234567890', for: :iban)
Validatable.should accept('DE21500500009876543210', 'DE87200500001234567890', for: [:iban, :iban_the_terrible])
end

it 'should not accept an invalid IBAN' do
Validatable.should_not accept('', 'xxx', 'DE22500500009876543210', 'DE2150050000987654321', for: :iban)
Validatable.should_not accept('', 'xxx', 'DE22500500009876543210', 'DE2150050000987654321', for: [:iban, :iban_the_terrible])
end
end

Expand All @@ -22,31 +23,48 @@ class Validatable
include ActiveModel::Model
attr_accessor :bic, :custom_bic
validates_with SEPA::BICValidator
validates_format_of :custom_bic, with: SEPA::BICValidator::REGEX
validates_with SEPA::BICValidator, field_name: :custom_bic
end

it 'should accept valid BICs' do
Validatable.should accept('DEUTDEDBDUE', 'DUSSDEDDXXX', for: :bic)
Validatable.should accept('DEUTDEDBDUE', 'DUSSDEDDXXX', for: :custom_bic)
Validatable.should accept('DEUTDEDBDUE', 'DUSSDEDDXXX', for: [:bic, :custom_bic])
end

it 'should not accept an invalid BIC' do
Validatable.should_not accept('', 'GENODE61HR', 'DEUTDEDBDUEDEUTDEDBDUE', for: :bic)
Validatable.should_not accept('', 'GENODE61HR', 'DEUTDEDBDUEDEUTDEDBDUE', for: [:bic, :custom_bic])
end
end

describe SEPA::CreditorIdentifierValidator do
class Validatable
include ActiveModel::Model
attr_accessor :creditor_identifier
attr_accessor :creditor_identifier, :crid
validates_with SEPA::CreditorIdentifierValidator
validates_with SEPA::CreditorIdentifierValidator, field_name: :crid
end

it 'should accept valid creditor_identifier' do
Validatable.should accept('DE98ZZZ09999999999', 'AT12ZZZ00000000001', 'FR12ZZZ123456', 'NL97ZZZ123456780001', for: :creditor_identifier)
Validatable.should accept('DE98ZZZ09999999999', 'AT12ZZZ00000000001', 'FR12ZZZ123456', 'NL97ZZZ123456780001', for: [:creditor_identifier, :crid])
end

it 'should not accept an invalid creditor_identifier' do
Validatable.should_not accept('', 'xxx', 'DE98ZZZ099999999990', for: :creditor_identifier)
Validatable.should_not accept('', 'xxx', 'DE98ZZZ099999999990', for: [:creditor_identifier, :crid])
end
end

describe SEPA::MandateIdentifierValidator do
class Validatable
include ActiveModel::Model
attr_accessor :mandate_id, :mid
validates_with SEPA::MandateIdentifierValidator
validates_with SEPA::MandateIdentifierValidator, field_name: :mid
end

it 'should accept valid mandate_identifier' do
Validatable.should accept('XYZ-123', "+?/-:().,'", 'X' * 35, for: [:mandate_id, :mid])
end

it 'should not accept an invalid mandate_identifier' do
Validatable.should_not accept(nil, '', 'X' * 36, 'ABC 123', '#/*', 'Ümläüt', for: [:mandate_id, :mid])
end
end

1 comment on commit 90cc4a6

@schorsch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

Please sign in to comment.