From 78b49fa70fd84cec6957283e21700539b2d367f3 Mon Sep 17 00:00:00 2001 From: Jerome Pratt Date: Thu, 7 May 2026 15:39:57 +0200 Subject: [PATCH 1/5] [RUBY-4303] Update README and validator to support custom permitted statuses --- README.md | 12 +++++- .../companies_house_number_validator/en.yml | 2 +- .../companies_house_number_validator.rb | 7 ++-- .../validators/companies_house_service.rb | 23 ++++++++++- .../companies_house_number_validator_spec.rb | 23 ++++++++++- .../companies_house_service_spec.rb | 41 +++++++++++++++++++ 6 files changed, 98 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4f1e0ab..d2edea6 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,9 @@ This validator checks the company registration number provided. Specifically it If the format is valid, it then makes a call to Companies House to validate that the number - is recognised -- belongs to an 'active' company +- belongs to a company with an allowed status -A company can be in various states for example liquidation, which means for the purposes of Defra its not a valid entity. So we check the status along with the number as part of the validation. +Allowed statuses default to `active` and `voluntary-arrangement`, preserving the existing behaviour. Services can override this when they need to accept other Companies House statuses. Add it to your model or form object using @@ -79,6 +79,14 @@ The company number also accepts a `company_type` option. This checks the `type` validates :company_no, "defra_ruby/validators/companies_house_number": { company_type: "ltd" } ``` +The allowed Companies House statuses can be changed with the `permitted_statuses` option: + +```ruby +validates :company_no, "defra_ruby/validators/companies_house_number": { + permitted_statuses: %i[active voluntary-arrangement liquidation] +} +``` + Note: to mock the Companies House service with the `company_type` option, you will need to be running at least v2.2.0 of [defra-ruby-mocks](https://github.com/DEFRA/defra-ruby-mocks#companies-house). ### Email diff --git a/config/locales/defra_ruby/validators/companies_house_number_validator/en.yml b/config/locales/defra_ruby/validators/companies_house_number_validator/en.yml index 13f8964..e034047 100644 --- a/config/locales/defra_ruby/validators/companies_house_number_validator/en.yml +++ b/config/locales/defra_ruby/validators/companies_house_number_validator/en.yml @@ -7,5 +7,5 @@ en: not_found: Companies House couldn't find a company with this number inactive: Your company must be registered as an active company unsupported_company_type: Your company must be registered as a limited company or a limited liability partnership - argument_error: Invalid permitted_types value. It must be nil, a string or an array of strings. + argument_error: Invalid permitted_types or permitted_statuses value. It must be nil, a string, a symbol or an array. error: There was an error connecting with Companies House. Hopefully this is a one off and will work if you try again. diff --git a/lib/defra_ruby/validators/companies_house_number_validator.rb b/lib/defra_ruby/validators/companies_house_number_validator.rb index 6d88ab0..aabfb60 100644 --- a/lib/defra_ruby/validators/companies_house_number_validator.rb +++ b/lib/defra_ruby/validators/companies_house_number_validator.rb @@ -17,7 +17,8 @@ def validate_each(record, attribute, value) return false unless format_is_valid?(record, attribute, value) permitted_types = options[:permitted_types] - validate_with_companies_house(record, attribute, value, permitted_types) + permitted_statuses = options[:permitted_statuses] + validate_with_companies_house(record, attribute, value, permitted_types, permitted_statuses) end private @@ -37,8 +38,8 @@ def format_is_valid?(record, attribute, value) false end - def validate_with_companies_house(record, attribute, value, permitted_types) - case CompaniesHouseService.new(company_number: value, permitted_types:).status + def validate_with_companies_house(record, attribute, value, permitted_types, permitted_statuses) + case CompaniesHouseService.new(company_number: value, permitted_types:, permitted_statuses:).status when :active true when :inactive diff --git a/lib/defra_ruby/validators/companies_house_service.rb b/lib/defra_ruby/validators/companies_house_service.rb index 48e625c..64618d6 100644 --- a/lib/defra_ruby/validators/companies_house_service.rb +++ b/lib/defra_ruby/validators/companies_house_service.rb @@ -5,11 +5,15 @@ module DefraRuby module Validators class CompaniesHouseService - def initialize(company_number:, permitted_types: nil) + DEFAULT_PERMITTED_STATUSES = %i[active voluntary-arrangement].freeze + + def initialize(company_number:, permitted_types: nil, permitted_statuses: nil) @company_number = company_number @permitted_types = permitted_types + @permitted_statuses = permitted_statuses validate_permitted_types + validate_permitted_statuses end def status @@ -34,8 +38,23 @@ def validate_permitted_types raise ArgumentError, I18n.t("defra_ruby.validators.CompaniesHouseNumberValidator.argument_error") end + def validate_permitted_statuses + return if @permitted_statuses.nil? + + return if @permitted_statuses.is_a?(String) || + @permitted_statuses.is_a?(Symbol) || + @permitted_statuses.is_a?(Array) + + raise ArgumentError, I18n.t("defra_ruby.validators.CompaniesHouseNumberValidator.argument_error") + end + def status_is_allowed?(companies_house_response) - %i[active voluntary-arrangement].include?(companies_house_response[:company_status]) + permitted_statuses.include?(companies_house_response[:company_status]) + end + + def permitted_statuses + @permitted_statuses ||= DEFAULT_PERMITTED_STATUSES + Array(@permitted_statuses).map { |status| status.to_s.to_sym } end def company_type_is_allowed?(companies_house_response) diff --git a/spec/defra_ruby/validators/companies_house_number_validator_spec.rb b/spec/defra_ruby/validators/companies_house_number_validator_spec.rb index f24662f..4c95c04 100644 --- a/spec/defra_ruby/validators/companies_house_number_validator_spec.rb +++ b/spec/defra_ruby/validators/companies_house_number_validator_spec.rb @@ -22,6 +22,14 @@ module Test validates :company_no, "defra_ruby/validators/companies_house_number": { permitted_types: %w[llp ltd] } end + CompaniesHousePermittedStatusesAndNumberValidatable = Struct.new(:company_no) do + include ActiveModel::Validations + + validates :company_no, "defra_ruby/validators/companies_house_number": { + permitted_statuses: %i[active voluntary-arrangement liquidation] + } + end + CompaniesHouseInvalidPermittedTypesAndNumberValidatable = Struct.new(:company_no) do include ActiveModel::Validations @@ -137,7 +145,7 @@ module Validators Test::CompaniesHouseSinglePermittedTypeAndNumberValidatable.new(valid_numbers.first).valid? expect(CompaniesHouseService).to have_received(:new) - .with(company_number: valid_numbers.first, permitted_types: "ltd") + .with(company_number: valid_numbers.first, permitted_types: "ltd", permitted_statuses: nil) end end @@ -148,7 +156,18 @@ module Validators Test::CompaniesHouseMultiplePermittedTypesAndNumberValidatable.new(valid_numbers.first).valid? expect(CompaniesHouseService).to have_received(:new) - .with(company_number: valid_numbers.first, permitted_types:) + .with(company_number: valid_numbers.first, permitted_types:, permitted_statuses: nil) + end + end + + context "with multiple permitted_statuses options" do + let(:permitted_statuses) { %i[active voluntary-arrangement liquidation] } + + it "calls the companies house service with all permitted statuses" do + Test::CompaniesHousePermittedStatusesAndNumberValidatable.new(valid_numbers.first).valid? + + expect(CompaniesHouseService).to have_received(:new) + .with(company_number: valid_numbers.first, permitted_types: nil, permitted_statuses:) end end diff --git a/spec/defra_ruby/validators/companies_house_service_spec.rb b/spec/defra_ruby/validators/companies_house_service_spec.rb index 7ad7a37..100e82b 100644 --- a/spec/defra_ruby/validators/companies_house_service_spec.rb +++ b/spec/defra_ruby/validators/companies_house_service_spec.rb @@ -66,6 +66,47 @@ end end + context "when the company_number has a liquidation status" do + let(:company_status) { :liquidation } + + it "returns :inactive by default" do + expect(companies_house_service.status).to eq(:inactive) + end + end + + context "when checking the company_status" do + subject(:companies_house_service) { described_class.new(company_number: "09360070", permitted_statuses:) } + + context "when an invalid permitted company statuses value is specified" do + let(:permitted_statuses) { 0 } + + it { expect { companies_house_service.status }.to raise_error(ArgumentError) } + end + + context "when a single permitted company status is specified" do + let(:permitted_statuses) { "liquidation" } + let(:company_status) { :liquidation } + + it { expect(companies_house_service.status).to eq(:active) } + end + + context "when multiple permitted company statuses are specified" do + let(:permitted_statuses) { %i[active voluntary-arrangement liquidation] } + + context "when the actual status is liquidation" do + let(:company_status) { :liquidation } + + it { expect(companies_house_service.status).to eq(:active) } + end + + context "when the actual status is dissolved" do + let(:company_status) { :dissolved } + + it { expect(companies_house_service.status).to eq(:inactive) } + end + end + end + context "when checking the company_type" do subject(:companies_house_service) { described_class.new(company_number: "09360070", permitted_types:) } From 210713c229cd9eb60bec9ebdd2d7f661c2baf05f Mon Sep 17 00:00:00 2001 From: Jerome Pratt Date: Thu, 7 May 2026 16:09:26 +0200 Subject: [PATCH 2/5] [RUBY-4303] Add Ruby version and additional RuboCop plugins to configuration --- .rubocop.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index d913572..caf4cdf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,8 +5,13 @@ inherit_gem: - default.yml plugins: + - rubocop-rake + - rubocop-rspec - rubocop-rspec_rails +AllCops: + TargetRubyVersion: 3.2 + Lint/EmptyClass: AllowComments: true From 80f83d6cdaca8b16ad9e7f5378f6673a390c4c0c Mon Sep 17 00:00:00 2001 From: Jerome Pratt Date: Thu, 7 May 2026 16:23:22 +0200 Subject: [PATCH 3/5] [RUBY-4303] Refactor error handling to use a constant for translation key --- README.md | 3 ++- lib/defra_ruby/validators/companies_house_service.rb | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d2edea6..32bb7ad 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ If the format is valid, it then makes a call to Companies House to validate that - is recognised - belongs to a company with an allowed status -Allowed statuses default to `active` and `voluntary-arrangement`, preserving the existing behaviour. Services can override this when they need to accept other Companies House statuses. +Allowed statuses default to `active` and `voluntary-arrangement`, preserving the existing behaviour. +Services can override this when they need to accept other Companies House statuses. Add it to your model or form object using diff --git a/lib/defra_ruby/validators/companies_house_service.rb b/lib/defra_ruby/validators/companies_house_service.rb index 64618d6..da2b16f 100644 --- a/lib/defra_ruby/validators/companies_house_service.rb +++ b/lib/defra_ruby/validators/companies_house_service.rb @@ -6,6 +6,9 @@ module DefraRuby module Validators class CompaniesHouseService DEFAULT_PERMITTED_STATUSES = %i[active voluntary-arrangement].freeze + ARGUMENT_ERROR_TRANSLATION_KEY = "defra_ruby.validators.CompaniesHouseNumberValidator.argument_error" + + private_constant :ARGUMENT_ERROR_TRANSLATION_KEY def initialize(company_number:, permitted_types: nil, permitted_statuses: nil) @company_number = company_number @@ -35,7 +38,7 @@ def validate_permitted_types return if @permitted_types.is_a?(String) || @permitted_types.is_a?(Array) - raise ArgumentError, I18n.t("defra_ruby.validators.CompaniesHouseNumberValidator.argument_error") + raise ArgumentError, I18n.t(ARGUMENT_ERROR_TRANSLATION_KEY) end def validate_permitted_statuses @@ -45,11 +48,11 @@ def validate_permitted_statuses @permitted_statuses.is_a?(Symbol) || @permitted_statuses.is_a?(Array) - raise ArgumentError, I18n.t("defra_ruby.validators.CompaniesHouseNumberValidator.argument_error") + raise ArgumentError, I18n.t(ARGUMENT_ERROR_TRANSLATION_KEY) end def status_is_allowed?(companies_house_response) - permitted_statuses.include?(companies_house_response[:company_status]) + permitted_statuses.include?(companies_house_response[:company_status].to_s.to_sym) end def permitted_statuses @@ -67,7 +70,7 @@ def company_type_is_allowed?(companies_house_response) when Array @permitted_types.include?(companies_house_response[:company_type].to_s) else - raise ArgumentError, I18n.t("defra_ruby.validators.CompaniesHouseNumberValidator.argument_error") + raise ArgumentError, I18n.t(ARGUMENT_ERROR_TRANSLATION_KEY) end end end From 4cf36223c845c889c5b58a33a0a8d2d9b72e936e Mon Sep 17 00:00:00 2001 From: Jerome Pratt Date: Fri, 8 May 2026 13:27:28 +0200 Subject: [PATCH 4/5] [RUBY-4303] Update validation logic for permitted statuses and adjust error message --- .../validators/companies_house_number_validator/en.yml | 2 +- lib/defra_ruby/validators/companies_house_service.rb | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/config/locales/defra_ruby/validators/companies_house_number_validator/en.yml b/config/locales/defra_ruby/validators/companies_house_number_validator/en.yml index e034047..9b95a7d 100644 --- a/config/locales/defra_ruby/validators/companies_house_number_validator/en.yml +++ b/config/locales/defra_ruby/validators/companies_house_number_validator/en.yml @@ -7,5 +7,5 @@ en: not_found: Companies House couldn't find a company with this number inactive: Your company must be registered as an active company unsupported_company_type: Your company must be registered as a limited company or a limited liability partnership - argument_error: Invalid permitted_types or permitted_statuses value. It must be nil, a string, a symbol or an array. + argument_error: Invalid permitted_types or permitted_statuses value. It must be nil, a string or an array. error: There was an error connecting with Companies House. Hopefully this is a one off and will work if you try again. diff --git a/lib/defra_ruby/validators/companies_house_service.rb b/lib/defra_ruby/validators/companies_house_service.rb index da2b16f..48b0bfb 100644 --- a/lib/defra_ruby/validators/companies_house_service.rb +++ b/lib/defra_ruby/validators/companies_house_service.rb @@ -44,18 +44,16 @@ def validate_permitted_types def validate_permitted_statuses return if @permitted_statuses.nil? - return if @permitted_statuses.is_a?(String) || - @permitted_statuses.is_a?(Symbol) || - @permitted_statuses.is_a?(Array) + return if @permitted_statuses.is_a?(String) || @permitted_statuses.is_a?(Array) raise ArgumentError, I18n.t(ARGUMENT_ERROR_TRANSLATION_KEY) end def status_is_allowed?(companies_house_response) - permitted_statuses.include?(companies_house_response[:company_status].to_s.to_sym) + normalised_permitted_statuses.include?(companies_house_response[:company_status].to_s.to_sym) end - def permitted_statuses + def normalised_permitted_statuses @permitted_statuses ||= DEFAULT_PERMITTED_STATUSES Array(@permitted_statuses).map { |status| status.to_s.to_sym } end From efdddcd7674e4977a71b5d23ed3123a8f6ef2eb1 Mon Sep 17 00:00:00 2001 From: Jerome Pratt Date: Fri, 8 May 2026 13:36:36 +0200 Subject: [PATCH 5/5] [RUBY-4303] Refactor validation methods to use a shared helper for permitted options --- lib/defra_ruby/validators/companies_house_service.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/defra_ruby/validators/companies_house_service.rb b/lib/defra_ruby/validators/companies_house_service.rb index 48b0bfb..7692d9b 100644 --- a/lib/defra_ruby/validators/companies_house_service.rb +++ b/lib/defra_ruby/validators/companies_house_service.rb @@ -34,21 +34,21 @@ def companies_house_response end def validate_permitted_types - return if @permitted_types.nil? - - return if @permitted_types.is_a?(String) || @permitted_types.is_a?(Array) + return if valid_permitted_option?(@permitted_types) raise ArgumentError, I18n.t(ARGUMENT_ERROR_TRANSLATION_KEY) end def validate_permitted_statuses - return if @permitted_statuses.nil? - - return if @permitted_statuses.is_a?(String) || @permitted_statuses.is_a?(Array) + return if valid_permitted_option?(@permitted_statuses) raise ArgumentError, I18n.t(ARGUMENT_ERROR_TRANSLATION_KEY) end + def valid_permitted_option?(option) + option.nil? || option.is_a?(String) || option.is_a?(Array) + end + def status_is_allowed?(companies_house_response) normalised_permitted_statuses.include?(companies_house_response[:company_status].to_s.to_sym) end