From c9fde480ad1408a39f08ce7a287798c1ffeb370a Mon Sep 17 00:00:00 2001 From: Giuseppe Lobraico Date: Wed, 12 Dec 2018 15:22:45 +0000 Subject: [PATCH 1/2] Merge `mas-rad_core` into `rad` More than 85% of `mas-rad_core` is used by `rad` only. A small percentage of the remaining 15% is "shared" between `rad_consumer` and `rad`: we believe that this is not enough to justify the `mas-rad_core` existence in the first instance. This commit adds back what belongs to `rad`, and the same will be done for `rad_consumer` in order to create a better separation of concerns, if it needs to be. --- .gitignore | 1 + Gemfile | 7 +- Gemfile.lock | 22 +- app/models/accreditation.rb | 7 + app/models/adviser.rb | 109 +++ app/models/allowed_payment_method.rb | 7 + app/models/firm.rb | 215 +++++ app/models/friendly_namable.rb | 16 + app/models/geocodable.rb | 50 ++ app/models/in_person_advice_method.rb | 9 + app/models/initial_advice_fee_structure.rb | 7 + app/models/initial_meeting_duration.rb | 7 + app/models/investment_size.rb | 18 + app/models/lookup/adviser.rb | 10 + app/models/lookup/firm.rb | 17 + app/models/lookup/subsidiary.rb | 11 + app/models/office.rb | 124 +++ app/models/ongoing_advice_fee_structure.rb | 7 + app/models/other_advice_method.rb | 16 + app/models/principal.rb | 105 +++ app/models/professional_body.rb | 3 + app/models/professional_standing.rb | 3 + app/models/qualification.rb | 7 + app/models/snapshot.rb | 28 + app/models/snapshot/adviser_queries.rb | 132 +++ app/models/snapshot/firm_queries.rb | 108 +++ app/models/snapshot/metrics_in_order.rb | 64 ++ app/models/snapshot/office_queries.rb | 6 + app/models/system_nameable.rb | 12 + app/models/translatable.rb | 12 + app/models/travel_distance.rb | 15 + app/serializers/adviser_serializer.rb | 27 + app/serializers/firm_serializer.rb | 104 +++ app/serializers/office_serializer.rb | 17 + config/initializers/geocoder.rb | 10 + config/initializers/sidekiq.rb | 8 + .../20141211110031_create_lookup_firms.rb | 11 + .../20141221140208_create_principals.rb | 23 + ...150416_add_last_sign_in_at_to_principal.rb | 5 + ...141230112136_create_lookup_subsidiaries.rb | 10 + ..._remove_last_sign_in_at_from_principals.rb | 5 + .../20150114144343_create_lookup_advisers.rb | 10 + db/migrate/20150114151447_create_firms.rb | 13 + .../20150115130949_add_address_to_firm.rb | 9 + ...9102735_create_in_person_advice_methods.rb | 15 + ...50119111754_create_other_advice_methods.rb | 15 + ...114218_add_free_initial_meeting_to_firm.rb | 5 + ...125208_create_initial_meeting_durations.rb | 11 + ...25_create_initial_advice_fee_structures.rb | 15 + ...36_create_ongoing_advice_fee_structures.rb | 15 + ...20133717_create_allowed_payment_methods.rb | 15 + ...120141928_add_minimum_fixed_fee_to_firm.rb | 5 + .../20150120150738_create_investment_sizes.rb | 15 + db/migrate/20150121110757_create_advisers.rb | 11 + ...usiness_income_breakdown_fields_to_firm.rb | 11 + .../20150121134845_create_qualifications.rb | 15 + .../20150121154458_create_accreditations.rb | 15 + ...121173015_create_professional_standings.rb | 15 + ...150121181341_create_professional_bodies.rb | 15 + ...28_add_confirmed_disclaimer_to_advisers.rb | 5 + ...4350_add_geographical_fields_to_adviser.rb | 7 + .../20150125145457_add_parent_id_to_firms.rb | 5 + ...unique_constraint_from_firms_fca_number.rb | 5 + ...27084858_add_ordering_on_reference_data.rb | 31 + ...remove_covers_whole_of_uk_from_advisers.rb | 17 + ...3610_add_latitude_and_longitude_to_firm.rb | 6 + ..._add_latitude_and_longitude_to_advisers.rb | 6 + ...315_add_cy_name_to_other_advice_methods.rb | 5 + ...05150719_add_cy_name_to_investment_size.rb | 20 + ...dex_on_lookup_advisers_reference_number.rb | 5 + ...entage_fields_to_boolean_fields_on_firm.rb | 35 + ...3_move_website_from_principals_to_firms.rb | 25 + ...0716123032_remove_other_flag_from_firms.rb | 5 + ...43047_add_investing_checkboxes_to_firms.rb | 6 + .../20150807134551_add_languages_to_firm.rb | 5 + db/migrate/20150812140642_create_offices.rb | 18 + ...move_confirmed_disclaimer_from_advisers.rb | 5 + .../20150817141257_add_status_to_firms.rb | 5 + ...50825090822_move_firm_address_to_office.rb | 48 ++ ...50930140851_remove_firm_email_and_phone.rb | 26 + ..._add_default_value_to_minimum_fixed_fee.rb | 15 + ...42_add_latitude_and_longitude_to_office.rb | 6 + ...remove_latitude_and_longitude_from_firm.rb | 72 ++ ...pass_reference_number_check_to_advisers.rb | 5 + db/migrate/20160211161127_create_snapshots.rb | 11 + ...160222091312_remove_other_accreditation.rb | 19 + .../20160317103053_add_sessions_table.rb | 12 + ...add_workplace_financial_advice_to_firms.rb | 5 + ...workplace_financial_advice_to_snapshots.rb | 5 + ...329105636_add_non_uk_residents_to_firms.rb | 5 + ...8_add_add_non_uk_residents_to_snapshots.rb | 5 + .../20161216141323_add_website_to_office.rb | 5 + db/schema.rb | 55 +- lib/elastic_search_client.rb | 52 ++ lib/firm_indexer.rb | 37 + lib/firm_repository.rb | 20 + lib/languages.rb | 8 + lib/model_geocoder.rb | 5 + lib/postcode.rb | 34 + lib/stats.rb | 23 + spec/factories/accreditation.rb | 6 + spec/factories/adviser.rb | 27 + spec/factories/allowed_payment_method.rb | 5 + spec/factories/firm.rb | 98 +++ spec/factories/in_person_advice_method.rb | 6 + .../factories/initial_advice_fee_structure.rb | 5 + spec/factories/initial_meeting_duration.rb | 5 + spec/factories/investment_size.rb | 6 + spec/factories/lookup_adviser.rb | 6 + spec/factories/lookup_firm.rb | 6 + spec/factories/lookup_subsidiary.rb | 6 + spec/factories/office.rb | 14 + .../factories/ongoing_advice_fee_structure.rb | 5 + spec/factories/other_advice_method.rb | 6 + spec/factories/principal.rb | 15 + spec/factories/professional_body.rb | 6 + spec/factories/professional_standing.rb | 6 + spec/factories/qualification.rb | 6 + .../england_and_scotland_postcode.yml | 64 ++ .../vcr_cassettes/geocode-no-results.yml | 54 ++ .../vcr_cassettes/geocode-one-result.yml | 114 +++ .../northern_ireland_and_england_postcode.yml | 57 ++ .../vcr_cassettes/postcode-no-results.yml | 54 ++ .../vcr_cassettes/postcode-one-result.yml | 114 +++ .../scotland_and_england_postcode.yml | 59 ++ .../wales_and_england_postcode.yml | 59 ++ spec/lib/firm_indexer_spec.rb | 123 +++ spec/lib/firm_repository_spec.rb | 34 + spec/lib/languages_spec.rb | 20 + spec/lib/model_geocoder_spec.rb | 46 ++ spec/models/accreditation_spec.rb | 4 + spec/models/adviser_spec.rb | 285 +++++++ spec/models/allowed_payment_method_spec.rb | 3 + spec/models/firm_factory_spec.rb | 191 +++++ spec/models/firm_spec.rb | 606 ++++++++++++++ spec/models/in_person_advice_method_spec.rb | 4 + .../initial_advice_fee_structure_spec.rb | 3 + spec/models/initial_meeting_duration_spec.rb | 3 + spec/models/investment_size_spec.rb | 40 + spec/models/lookup/adviser_spec.rb | 21 + spec/models/lookup/firm_spec.rb | 19 + spec/models/office_spec.rb | 351 ++++++++ .../ongoing_advice_fee_structure_spec.rb | 3 + spec/models/other_advice_method_spec.rb | 6 + spec/models/principal_spec.rb | 239 ++++++ spec/models/qualification_spec.rb | 4 + spec/models/snapshot_spec.rb | 763 ++++++++++++++++++ spec/serializers/firm_serializer_spec.rb | 141 ++++ .../serializers/adviser_serializer_spec.rb | 57 ++ .../serializers/firm_serializer_spec.rb | 141 ++++ .../serializers/office_serializer_spec.rb | 20 + .../field_length_validation_helpers.rb | 10 + .../support/shared_examples/friendly_named.rb | 14 + .../shared_examples/geocodable_examples.rb | 284 +++++++ .../support/shared_examples/reference_data.rb | 14 + spec/support/shared_examples/system_named.rb | 15 + .../shared_examples/translatable_examples.rb | 32 + 157 files changed, 6530 insertions(+), 64 deletions(-) create mode 100644 app/models/accreditation.rb create mode 100644 app/models/adviser.rb create mode 100644 app/models/allowed_payment_method.rb create mode 100644 app/models/firm.rb create mode 100644 app/models/friendly_namable.rb create mode 100644 app/models/geocodable.rb create mode 100644 app/models/in_person_advice_method.rb create mode 100644 app/models/initial_advice_fee_structure.rb create mode 100644 app/models/initial_meeting_duration.rb create mode 100644 app/models/investment_size.rb create mode 100644 app/models/lookup/adviser.rb create mode 100644 app/models/lookup/firm.rb create mode 100644 app/models/lookup/subsidiary.rb create mode 100644 app/models/office.rb create mode 100644 app/models/ongoing_advice_fee_structure.rb create mode 100644 app/models/other_advice_method.rb create mode 100644 app/models/principal.rb create mode 100644 app/models/professional_body.rb create mode 100644 app/models/professional_standing.rb create mode 100644 app/models/qualification.rb create mode 100644 app/models/snapshot.rb create mode 100644 app/models/snapshot/adviser_queries.rb create mode 100644 app/models/snapshot/firm_queries.rb create mode 100644 app/models/snapshot/metrics_in_order.rb create mode 100644 app/models/snapshot/office_queries.rb create mode 100644 app/models/system_nameable.rb create mode 100644 app/models/translatable.rb create mode 100644 app/models/travel_distance.rb create mode 100644 app/serializers/adviser_serializer.rb create mode 100644 app/serializers/firm_serializer.rb create mode 100644 app/serializers/office_serializer.rb create mode 100644 config/initializers/geocoder.rb create mode 100644 config/initializers/sidekiq.rb create mode 100644 db/migrate/20141211110031_create_lookup_firms.rb create mode 100644 db/migrate/20141221140208_create_principals.rb create mode 100644 db/migrate/20141222150416_add_last_sign_in_at_to_principal.rb create mode 100644 db/migrate/20141230112136_create_lookup_subsidiaries.rb create mode 100644 db/migrate/20150106115732_remove_last_sign_in_at_from_principals.rb create mode 100644 db/migrate/20150114144343_create_lookup_advisers.rb create mode 100644 db/migrate/20150114151447_create_firms.rb create mode 100644 db/migrate/20150115130949_add_address_to_firm.rb create mode 100644 db/migrate/20150119102735_create_in_person_advice_methods.rb create mode 100644 db/migrate/20150119111754_create_other_advice_methods.rb create mode 100644 db/migrate/20150119114218_add_free_initial_meeting_to_firm.rb create mode 100644 db/migrate/20150119125208_create_initial_meeting_durations.rb create mode 100644 db/migrate/20150119152325_create_initial_advice_fee_structures.rb create mode 100644 db/migrate/20150119152336_create_ongoing_advice_fee_structures.rb create mode 100644 db/migrate/20150120133717_create_allowed_payment_methods.rb create mode 100644 db/migrate/20150120141928_add_minimum_fixed_fee_to_firm.rb create mode 100644 db/migrate/20150120150738_create_investment_sizes.rb create mode 100644 db/migrate/20150121110757_create_advisers.rb create mode 100644 db/migrate/20150121123437_add_business_income_breakdown_fields_to_firm.rb create mode 100644 db/migrate/20150121134845_create_qualifications.rb create mode 100644 db/migrate/20150121154458_create_accreditations.rb create mode 100644 db/migrate/20150121173015_create_professional_standings.rb create mode 100644 db/migrate/20150121181341_create_professional_bodies.rb create mode 100644 db/migrate/20150121183728_add_confirmed_disclaimer_to_advisers.rb create mode 100644 db/migrate/20150124124350_add_geographical_fields_to_adviser.rb create mode 100644 db/migrate/20150125145457_add_parent_id_to_firms.rb create mode 100644 db/migrate/20150125164156_remove_unique_constraint_from_firms_fca_number.rb create mode 100644 db/migrate/20150127084858_add_ordering_on_reference_data.rb create mode 100644 db/migrate/20150209144836_remove_covers_whole_of_uk_from_advisers.rb create mode 100644 db/migrate/20150210113610_add_latitude_and_longitude_to_firm.rb create mode 100644 db/migrate/20150222092417_add_latitude_and_longitude_to_advisers.rb create mode 100644 db/migrate/20150228095315_add_cy_name_to_other_advice_methods.rb create mode 100644 db/migrate/20150305150719_add_cy_name_to_investment_size.rb create mode 100644 db/migrate/20150423154732_add_index_on_lookup_advisers_reference_number.rb create mode 100644 db/migrate/20150630143001_change_percentage_fields_to_boolean_fields_on_firm.rb create mode 100644 db/migrate/20150706102943_move_website_from_principals_to_firms.rb create mode 100644 db/migrate/20150716123032_remove_other_flag_from_firms.rb create mode 100644 db/migrate/20150806143047_add_investing_checkboxes_to_firms.rb create mode 100644 db/migrate/20150807134551_add_languages_to_firm.rb create mode 100644 db/migrate/20150812140642_create_offices.rb create mode 100644 db/migrate/20150813103046_remove_confirmed_disclaimer_from_advisers.rb create mode 100644 db/migrate/20150817141257_add_status_to_firms.rb create mode 100644 db/migrate/20150825090822_move_firm_address_to_office.rb create mode 100644 db/migrate/20150930140851_remove_firm_email_and_phone.rb create mode 100644 db/migrate/20151102113518_add_default_value_to_minimum_fixed_fee.rb create mode 100644 db/migrate/20151103161642_add_latitude_and_longitude_to_office.rb create mode 100644 db/migrate/20151111132037_remove_latitude_and_longitude_from_firm.rb create mode 100644 db/migrate/20160205150033_add_bypass_reference_number_check_to_advisers.rb create mode 100644 db/migrate/20160211161127_create_snapshots.rb create mode 100644 db/migrate/20160222091312_remove_other_accreditation.rb create mode 100644 db/migrate/20160317103053_add_sessions_table.rb create mode 100644 db/migrate/20160325155550_add_workplace_financial_advice_to_firms.rb create mode 100644 db/migrate/20160328130032_add_workplace_financial_advice_to_snapshots.rb create mode 100644 db/migrate/20160329105636_add_non_uk_residents_to_firms.rb create mode 100644 db/migrate/20160329110348_add_add_non_uk_residents_to_snapshots.rb create mode 100644 db/migrate/20161216141323_add_website_to_office.rb create mode 100644 lib/elastic_search_client.rb create mode 100644 lib/firm_indexer.rb create mode 100644 lib/firm_repository.rb create mode 100644 lib/languages.rb create mode 100644 lib/model_geocoder.rb create mode 100644 lib/postcode.rb create mode 100644 lib/stats.rb create mode 100644 spec/factories/accreditation.rb create mode 100644 spec/factories/adviser.rb create mode 100644 spec/factories/allowed_payment_method.rb create mode 100644 spec/factories/firm.rb create mode 100644 spec/factories/in_person_advice_method.rb create mode 100644 spec/factories/initial_advice_fee_structure.rb create mode 100644 spec/factories/initial_meeting_duration.rb create mode 100644 spec/factories/investment_size.rb create mode 100644 spec/factories/lookup_adviser.rb create mode 100644 spec/factories/lookup_firm.rb create mode 100644 spec/factories/lookup_subsidiary.rb create mode 100644 spec/factories/office.rb create mode 100644 spec/factories/ongoing_advice_fee_structure.rb create mode 100644 spec/factories/other_advice_method.rb create mode 100644 spec/factories/principal.rb create mode 100644 spec/factories/professional_body.rb create mode 100644 spec/factories/professional_standing.rb create mode 100644 spec/factories/qualification.rb create mode 100644 spec/fixtures/vcr_cassettes/england_and_scotland_postcode.yml create mode 100644 spec/fixtures/vcr_cassettes/geocode-no-results.yml create mode 100644 spec/fixtures/vcr_cassettes/geocode-one-result.yml create mode 100644 spec/fixtures/vcr_cassettes/northern_ireland_and_england_postcode.yml create mode 100644 spec/fixtures/vcr_cassettes/postcode-no-results.yml create mode 100644 spec/fixtures/vcr_cassettes/postcode-one-result.yml create mode 100644 spec/fixtures/vcr_cassettes/scotland_and_england_postcode.yml create mode 100644 spec/fixtures/vcr_cassettes/wales_and_england_postcode.yml create mode 100644 spec/lib/firm_indexer_spec.rb create mode 100644 spec/lib/firm_repository_spec.rb create mode 100644 spec/lib/languages_spec.rb create mode 100644 spec/lib/model_geocoder_spec.rb create mode 100644 spec/models/accreditation_spec.rb create mode 100644 spec/models/adviser_spec.rb create mode 100644 spec/models/allowed_payment_method_spec.rb create mode 100644 spec/models/firm_factory_spec.rb create mode 100644 spec/models/firm_spec.rb create mode 100644 spec/models/in_person_advice_method_spec.rb create mode 100644 spec/models/initial_advice_fee_structure_spec.rb create mode 100644 spec/models/initial_meeting_duration_spec.rb create mode 100644 spec/models/investment_size_spec.rb create mode 100644 spec/models/lookup/adviser_spec.rb create mode 100644 spec/models/lookup/firm_spec.rb create mode 100644 spec/models/office_spec.rb create mode 100644 spec/models/ongoing_advice_fee_structure_spec.rb create mode 100644 spec/models/other_advice_method_spec.rb create mode 100644 spec/models/principal_spec.rb create mode 100644 spec/models/qualification_spec.rb create mode 100644 spec/models/snapshot_spec.rb create mode 100644 spec/serializers/firm_serializer_spec.rb create mode 100644 spec/serializers/serializers/adviser_serializer_spec.rb create mode 100644 spec/serializers/serializers/firm_serializer_spec.rb create mode 100644 spec/serializers/serializers/office_serializer_spec.rb create mode 100644 spec/support/field_length_validation_helpers.rb create mode 100644 spec/support/shared_examples/friendly_named.rb create mode 100644 spec/support/shared_examples/geocodable_examples.rb create mode 100644 spec/support/shared_examples/reference_data.rb create mode 100644 spec/support/shared_examples/system_named.rb create mode 100644 spec/support/shared_examples/translatable_examples.rb diff --git a/.gitignore b/.gitignore index dc4d10e09..8e012c9b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .bundle +.DS_Store /log/*.log /tmp /vendor/assets/bower_components diff --git a/Gemfile b/Gemfile index 98e5706da..d6b0f8ce0 100644 --- a/Gemfile +++ b/Gemfile @@ -29,7 +29,12 @@ gem 'jquery-rails' gem 'kaminari' gem 'letter_opener', group: :development gem 'mailjet' -gem 'mas-rad_core', '0.1.5' +gem 'uk_postcode', '~> 2.1.2' +gem 'uk_phone_numbers', '~> 0.1.1' +gem 'language_list', '~> 1.2.1' +gem 'httpclient', '~> 2.8.3' +gem 'geocoder', '~> 1.4.7' +gem 'statsd-ruby', '~> 1.4.0' gem 'oga' gem 'pg', '0.21.0' gem 'rails_email_validator' diff --git a/Gemfile.lock b/Gemfile.lock index 59068d97b..b78ac856a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -178,7 +178,7 @@ GEM faraday_middleware (0.12.2) faraday (>= 0.7.4, < 1.0) ffaker (2.8.1) - geocoder (1.4.7) + geocoder (1.4.9) gherkin (5.0.0) gli (2.17.1) globalid (0.4.1) @@ -226,17 +226,6 @@ GEM activesupport (>= 3.1.0) rack (>= 1.4.0) rest-client - mas-rad_core (0.1.5) - active_model_serializers - geocoder - httpclient - language_list - pg - rails (~> 4.2.10) - redis - statsd-ruby - uk_phone_numbers - uk_postcode method_source (0.9.0) mime-types (3.1) mime-types-data (~> 3.2015) @@ -415,7 +404,7 @@ GEM uglifier (4.1.8) execjs (>= 0.3.0, < 3) uk_phone_numbers (0.1.1) - uk_postcode (2.1.2) + uk_postcode (2.1.3) unf (0.1.4) unf_ext unf_ext (0.0.7.5) @@ -458,12 +447,14 @@ DEPENDENCIES factory_girl_rails faker ffaker + geocoder (~> 1.4.7) + httpclient (~> 2.8.3) jquery-rails kaminari + language_list (~> 1.2.1) launchy letter_opener mailjet - mas-rad_core (= 0.1.5) oga pg (= 0.21.0) poltergeist @@ -486,9 +477,12 @@ DEPENDENCIES sinatra site_prism slack-ruby-client + statsd-ruby (~> 1.4.0) timecop tzinfo-data uglifier (>= 1.3.0) + uk_phone_numbers (~> 0.1.1) + uk_postcode (~> 2.1.2) unicorn vcr webmock diff --git a/app/models/accreditation.rb b/app/models/accreditation.rb new file mode 100644 index 000000000..6f1c7c96f --- /dev/null +++ b/app/models/accreditation.rb @@ -0,0 +1,7 @@ +class Accreditation < ActiveRecord::Base + include FriendlyNamable + + validates_presence_of :name + + default_scope { order(:order) } +end diff --git a/app/models/adviser.rb b/app/models/adviser.rb new file mode 100644 index 000000000..d301a842e --- /dev/null +++ b/app/models/adviser.rb @@ -0,0 +1,109 @@ +class Adviser < ActiveRecord::Base + include Geocodable + + attr_reader :old_firm_id + + belongs_to :firm + + has_and_belongs_to_many :qualifications + has_and_belongs_to_many :accreditations + has_and_belongs_to_many :professional_standings + has_and_belongs_to_many :professional_bodies + + before_validation :assign_name, if: :reference_number? + + before_validation :upcase_postcode + + validates :travel_distance, + presence: true, + inclusion: { in: TravelDistance.all.values } + + validates :postcode, + presence: true, + format: { with: /\A[A-Z\d]{1,4} ?[A-Z\d]{1,3}\z/ } + + validates :reference_number, + presence: true, + uniqueness: true, + format: { + with: /\A[A-Z]{3}[0-9]{5}\z/ + }, unless: :bypass_reference_number_check? + + validate :match_reference_number, unless: :bypass_reference_number_check? + + scope :sorted_by_name, -> { order(:name) } + + after_save :flag_changes_for_after_commit + after_commit :notify_indexer + after_commit :reindex_old_firm + + def notify_indexer + FirmIndexer.handle_aggregate_changed(self) + end + + def self.on_firms_with_fca_number(fca_number) + firms = Firm.where(fca_number: fca_number) + where(firm: firms) + end + + def self.move_all_to_firm(receiving_firm) + transaction do + current_scope.each do |adviser| + adviser.update!(firm: receiving_firm) + end + end + end + + def full_street_address + "#{postcode}, United Kingdom" + end + + def has_address_changes? + changed_attributes.include? :postcode + end + + def add_geocoding_failed_error + errors.add(:geocoding, I18n.t("#{model_name.i18n_key}.geocoding.failure_message")) + end + + def field_order + [ + :reference_number, + :postcode, + :travel_distance + ] + end + + private + + # All record of what changed is gone by the time we get to the after_commit + # hooks. So we cannot use #firm_id_changed? at that point. To work around + # this we flag any important changes here to be actioned later. + def flag_changes_for_after_commit + @old_firm_id = firm_id_change.first if firm_id_changed? + end + + def reindex_old_firm + Firm.find(@old_firm_id).notify_indexer if @old_firm_id.present? + @old_firm_id = nil + end + + def upcase_postcode + postcode.upcase! if postcode.present? + end + + def assign_name + self.name = self.name || Lookup::Adviser.find_by( + reference_number: reference_number + ).try(:name) + end + + def match_reference_number + unless Lookup::Adviser.exists?(reference_number: reference_number) + errors.add( + :reference_number, + I18n.t('adviser.reference_number_unmatched') + ) + end + end +end diff --git a/app/models/allowed_payment_method.rb b/app/models/allowed_payment_method.rb new file mode 100644 index 000000000..8d89f62b1 --- /dev/null +++ b/app/models/allowed_payment_method.rb @@ -0,0 +1,7 @@ +class AllowedPaymentMethod < ActiveRecord::Base + has_and_belongs_to_many :firms + + validates_presence_of :name + + default_scope { order(:order) } +end diff --git a/app/models/firm.rb b/app/models/firm.rb new file mode 100644 index 000000000..b53929f6f --- /dev/null +++ b/app/models/firm.rb @@ -0,0 +1,215 @@ +class Firm < ActiveRecord::Base + FREE_INITIAL_MEETING_VALID_VALUES = [true, false].freeze + + # We use a scalar required field as a marker to detect a record saved with validation + REGISTERED_MARKER_FIELD = :free_initial_meeting + REGISTERED_MARKER_FIELD_VALID_VALUES = FREE_INITIAL_MEETING_VALID_VALUES + + ADVICE_TYPES_ATTRIBUTES = [ + :retirement_income_products_flag, + :pension_transfer_flag, + :long_term_care_flag, + :equity_release_flag, + :inheritance_tax_and_estate_planning_flag, + :wills_and_probate_flag + ] + + scope :registered, -> { where.not(REGISTERED_MARKER_FIELD => nil) } + scope :sorted_by_registered_name, -> { order(:registered_name) } + + def self.languages_used + pluck('DISTINCT unnest("languages")').sort + end + + has_and_belongs_to_many :in_person_advice_methods + has_and_belongs_to_many :other_advice_methods + has_and_belongs_to_many :initial_advice_fee_structures + has_and_belongs_to_many :ongoing_advice_fee_structures + has_and_belongs_to_many :allowed_payment_methods + has_and_belongs_to_many :investment_sizes + + belongs_to :initial_meeting_duration + belongs_to :principal, primary_key: :fca_number, foreign_key: :fca_number + belongs_to :parent, class_name: 'Firm' + + has_many :advisers, dependent: :destroy + has_many :offices, -> { order created_at: :asc }, dependent: :destroy + has_many :subsidiaries, class_name: 'Firm', foreign_key: :parent_id, dependent: :destroy + has_many :trading_names, class_name: 'Firm', foreign_key: :parent_id, dependent: :destroy + has_many :qualifications, -> { reorder('').uniq }, through: :advisers + has_many :accreditations, -> { reorder('').uniq }, through: :advisers + + attr_accessor :percent_total + attr_accessor :primary_advice_method + + before_validation :clear_inapplicable_advice_methods, + if: -> { primary_advice_method == :remote } + before_validation :clear_blank_languages + before_validation :deduplicate_languages + + validates :website_address, + allow_blank: true, + length: { maximum: 100 }, + format: { with: /\A(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]+/ } + + validates :free_initial_meeting, + inclusion: { in: FREE_INITIAL_MEETING_VALID_VALUES } + + validates :initial_meeting_duration, + presence: true, + if: ->{ free_initial_meeting? } + + validates :initial_advice_fee_structures, + length: { minimum: 1 } + + validates :ongoing_advice_fee_structures, + length: { minimum: 1 } + + validates :allowed_payment_methods, + length: { minimum: 1 } + + validates :minimum_fixed_fee, + allow_blank: false, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0, + less_than: 2147483648 # max value for postgres integer type + } + + validates :in_person_advice_methods, + presence: true, + if: ->{ primary_advice_method == :local } + + validates :other_advice_methods, + presence: true, + if: ->{ primary_advice_method == :remote } + + validates *ADVICE_TYPES_ATTRIBUTES, + inclusion: { in: [true, false] } + + validates :primary_advice_method, + presence: true + + validate :languages do + unless languages.all? { |lang| Languages::AVAILABLE_LANGUAGES_ISO_639_3_CODES.include?(lang) } + errors.add(:languages, :invalid) + end + end + + validate do + unless advice_types.values.any? + errors.add(:advice_types, :invalid) + end + end + + validates :investment_sizes, + length: { minimum: 1 } + + after_commit :notify_indexer + + def notify_indexer + FirmIndexer.handle_firm_changed(self) + end + + # A heuristic that allows us to infer validity + # + # This method is basically a cheap way to answer the question: has this + # record ever been saved with validation enabled? + def registered? + # false is a valid value so we cannot use `.present?` + !(send(REGISTERED_MARKER_FIELD).nil?) + end + + if Rails.env.test? + # A helper to shield tests from modifying the marker field directly + def __set_registered(state) + new_value = (state) ? REGISTERED_MARKER_FIELD_VALID_VALUES.first : nil + send("#{REGISTERED_MARKER_FIELD}=", new_value) + end + alias_method :__registered=, :__set_registered + end + + enum status: { independent: 1, restricted: 2 } + + def in_person_advice? + in_person_advice_methods.present? + end + alias :postcode_searchable? :in_person_advice? + + def trading_name? + parent.present? + end + + alias_method :subsidiary?, :trading_name? + + def field_order + [ + :website_address, + :address_line_one, + :address_line_two, + :address_town, + :address_county, + :address_postcode, + :primary_advice_method, + :in_person_advice_methods, + :other_advice_methods, + :free_initial_meeting, + :initial_meeting_duration, + :initial_advice_fee_structures, + :ongoing_advice_fee_structures, + :allowed_payment_methods, + :minimum_fixed_fee, + :percent_total, + *ADVICE_TYPES_ATTRIBUTES, + :ethical_investing_flag, + :sharia_investing_flag, + :languages, + :investment_sizes + ] + end + + def advice_types + ADVICE_TYPES_ATTRIBUTES.map { |a| [a, self[a]] }.to_h + end + + def primary_advice_method + return @primary_advice_method.to_sym if @primary_advice_method + infer_primary_advice_method + end + + def main_office + offices.first + end + + def publishable? + registered? && offices.any? && !missing_advisers? + end + + def missing_advisers? + (primary_advice_method == :local) && advisers.empty? + end + + private + + def infer_primary_advice_method + if in_person_advice_methods.any? + :local + elsif other_advice_methods.any? + :remote + else + nil + end + end + + def clear_inapplicable_advice_methods + self.in_person_advice_methods = [] + end + + def clear_blank_languages + languages.reject! &:blank? + end + + def deduplicate_languages + languages.uniq! + end +end diff --git a/app/models/friendly_namable.rb b/app/models/friendly_namable.rb new file mode 100644 index 000000000..b2e4fd030 --- /dev/null +++ b/app/models/friendly_namable.rb @@ -0,0 +1,16 @@ +module FriendlyNamable + def self.included(base) + base.extend(ClassMethods) + + def friendly_name + I18n.t("#{model_name.i18n_key}.ordinal.#{order}") + end + end + + module ClassMethods + def friendly_name(id) + order = find(id).order + I18n.t("#{model_name.i18n_key}.ordinal.#{order}") + end + end +end diff --git a/app/models/geocodable.rb b/app/models/geocodable.rb new file mode 100644 index 000000000..66717dbca --- /dev/null +++ b/app/models/geocodable.rb @@ -0,0 +1,50 @@ +module Geocodable + def self.included(model) + model.scope :geocoded, -> { model.where.not(latitude: nil, longitude: nil) } + end + + def latitude=(value) + value = value.to_f.round(6) unless value.nil? + write_attribute(:latitude, value) + end + + def longitude=(value) + value = value.to_f.round(6) unless value.nil? + write_attribute(:longitude, value) + end + + def geocoded? + coordinates.compact.present? + end + + def coordinates + [latitude, longitude] + end + + def coordinates=(coordinates) + self.latitude, self.longitude = coordinates + end + + def geocode + return false unless valid? + return true unless needs_to_be_geocoded? + + self.coordinates = ModelGeocoder.geocode(self) + add_geocoding_failed_error unless geocoded? + + geocoded? + end + + def needs_to_be_geocoded? + !geocoded? || has_address_changes? + end + + def save_with_geocoding + geocode && save + end + + def update_with_geocoding(params) + self.attributes = params + save_with_geocoding + end +end diff --git a/app/models/in_person_advice_method.rb b/app/models/in_person_advice_method.rb new file mode 100644 index 000000000..b261d5b17 --- /dev/null +++ b/app/models/in_person_advice_method.rb @@ -0,0 +1,9 @@ +class InPersonAdviceMethod < ActiveRecord::Base + include FriendlyNamable + + has_and_belongs_to_many :firms + + validates_presence_of :name + + default_scope { order(:order) } +end diff --git a/app/models/initial_advice_fee_structure.rb b/app/models/initial_advice_fee_structure.rb new file mode 100644 index 000000000..7b50e08f9 --- /dev/null +++ b/app/models/initial_advice_fee_structure.rb @@ -0,0 +1,7 @@ +class InitialAdviceFeeStructure < ActiveRecord::Base + has_and_belongs_to_many :firms + + validates_presence_of :name + + default_scope { order(:order) } +end diff --git a/app/models/initial_meeting_duration.rb b/app/models/initial_meeting_duration.rb new file mode 100644 index 000000000..a0ac8f029 --- /dev/null +++ b/app/models/initial_meeting_duration.rb @@ -0,0 +1,7 @@ +class InitialMeetingDuration < ActiveRecord::Base + has_many :firms + + validates_presence_of :name + + default_scope { order(:order) } +end diff --git a/app/models/investment_size.rb b/app/models/investment_size.rb new file mode 100644 index 000000000..3647771ac --- /dev/null +++ b/app/models/investment_size.rb @@ -0,0 +1,18 @@ +class InvestmentSize < ActiveRecord::Base + include Translatable + include FriendlyNamable + + has_and_belongs_to_many :firms + + validates_presence_of :name + + default_scope { order(:order) } + + def self.lowest + first + end + + def lowest? + self == self.class.lowest + end +end diff --git a/app/models/lookup/adviser.rb b/app/models/lookup/adviser.rb new file mode 100644 index 000000000..3a916fc93 --- /dev/null +++ b/app/models/lookup/adviser.rb @@ -0,0 +1,10 @@ +module Lookup + class Adviser < ActiveRecord::Base + validates_length_of :reference_number, is: 8 + validates_presence_of :name + + def self.table_name + "lookup_#{super}" + end + end +end diff --git a/app/models/lookup/firm.rb b/app/models/lookup/firm.rb new file mode 100644 index 000000000..a1165624f --- /dev/null +++ b/app/models/lookup/firm.rb @@ -0,0 +1,17 @@ +module Lookup + class Firm < ActiveRecord::Base + has_many :subsidiaries, primary_key: :fca_number, foreign_key: :fca_number + + validates :fca_number, + length: { is: 6 }, + numericality: { only_integer: true } + + def subsidiaries? + subsidiaries.present? + end + + def self.table_name + "lookup_#{super}" + end + end +end diff --git a/app/models/lookup/subsidiary.rb b/app/models/lookup/subsidiary.rb new file mode 100644 index 000000000..64db5411e --- /dev/null +++ b/app/models/lookup/subsidiary.rb @@ -0,0 +1,11 @@ +module Lookup + class Subsidiary < ActiveRecord::Base + validates :fca_number, + length: { is: 6 }, + numericality: { only_integer: true } + + def self.table_name + "lookup_#{super}" + end + end +end diff --git a/app/models/office.rb b/app/models/office.rb new file mode 100644 index 000000000..d3799aef7 --- /dev/null +++ b/app/models/office.rb @@ -0,0 +1,124 @@ +require 'uk_postcode' +require 'uk_phone_numbers' + +class Office < ActiveRecord::Base + include Geocodable + + ADDRESS_FIELDS = [ + :address_line_one, + :address_line_two, + :address_town, + :address_county, + :address_postcode + ].freeze + + belongs_to :firm + + validates :email_address, + presence: false, + length: { maximum: 50 }, + format: { with: /.+@.+\..+/ } + + validate :telephone_number_is_valid + + validates :address_line_one, + presence: true, + length: { maximum: 100 } + + validates :address_line_two, + length: { maximum: 100 } + + validate :postcode_is_valid + + validates :address_town, + presence: true, + length: { maximum: 100 } + + validates :address_county, + presence: false, + length: { maximum: 100 } + + validates :disabled_access, inclusion: { in: [true, false] } + + after_commit :notify_indexer + + def notify_indexer + FirmIndexer.handle_aggregate_changed(self) + end + + def field_order + [ + :address_line_one, + :address_line_two, + :address_town, + :address_county, + :address_postcode, + :email_address, + :telephone_number, + :disabled_access, + :website + ] + end + + def telephone_number=(new_phone_number) + super cleanup_telephone_number(new_phone_number) + end + + def telephone_number + return format_telephone_number(cleanup_telephone_number(super)) + end + + # The Geocodable interface expect an object that responds to + # Geocodable#full_street_address. So, to respect the interface, + # we created internally the method postcode_only_address, + # to reflect what this object returns differently from the others. + # + def full_street_address + postcode_only_address + end + + def has_address_changes? + ADDRESS_FIELDS.any? { |field| changed_attributes.include? field } + end + + def add_geocoding_failed_error + errors.add(:geocoding, I18n.t("#{model_name.i18n_key}.geocoding.failure_message")) + end + + def address_postcode=(postcode) + return super unless postcode.present? + + parsed_postcode = UKPostcode.parse(postcode) + + return super unless parsed_postcode.full_valid? + + new_postcode = "#{parsed_postcode.outcode} #{parsed_postcode.incode}" + write_attribute(:address_postcode, new_postcode) + end + + private + + def postcode_only_address + [address_postcode, 'United Kingdom'].join(', ') + end + + def postcode_is_valid + if address_postcode.nil? || !UKPostcode.parse(address_postcode).full_valid? + errors.add(:address_postcode, 'is invalid') + end + end + + def telephone_number_is_valid + if telephone_number.nil? || !UKPhoneNumbers.valid?(telephone_number.gsub(' ', '')) + errors.add(:telephone_number, I18n.t("#{model_name.i18n_key}.telephone_number.invalid_format")) + end + end + + def cleanup_telephone_number(telephone_number) + telephone_number.try { |t| t.gsub(/\s+/, ' ').strip } + end + + def format_telephone_number(telephone_number) + telephone_number.try { |t| UKPhoneNumbers.format(t) || t } + end +end diff --git a/app/models/ongoing_advice_fee_structure.rb b/app/models/ongoing_advice_fee_structure.rb new file mode 100644 index 000000000..35a3d26ad --- /dev/null +++ b/app/models/ongoing_advice_fee_structure.rb @@ -0,0 +1,7 @@ +class OngoingAdviceFeeStructure < ActiveRecord::Base + has_and_belongs_to_many :firms + + validates_presence_of :name + + default_scope { order(:order) } +end diff --git a/app/models/other_advice_method.rb b/app/models/other_advice_method.rb new file mode 100644 index 000000000..044c8c07e --- /dev/null +++ b/app/models/other_advice_method.rb @@ -0,0 +1,16 @@ +class OtherAdviceMethod < ActiveRecord::Base + include Translatable + include FriendlyNamable + include SystemNameable + + SYSTEM_NAMES = { + 1 => :phone, + 2 => :online + } + + has_and_belongs_to_many :firms + + validates_presence_of :name + + default_scope { order(:order) } +end diff --git a/app/models/principal.rb b/app/models/principal.rb new file mode 100644 index 000000000..26c20fb3f --- /dev/null +++ b/app/models/principal.rb @@ -0,0 +1,105 @@ +class Principal < ActiveRecord::Base + self.primary_key = 'token' + + before_create :generate_token + after_create :associate_firm + + has_one :firm, + -> { where(parent_id: nil) }, + primary_key: :fca_number, + foreign_key: :fca_number, + dependent: :destroy + + validates :fca_number, + presence: true, + uniqueness: true, + length: { is: 6 }, + numericality: { only_integer: true } + + validates :email_address, + presence: true, + uniqueness: true, + length: { maximum: 50 }, + format: { with: /.+@.+\..+/ } + + validates :first_name, :last_name, :job_title, presence: true, length: 2..80 + + validates :telephone_number, + presence: true, + length: { maximum: 50 }, + format: { with: /\A[0-9 ]+\z/ } + + validates_acceptance_of :confirmed_disclaimer, accept: true + + validate :match_fca_number, if: :fca_number? + + def main_firm_with_trading_names + Firm.where(fca_number: fca_number) + end + + def to_param + token.parameterize + end + + def lookup_firm + @lookup_firm ||= Lookup::Firm.find_by(fca_number: fca_number) + end + + delegate :subsidiaries?, to: :lookup_firm + + def field_order + [ + :fca_number, + :first_name, + :last_name, + :job_title, + :email_address, + :telephone_number, + :confirmed_disclaimer + ] + end + + def find_or_create_subsidiary(id) + subsidiary = lookup_firm.subsidiaries.find(id) + + find_subsidiary(subsidiary).tap do |firm| + firm.save(validate: false) unless firm.persisted? + end + end + + def full_name + "#{first_name} #{last_name}" + end + + def onboarded? + main_firm_with_trading_names.any?(&:publishable?) + end + + private + + def find_subsidiary(subsidiary) + firm.subsidiaries.find_or_initialize_by( + registered_name: subsidiary.name, + fca_number: subsidiary.fca_number + ) + end + + def associate_firm + Firm.new(fca_number: lookup_firm.fca_number, registered_name: lookup_firm.registered_name).tap do |f| + f.save!(validate: false) + end + end + + def match_fca_number + unless Lookup::Firm.exists?(fca_number: self.fca_number) + errors.add( + :fca_number, + I18n.t('registration.principal.fca_number_unmatched') + ) + end + end + + def generate_token + self.token = SecureRandom.hex(4) + end +end diff --git a/app/models/professional_body.rb b/app/models/professional_body.rb new file mode 100644 index 000000000..389624a9a --- /dev/null +++ b/app/models/professional_body.rb @@ -0,0 +1,3 @@ +class ProfessionalBody < ActiveRecord::Base + default_scope { order(:order) } +end diff --git a/app/models/professional_standing.rb b/app/models/professional_standing.rb new file mode 100644 index 000000000..98b0eb9bf --- /dev/null +++ b/app/models/professional_standing.rb @@ -0,0 +1,3 @@ +class ProfessionalStanding < ActiveRecord::Base + default_scope { order(:order) } +end diff --git a/app/models/qualification.rb b/app/models/qualification.rb new file mode 100644 index 000000000..770ac2ea7 --- /dev/null +++ b/app/models/qualification.rb @@ -0,0 +1,7 @@ +class Qualification < ActiveRecord::Base + include FriendlyNamable + + validates_presence_of :name + + default_scope { order(:order) } +end diff --git a/app/models/snapshot.rb b/app/models/snapshot.rb new file mode 100644 index 000000000..0a17fc4e1 --- /dev/null +++ b/app/models/snapshot.rb @@ -0,0 +1,28 @@ +class Snapshot < ActiveRecord::Base + include Snapshot::MetricsInOrder + include Snapshot::AdviserQueries + include Snapshot::FirmQueries + include Snapshot::OfficeQueries + + def run_queries_and_save + run_queries + save + end + + private + + def publishable_firms + @_publishable_firms ||= Firm.registered.select(&:publishable?) + end + + # 1. Gets all metric fields + # 2. For each of those, find the related query + # 3. Run the query method, count the return value, and set the metric + def run_queries + metrics_in_order.each do |metric| + query_method = "query_#{metric}" + result = send(query_method) + self[metric] = result.count + end + end +end diff --git a/app/models/snapshot/adviser_queries.rb b/app/models/snapshot/adviser_queries.rb new file mode 100644 index 000000000..232aa8d02 --- /dev/null +++ b/app/models/snapshot/adviser_queries.rb @@ -0,0 +1,132 @@ +module Snapshot::AdviserQueries + def query_registered_advisers + Adviser.all + end + + def query_advisers_in_england + advisers_in_country(Adviser.all, 'England') + end + + def query_advisers_in_scotland + advisers_in_country(Adviser.all, 'Scotland') + end + + def query_advisers_in_wales + advisers_in_country(Adviser.all, 'Wales') + end + + def query_advisers_in_northern_ireland + advisers_in_country(Adviser.all, 'Northern Ireland') + end + + TravelDistance.all.keys.each do |val| + method_name = val.downcase.gsub(' ', '_') + define_method "query_advisers_who_travel_#{method_name}" do + advisers_who_travel(val) + end + end + + def query_advisers_accredited_in_solla + advisers_with_accreditation('SOLLA') + end + + def query_advisers_accredited_in_later_life_academy + advisers_with_accreditation('Later Life Academy') + end + + def query_advisers_accredited_in_iso22222 + advisers_with_accreditation('ISO 22222') + end + + def query_advisers_accredited_in_bs8577 + advisers_with_accreditation('British Standard in Financial Planning BS8577') + end + + def query_advisers_with_qualification_in_level_4 + advisers_with_qualification('Level 4 (DipPFS, DipFA® or equivalent)') + end + + def query_advisers_with_qualification_in_level_6 + advisers_with_qualification('Level 6 (APFS, Adv DipFA®)') + end + + def query_advisers_with_qualification_in_chartered_financial_planner + advisers_with_qualification('Chartered Financial Planner') + end + + def query_advisers_with_qualification_in_certified_financial_planner + advisers_with_qualification('Certified Financial Planner') + end + + def query_advisers_with_qualification_in_pension_transfer + advisers_with_qualification('Pension transfer qualifications - holder of G60, AF3, AwPETR®, or equivalent') + end + + def query_advisers_with_qualification_in_equity_release + advisers_with_qualification('Equity release qualifications i.e. holder of Certificate in Equity Release or equivalent') + end + + def query_advisers_with_qualification_in_long_term_care_planning + advisers_with_qualification('Long term care planning qualifications i.e. holder of CF8, CeLTCI®. or equivalent') + end + + def query_advisers_with_qualification_in_tep + advisers_with_qualification('Holder of Trust and Estate Practitioner qualification (TEP) i.e. full member of STEP') + end + + def query_advisers_with_qualification_in_fcii + advisers_with_qualification('Fellow of the Chartered Insurance Institute (FCII)') + end + + def query_advisers_part_of_personal_finance_society + advisers_part_of('Personal Finance Society / Chartered Insurance Institute') + end + + def query_advisers_part_of_institute_financial_planning + advisers_part_of('Institute of Financial Planning') + end + + def query_advisers_part_of_institute_financial_services + advisers_part_of('Institute of Financial Services') + end + + def query_advisers_part_of_ci_bankers_scotland + advisers_part_of('The Chartered Institute of Bankers in Scotland') + end + + def query_advisers_part_of_ci_securities_and_investments + advisers_part_of('The Chartered Institute for Securities and Investments') + end + + def query_advisers_part_of_cfa_institute + advisers_part_of('CFA Institute') + end + + def query_advisers_part_of_chartered_accountants + advisers_part_of('Institute of Chartered Accountants for England and Wales') + end + + private + + def advisers_in_country(advisers, country) + postcodes = advisers.map { |adviser| adviser.postcode } + country_postcodes = Postcode.new.filter_postcodes_by_country(postcodes, country) + advisers.select { |adviser| country_postcodes.include?(adviser.postcode) } + end + + def advisers_part_of(professional_body) + Adviser.includes(:professional_bodies).where(professional_bodies: { name: professional_body }) + end + + def advisers_who_travel(distance) + Adviser.where(travel_distance: TravelDistance.all[distance]) + end + + def advisers_with_accreditation(accreditation) + Adviser.includes(:accreditations).where(accreditations: { name: accreditation }) + end + + def advisers_with_qualification(qualification) + Adviser.includes(:qualifications).where(qualifications: { name: qualification }) + end +end diff --git a/app/models/snapshot/firm_queries.rb b/app/models/snapshot/firm_queries.rb new file mode 100644 index 000000000..593756672 --- /dev/null +++ b/app/models/snapshot/firm_queries.rb @@ -0,0 +1,108 @@ +module Snapshot::FirmQueries + def query_firms_with_no_minimum_fee + publishable_firms.select { |f| [0, nil].include?(f.minimum_fixed_fee) } + end + + def query_firms_with_min_fee_between_1_500 + publishable_firms.select { |f| (1..500).include?(f.minimum_fixed_fee) } + end + + def query_firms_with_min_fee_between_501_1000 + publishable_firms.select { |f| (501..1000).include?(f.minimum_fixed_fee) } + end + + def query_firms_any_pot_size + under_50k_size = InvestmentSize.find_by(name: 'Under £50,000') + publishable_firms.select { |f| f.investment_sizes.exists?(under_50k_size.id) } + end + + def query_firms_any_pot_size_min_fee_less_than_500 + query_firms_any_pot_size.select do |f| + f.minimum_fixed_fee < 500 + end + end + + def query_registered_firms + Firm.registered + end + + def query_published_firms + publishable_firms + end + + def query_firms_offering_face_to_face_advice + publishable_firms.select { |f| f.other_advice_methods.empty? } + end + + def query_firms_offering_remote_advice + publishable_firms.select { |f| f.in_person_advice_methods.empty? } + end + + def query_firms_in_england + firms_in_country(publishable_firms, 'England') + end + + def query_firms_in_scotland + firms_in_country(publishable_firms, 'Scotland') + end + + def query_firms_in_wales + firms_in_country(publishable_firms, 'Wales') + end + + def query_firms_in_northern_ireland + firms_in_country(publishable_firms, 'Northern Ireland') + end + + def query_firms_providing_retirement_income_products + publishable_firms.select { |f| f.retirement_income_products_flag? } + end + + def query_firms_providing_pension_transfer + publishable_firms.select { |f| f.pension_transfer_flag? } + end + + def query_firms_providing_long_term_care + publishable_firms.select { |f| f.long_term_care_flag? } + end + + def query_firms_providing_equity_release + publishable_firms.select { |f| f.equity_release_flag? } + end + + def query_firms_providing_inheritance_tax_and_estate_planning + publishable_firms.select { |f| f.inheritance_tax_and_estate_planning_flag? } + end + + def query_firms_providing_wills_and_probate + publishable_firms.select { |f| f.wills_and_probate_flag? } + end + + def query_firms_providing_ethical_investing + publishable_firms.select { |f| f.ethical_investing_flag? } + end + + def query_firms_providing_workplace_financial_advice + publishable_firms.select { |f| f.workplace_financial_advice_flag? } + end + + def query_firms_providing_non_uk_residents + publishable_firms.select { |f| f.non_uk_residents_flag? } + end + + def query_firms_providing_sharia_investing + publishable_firms.select { |f| f.sharia_investing_flag? } + end + + def query_firms_offering_languages_other_than_english + publishable_firms.select { |f| f.languages.present? } + end + + private + + def firms_in_country(firms, country) + postcodes = firms.map { |firm| firm.main_office.address_postcode } + country_postcodes = Postcode.new.filter_postcodes_by_country(postcodes, country) + firms.select { |firm| country_postcodes.include?(firm.main_office.address_postcode) } + end +end diff --git a/app/models/snapshot/metrics_in_order.rb b/app/models/snapshot/metrics_in_order.rb new file mode 100644 index 000000000..d1024b003 --- /dev/null +++ b/app/models/snapshot/metrics_in_order.rb @@ -0,0 +1,64 @@ +module Snapshot::MetricsInOrder + def metrics_in_order + [ + :firms_with_no_minimum_fee, + :firms_with_min_fee_between_1_500, + :firms_with_min_fee_between_501_1000, + :firms_any_pot_size, + :firms_any_pot_size_min_fee_less_than_500, + :registered_firms, + :published_firms, + :firms_offering_face_to_face_advice, + :firms_offering_remote_advice, + :firms_in_england, + :firms_in_scotland, + :firms_in_wales, + :firms_in_northern_ireland, + :firms_providing_retirement_income_products, + :firms_providing_pension_transfer, + :firms_providing_long_term_care, + :firms_providing_equity_release, + :firms_providing_inheritance_tax_and_estate_planning, + :firms_providing_wills_and_probate, + :firms_providing_ethical_investing, + :firms_providing_sharia_investing, + :firms_providing_workplace_financial_advice, + :firms_providing_non_uk_residents, + :firms_offering_languages_other_than_english, + :offices_with_disabled_access, + :registered_advisers, + :advisers_in_england, + :advisers_in_scotland, + :advisers_in_wales, + :advisers_in_northern_ireland, + :advisers_who_travel_5_miles, + :advisers_who_travel_10_miles, + :advisers_who_travel_25_miles, + :advisers_who_travel_50_miles, + :advisers_who_travel_100_miles, + :advisers_who_travel_150_miles, + :advisers_who_travel_200_miles, + :advisers_who_travel_250_miles, + :advisers_who_travel_uk_wide, + :advisers_accredited_in_solla, + :advisers_accredited_in_later_life_academy, + :advisers_accredited_in_iso22222, + :advisers_accredited_in_bs8577, + :advisers_with_qualification_in_level_4, + :advisers_with_qualification_in_level_6, + :advisers_with_qualification_in_chartered_financial_planner, + :advisers_with_qualification_in_certified_financial_planner, + :advisers_with_qualification_in_pension_transfer, + :advisers_with_qualification_in_equity_release, + :advisers_with_qualification_in_long_term_care_planning, + :advisers_with_qualification_in_tep, + :advisers_with_qualification_in_fcii, + :advisers_part_of_personal_finance_society, + :advisers_part_of_institute_financial_planning, + :advisers_part_of_institute_financial_services, + :advisers_part_of_ci_bankers_scotland, + :advisers_part_of_ci_securities_and_investments, + :advisers_part_of_cfa_institute, :advisers_part_of_chartered_accountants + ] + end +end diff --git a/app/models/snapshot/office_queries.rb b/app/models/snapshot/office_queries.rb new file mode 100644 index 000000000..2b699a4a6 --- /dev/null +++ b/app/models/snapshot/office_queries.rb @@ -0,0 +1,6 @@ +module Snapshot::OfficeQueries + def query_offices_with_disabled_access + firm_ids = publishable_firms.map(&:id) + Office.includes(:firm).where(disabled_access: true, firms: { id: firm_ids }) + end +end diff --git a/app/models/system_nameable.rb b/app/models/system_nameable.rb new file mode 100644 index 000000000..938d5e5a0 --- /dev/null +++ b/app/models/system_nameable.rb @@ -0,0 +1,12 @@ +module SystemNameable + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def system_name(id) + order = find(id).order + self::SYSTEM_NAMES.fetch(order, :not_found) + end + end +end diff --git a/app/models/translatable.rb b/app/models/translatable.rb new file mode 100644 index 000000000..432ba3974 --- /dev/null +++ b/app/models/translatable.rb @@ -0,0 +1,12 @@ +module Translatable + alias_attribute :en_name, :name + + def localized_name + case I18n.locale + when :en + en_name + when :cy + cy_name + end + end +end diff --git a/app/models/travel_distance.rb b/app/models/travel_distance.rb new file mode 100644 index 000000000..2af2935ea --- /dev/null +++ b/app/models/travel_distance.rb @@ -0,0 +1,15 @@ +class TravelDistance + def self.all + { + '5 miles' => 5, + '10 miles' => 10, + '25 miles' => 25, + '50 miles' => 50, + '100 miles' => 100, + '150 miles' => 150, + '200 miles' => 200, + '250 miles' => 250, + 'UK wide' => 650 + } + end +end diff --git a/app/serializers/adviser_serializer.rb b/app/serializers/adviser_serializer.rb new file mode 100644 index 000000000..a0d4c4148 --- /dev/null +++ b/app/serializers/adviser_serializer.rb @@ -0,0 +1,27 @@ +class AdviserSerializer < ActiveModel::Serializer + + attributes :_id, :name, :postcode, :range, :location, :range_location, :qualification_ids, :accreditation_ids + + def _id + object.id + end + + def range + object.travel_distance + end + + def location + { + lat: object.latitude, + lon: object.longitude + } + end + + def range_location + { + type: :circle, + coordinates: [object.longitude, object.latitude], + radius: "#{object.travel_distance}miles" + } + end +end diff --git a/app/serializers/firm_serializer.rb b/app/serializers/firm_serializer.rb new file mode 100644 index 000000000..c859a8d90 --- /dev/null +++ b/app/serializers/firm_serializer.rb @@ -0,0 +1,104 @@ +class FirmSerializer < ActiveModel::Serializer + + attributes :_id, + :registered_name, + :postcode_searchable, + :telephone_number, + :website_address, + :email_address, + :free_initial_meeting, + :minimum_fixed_fee, + :retirement_income_products, + :pension_transfer, + :options_when_paying_for_care, + :equity_release, + :inheritance_tax_planning, + :wills_and_probate, + :other_advice_methods, + :investment_sizes, + :in_person_advice_methods, + :adviser_qualification_ids, + :adviser_accreditation_ids, + :ethical_investing_flag, + :sharia_investing_flag, + :workplace_financial_advice_flag, + :non_uk_residents_flag, + :languages + + has_many :advisers + has_many :offices + + def adviser_accreditation_ids + object.accreditation_ids + end + + def adviser_qualification_ids + object.qualification_ids + end + + def advisers + object.advisers.geocoded + end + + def offices + object.offices.geocoded + end + + def telephone_number + object.main_office.telephone_number + end + + def email_address + object.main_office.email_address + end + + def postcode_searchable + object.postcode_searchable? + end + + def retirement_income_products + boolean_to_percentage object.retirement_income_products_flag + end + + def pension_transfer + boolean_to_percentage object.pension_transfer_flag + end + + def options_when_paying_for_care + boolean_to_percentage object.long_term_care_flag + end + + def equity_release + boolean_to_percentage object.equity_release_flag + end + + def inheritance_tax_planning + boolean_to_percentage object.inheritance_tax_and_estate_planning_flag + end + + def wills_and_probate + boolean_to_percentage object.wills_and_probate_flag + end + + def _id + object.id + end + + def other_advice_methods + object.other_advice_method_ids + end + + def in_person_advice_methods + object.in_person_advice_method_ids + end + + def investment_sizes + object.investment_size_ids + end + + private + + def boolean_to_percentage(boolean) + boolean ? 100 : 0 + end +end diff --git a/app/serializers/office_serializer.rb b/app/serializers/office_serializer.rb new file mode 100644 index 000000000..2e4738133 --- /dev/null +++ b/app/serializers/office_serializer.rb @@ -0,0 +1,17 @@ +class OfficeSerializer < ActiveModel::Serializer + + attributes :_id, :address_line_one, :address_line_two, :address_town, + :address_county, :address_postcode, :email_address, + :telephone_number, :disabled_access, :location, :website + + def _id + object.id + end + + def location + { + lat: object.latitude, + lon: object.longitude + } + end +end diff --git a/config/initializers/geocoder.rb b/config/initializers/geocoder.rb new file mode 100644 index 000000000..14675149c --- /dev/null +++ b/config/initializers/geocoder.rb @@ -0,0 +1,10 @@ +if Rails.env.production? + require 'redis' + + Geocoder.configure( + lookup: :google, + use_https: true, + api_key: ENV['GOOGLE_GEOCODER_API_KEY'], + cache: Redis.new(url: ENV['REDISTOGO_URL']) + ) +end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb new file mode 100644 index 000000000..df440167d --- /dev/null +++ b/config/initializers/sidekiq.rb @@ -0,0 +1,8 @@ +if Rails.env.production? + Sidekiq.configure_server do |config| + SIDEKIQ_DB_POOL_SIZE = ENV.fetch('SIDEKIQ_DB_POOL', 25) + ENV['DATABASE_URL'] = "#{ENV['DATABASE_URL']}?pool=#{SIDEKIQ_DB_POOL_SIZE}" + + ActiveRecord::Base.establish_connection(ENV['DATABASE_URL']) + end +end diff --git a/db/migrate/20141211110031_create_lookup_firms.rb b/db/migrate/20141211110031_create_lookup_firms.rb new file mode 100644 index 000000000..6828d6edc --- /dev/null +++ b/db/migrate/20141211110031_create_lookup_firms.rb @@ -0,0 +1,11 @@ +class CreateLookupFirms < ActiveRecord::Migration + def change + create_table :lookup_firms do |t| + t.integer :fca_number, null: false + t.string :registered_name, null: false, default: '' + t.index :fca_number, unique: true + + t.timestamps + end + end +end diff --git a/db/migrate/20141221140208_create_principals.rb b/db/migrate/20141221140208_create_principals.rb new file mode 100644 index 000000000..3f3796dda --- /dev/null +++ b/db/migrate/20141221140208_create_principals.rb @@ -0,0 +1,23 @@ +class CreatePrincipals < ActiveRecord::Migration + def change + create_table :principals do |t| + with_options null: false do + t.integer :fca_number, length: 6 + t.index :fca_number, unique: true + + t.string :token, length: 32 + t.index :token, unique: true + + t.string :website_address + t.string :first_name + t.string :last_name + t.string :job_title + t.string :email_address + t.string :telephone_number + t.boolean :confirmed_disclaimer, default: false + + t.timestamps + end + end + end +end diff --git a/db/migrate/20141222150416_add_last_sign_in_at_to_principal.rb b/db/migrate/20141222150416_add_last_sign_in_at_to_principal.rb new file mode 100644 index 000000000..a6f71700f --- /dev/null +++ b/db/migrate/20141222150416_add_last_sign_in_at_to_principal.rb @@ -0,0 +1,5 @@ +class AddLastSignInAtToPrincipal < ActiveRecord::Migration + def change + add_column :principals, :last_sign_in_at, :datetime + end +end diff --git a/db/migrate/20141230112136_create_lookup_subsidiaries.rb b/db/migrate/20141230112136_create_lookup_subsidiaries.rb new file mode 100644 index 000000000..babe40301 --- /dev/null +++ b/db/migrate/20141230112136_create_lookup_subsidiaries.rb @@ -0,0 +1,10 @@ +class CreateLookupSubsidiaries < ActiveRecord::Migration + def change + create_table :lookup_subsidiaries do |t| + t.integer :fca_number, null: false, index: true + t.string :name, null: false, default: '' + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150106115732_remove_last_sign_in_at_from_principals.rb b/db/migrate/20150106115732_remove_last_sign_in_at_from_principals.rb new file mode 100644 index 000000000..be353f66e --- /dev/null +++ b/db/migrate/20150106115732_remove_last_sign_in_at_from_principals.rb @@ -0,0 +1,5 @@ +class RemoveLastSignInAtFromPrincipals < ActiveRecord::Migration + def change + remove_column :principals, :last_sign_in_at + end +end diff --git a/db/migrate/20150114144343_create_lookup_advisers.rb b/db/migrate/20150114144343_create_lookup_advisers.rb new file mode 100644 index 000000000..920df3c71 --- /dev/null +++ b/db/migrate/20150114144343_create_lookup_advisers.rb @@ -0,0 +1,10 @@ +class CreateLookupAdvisers < ActiveRecord::Migration + def change + create_table :lookup_advisers do |t| + t.string :reference_number, null: false, unique: true, length: 8 + t.string :name, null: false + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150114151447_create_firms.rb b/db/migrate/20150114151447_create_firms.rb new file mode 100644 index 000000000..7c30005f8 --- /dev/null +++ b/db/migrate/20150114151447_create_firms.rb @@ -0,0 +1,13 @@ +class CreateFirms < ActiveRecord::Migration + def change + create_table :firms do |t| + t.integer :fca_number, null: false + t.string :registered_name, null: false + t.string :email_address + t.string :telephone_number + t.index :fca_number, unique: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150115130949_add_address_to_firm.rb b/db/migrate/20150115130949_add_address_to_firm.rb new file mode 100644 index 000000000..9cd435ad5 --- /dev/null +++ b/db/migrate/20150115130949_add_address_to_firm.rb @@ -0,0 +1,9 @@ +class AddAddressToFirm < ActiveRecord::Migration + def change + add_column :firms, :address_line_one, :string + add_column :firms, :address_line_two, :string + add_column :firms, :address_town, :string + add_column :firms, :address_county, :string + add_column :firms, :address_postcode, :string + end +end diff --git a/db/migrate/20150119102735_create_in_person_advice_methods.rb b/db/migrate/20150119102735_create_in_person_advice_methods.rb new file mode 100644 index 000000000..a1e4ae4b6 --- /dev/null +++ b/db/migrate/20150119102735_create_in_person_advice_methods.rb @@ -0,0 +1,15 @@ +class CreateInPersonAdviceMethods < ActiveRecord::Migration + def change + create_table :in_person_advice_methods do |t| + t.string :name, null: false, unique: true + + t.timestamps null: false + end + + create_join_table :firms, :in_person_advice_methods do |t| + t.index %i(firm_id in_person_advice_method_id), + name: 'firms_in_person_advice_methods_index', + unique: true + end + end +end diff --git a/db/migrate/20150119111754_create_other_advice_methods.rb b/db/migrate/20150119111754_create_other_advice_methods.rb new file mode 100644 index 000000000..1cb2a87c9 --- /dev/null +++ b/db/migrate/20150119111754_create_other_advice_methods.rb @@ -0,0 +1,15 @@ +class CreateOtherAdviceMethods < ActiveRecord::Migration + def change + create_table :other_advice_methods do |t| + t.string :name, null: false, unique: true + + t.timestamps null: false + end + + create_join_table :firms, :other_advice_methods do |t| + t.index %i(firm_id other_advice_method_id), + name: 'firms_other_advice_methods_index', + unique: true + end + end +end diff --git a/db/migrate/20150119114218_add_free_initial_meeting_to_firm.rb b/db/migrate/20150119114218_add_free_initial_meeting_to_firm.rb new file mode 100644 index 000000000..dac4be100 --- /dev/null +++ b/db/migrate/20150119114218_add_free_initial_meeting_to_firm.rb @@ -0,0 +1,5 @@ +class AddFreeInitialMeetingToFirm < ActiveRecord::Migration + def change + add_column :firms, :free_initial_meeting, :boolean + end +end diff --git a/db/migrate/20150119125208_create_initial_meeting_durations.rb b/db/migrate/20150119125208_create_initial_meeting_durations.rb new file mode 100644 index 000000000..34fa640db --- /dev/null +++ b/db/migrate/20150119125208_create_initial_meeting_durations.rb @@ -0,0 +1,11 @@ +class CreateInitialMeetingDurations < ActiveRecord::Migration + def change + create_table :initial_meeting_durations do |t| + t.string :name, null: false, unique: true + + t.timestamps null: false + end + + add_reference :firms, :initial_meeting_duration, index: true + end +end diff --git a/db/migrate/20150119152325_create_initial_advice_fee_structures.rb b/db/migrate/20150119152325_create_initial_advice_fee_structures.rb new file mode 100644 index 000000000..435c583d7 --- /dev/null +++ b/db/migrate/20150119152325_create_initial_advice_fee_structures.rb @@ -0,0 +1,15 @@ +class CreateInitialAdviceFeeStructures < ActiveRecord::Migration + def change + create_table :initial_advice_fee_structures do |t| + t.string :name, null: false, unique: true + + t.timestamps null: false + end + + create_join_table :firms, :initial_advice_fee_structures do |t| + t.index %i(firm_id initial_advice_fee_structure_id), + name: 'firms_initial_advice_fee_structures_index', + unique: true + end + end +end diff --git a/db/migrate/20150119152336_create_ongoing_advice_fee_structures.rb b/db/migrate/20150119152336_create_ongoing_advice_fee_structures.rb new file mode 100644 index 000000000..4547ff79b --- /dev/null +++ b/db/migrate/20150119152336_create_ongoing_advice_fee_structures.rb @@ -0,0 +1,15 @@ +class CreateOngoingAdviceFeeStructures < ActiveRecord::Migration + def change + create_table :ongoing_advice_fee_structures do |t| + t.string :name, null: false, unique: true + + t.timestamps null: false + end + + create_join_table :firms, :ongoing_advice_fee_structures do |t| + t.index %i(firm_id ongoing_advice_fee_structure_id), + name: 'firms_ongoing_advice_fee_structures_index', + unique: true + end + end +end diff --git a/db/migrate/20150120133717_create_allowed_payment_methods.rb b/db/migrate/20150120133717_create_allowed_payment_methods.rb new file mode 100644 index 000000000..ab72344a6 --- /dev/null +++ b/db/migrate/20150120133717_create_allowed_payment_methods.rb @@ -0,0 +1,15 @@ +class CreateAllowedPaymentMethods < ActiveRecord::Migration + def change + create_table :allowed_payment_methods do |t| + t.string :name, null: false, unique: true + + t.timestamps null: false + end + + create_join_table :firms, :allowed_payment_methods do |t| + t.index %i(firm_id allowed_payment_method_id), + name: 'firms_allowed_payment_methods_index', + unique: true + end + end +end diff --git a/db/migrate/20150120141928_add_minimum_fixed_fee_to_firm.rb b/db/migrate/20150120141928_add_minimum_fixed_fee_to_firm.rb new file mode 100644 index 000000000..d31bf7bbb --- /dev/null +++ b/db/migrate/20150120141928_add_minimum_fixed_fee_to_firm.rb @@ -0,0 +1,5 @@ +class AddMinimumFixedFeeToFirm < ActiveRecord::Migration + def change + add_column :firms, :minimum_fixed_fee, :integer + end +end diff --git a/db/migrate/20150120150738_create_investment_sizes.rb b/db/migrate/20150120150738_create_investment_sizes.rb new file mode 100644 index 000000000..102fcb661 --- /dev/null +++ b/db/migrate/20150120150738_create_investment_sizes.rb @@ -0,0 +1,15 @@ +class CreateInvestmentSizes < ActiveRecord::Migration + def change + create_table :investment_sizes do |t| + t.string :name, null: false, unique: true + + t.timestamps null: false + end + + create_join_table :firms, :investment_sizes do |t| + t.index %i(firm_id investment_size_id), + name: 'firms_investment_sizes_index', + unique: true + end + end +end diff --git a/db/migrate/20150121110757_create_advisers.rb b/db/migrate/20150121110757_create_advisers.rb new file mode 100644 index 000000000..5d895b996 --- /dev/null +++ b/db/migrate/20150121110757_create_advisers.rb @@ -0,0 +1,11 @@ +class CreateAdvisers < ActiveRecord::Migration + def change + create_table :advisers do |t| + t.string :reference_number, null: false, unique: true, length: 8 + t.string :name, null: false + t.belongs_to :firm, null: false + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150121123437_add_business_income_breakdown_fields_to_firm.rb b/db/migrate/20150121123437_add_business_income_breakdown_fields_to_firm.rb new file mode 100644 index 000000000..cac3b0dd5 --- /dev/null +++ b/db/migrate/20150121123437_add_business_income_breakdown_fields_to_firm.rb @@ -0,0 +1,11 @@ +class AddBusinessIncomeBreakdownFieldsToFirm < ActiveRecord::Migration + def change + add_column :firms, :retirement_income_products_percent, :integer + add_column :firms, :pension_transfer_percent, :integer + add_column :firms, :long_term_care_percent, :integer + add_column :firms, :equity_release_percent, :integer + add_column :firms, :inheritance_tax_and_estate_planning_percent, :integer + add_column :firms, :wills_and_probate_percent, :integer + add_column :firms, :other_percent, :integer + end +end diff --git a/db/migrate/20150121134845_create_qualifications.rb b/db/migrate/20150121134845_create_qualifications.rb new file mode 100644 index 000000000..2c36f076d --- /dev/null +++ b/db/migrate/20150121134845_create_qualifications.rb @@ -0,0 +1,15 @@ +class CreateQualifications < ActiveRecord::Migration + def change + create_table :qualifications do |t| + t.string :name, null: false + + t.timestamps null: false + end + + create_join_table :advisers, :qualifications do |t| + t.index %i(adviser_id qualification_id), + unique: true, + name: 'advisers_qualifications_index' + end + end +end diff --git a/db/migrate/20150121154458_create_accreditations.rb b/db/migrate/20150121154458_create_accreditations.rb new file mode 100644 index 000000000..359bdcb94 --- /dev/null +++ b/db/migrate/20150121154458_create_accreditations.rb @@ -0,0 +1,15 @@ +class CreateAccreditations < ActiveRecord::Migration + def change + create_table :accreditations do |t| + t.string :name, null: false + + t.timestamps null: false + end + + create_join_table :advisers, :accreditations do |t| + t.index %i(adviser_id accreditation_id), + unique: true, + name: 'advisers_accreditations_index' + end + end +end diff --git a/db/migrate/20150121173015_create_professional_standings.rb b/db/migrate/20150121173015_create_professional_standings.rb new file mode 100644 index 000000000..10f392732 --- /dev/null +++ b/db/migrate/20150121173015_create_professional_standings.rb @@ -0,0 +1,15 @@ +class CreateProfessionalStandings < ActiveRecord::Migration + def change + create_table :professional_standings do |t| + t.string :name, null: false + + t.timestamps null: false + end + + create_join_table :advisers, :professional_standings do |t| + t.index %i(adviser_id professional_standing_id), + unique: true, + name: 'advisers_professional_standings_index' + end + end +end diff --git a/db/migrate/20150121181341_create_professional_bodies.rb b/db/migrate/20150121181341_create_professional_bodies.rb new file mode 100644 index 000000000..b66a873ee --- /dev/null +++ b/db/migrate/20150121181341_create_professional_bodies.rb @@ -0,0 +1,15 @@ +class CreateProfessionalBodies < ActiveRecord::Migration + def change + create_table :professional_bodies do |t| + t.string :name, null: false + + t.timestamps null: false + end + + create_join_table :advisers, :professional_bodies do |t| + t.index %i(adviser_id professional_body_id), + unique: true, + name: 'advisers_professional_bodies_index' + end + end +end diff --git a/db/migrate/20150121183728_add_confirmed_disclaimer_to_advisers.rb b/db/migrate/20150121183728_add_confirmed_disclaimer_to_advisers.rb new file mode 100644 index 000000000..7e9720957 --- /dev/null +++ b/db/migrate/20150121183728_add_confirmed_disclaimer_to_advisers.rb @@ -0,0 +1,5 @@ +class AddConfirmedDisclaimerToAdvisers < ActiveRecord::Migration + def change + add_column :advisers, :confirmed_disclaimer, :boolean, null: false + end +end diff --git a/db/migrate/20150124124350_add_geographical_fields_to_adviser.rb b/db/migrate/20150124124350_add_geographical_fields_to_adviser.rb new file mode 100644 index 000000000..781156220 --- /dev/null +++ b/db/migrate/20150124124350_add_geographical_fields_to_adviser.rb @@ -0,0 +1,7 @@ +class AddGeographicalFieldsToAdviser < ActiveRecord::Migration + def change + add_column :advisers, :postcode, :string, null: false, default: '' + add_column :advisers, :travel_distance, :integer, null: false, default: 0 + add_column :advisers, :covers_whole_of_uk, :boolean, null: false, default: false + end +end diff --git a/db/migrate/20150125145457_add_parent_id_to_firms.rb b/db/migrate/20150125145457_add_parent_id_to_firms.rb new file mode 100644 index 000000000..ccbd3beaf --- /dev/null +++ b/db/migrate/20150125145457_add_parent_id_to_firms.rb @@ -0,0 +1,5 @@ +class AddParentIdToFirms < ActiveRecord::Migration + def change + add_column :firms, :parent_id, :integer, index: true + end +end diff --git a/db/migrate/20150125164156_remove_unique_constraint_from_firms_fca_number.rb b/db/migrate/20150125164156_remove_unique_constraint_from_firms_fca_number.rb new file mode 100644 index 000000000..fe225c0c8 --- /dev/null +++ b/db/migrate/20150125164156_remove_unique_constraint_from_firms_fca_number.rb @@ -0,0 +1,5 @@ +class RemoveUniqueConstraintFromFirmsFcaNumber < ActiveRecord::Migration + def change + remove_index :firms, :fca_number + end +end diff --git a/db/migrate/20150127084858_add_ordering_on_reference_data.rb b/db/migrate/20150127084858_add_ordering_on_reference_data.rb new file mode 100644 index 000000000..b833efcb3 --- /dev/null +++ b/db/migrate/20150127084858_add_ordering_on_reference_data.rb @@ -0,0 +1,31 @@ +class AddOrderingOnReferenceData < ActiveRecord::Migration + REFERENCE_TABLES = [ + :accreditations, + :allowed_payment_methods, + :in_person_advice_methods, + :initial_advice_fee_structures, + :initial_meeting_durations, + :investment_sizes, + :ongoing_advice_fee_structures, + :other_advice_methods, + :professional_bodies, + :professional_standings, + :qualifications + ] + + def up + REFERENCE_TABLES.each do |table| + add_column table, :order, :integer, null: false, default: 0 + + table.to_s.classify.constantize.unscoped.order(:id).each do |m| + m.update_attribute(:order, m.id) + end + end + end + + def down + REFERENCE_TABLES.each do |table| + remove_column table, :order + end + end +end diff --git a/db/migrate/20150209144836_remove_covers_whole_of_uk_from_advisers.rb b/db/migrate/20150209144836_remove_covers_whole_of_uk_from_advisers.rb new file mode 100644 index 000000000..f1044075c --- /dev/null +++ b/db/migrate/20150209144836_remove_covers_whole_of_uk_from_advisers.rb @@ -0,0 +1,17 @@ +class RemoveCoversWholeOfUkFromAdvisers < ActiveRecord::Migration + WHOLE_OF_UK_MILES = 650 + + def up + Adviser.where(covers_whole_of_uk: true).find_each do |adviser| + adviser.postcode = adviser.firm.address_postcode + adviser.travel_distance = WHOLE_OF_UK_MILES + adviser.save! + end + + remove_column :advisers, :covers_whole_of_uk + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20150210113610_add_latitude_and_longitude_to_firm.rb b/db/migrate/20150210113610_add_latitude_and_longitude_to_firm.rb new file mode 100644 index 000000000..749e3522f --- /dev/null +++ b/db/migrate/20150210113610_add_latitude_and_longitude_to_firm.rb @@ -0,0 +1,6 @@ +class AddLatitudeAndLongitudeToFirm < ActiveRecord::Migration + def change + add_column :firms, :latitude, :float + add_column :firms, :longitude, :float + end +end diff --git a/db/migrate/20150222092417_add_latitude_and_longitude_to_advisers.rb b/db/migrate/20150222092417_add_latitude_and_longitude_to_advisers.rb new file mode 100644 index 000000000..7867128d1 --- /dev/null +++ b/db/migrate/20150222092417_add_latitude_and_longitude_to_advisers.rb @@ -0,0 +1,6 @@ +class AddLatitudeAndLongitudeToAdvisers < ActiveRecord::Migration + def change + add_column :advisers, :latitude, :float + add_column :advisers, :longitude, :float + end +end diff --git a/db/migrate/20150228095315_add_cy_name_to_other_advice_methods.rb b/db/migrate/20150228095315_add_cy_name_to_other_advice_methods.rb new file mode 100644 index 000000000..6660446df --- /dev/null +++ b/db/migrate/20150228095315_add_cy_name_to_other_advice_methods.rb @@ -0,0 +1,5 @@ +class AddCyNameToOtherAdviceMethods < ActiveRecord::Migration + def change + add_column :other_advice_methods, :cy_name, :string + end +end diff --git a/db/migrate/20150305150719_add_cy_name_to_investment_size.rb b/db/migrate/20150305150719_add_cy_name_to_investment_size.rb new file mode 100644 index 000000000..ef5c90d6f --- /dev/null +++ b/db/migrate/20150305150719_add_cy_name_to_investment_size.rb @@ -0,0 +1,20 @@ +class AddCyNameToInvestmentSize < ActiveRecord::Migration + def up + add_column :investment_sizes, :cy_name, :string + + mapping = { + 'Under £50,000' => 'Dan £50,000', + '£50,000 - £99,999' => '£50,000 - £99,999', + '£100,000 - £149,999' => '£100,000 - £149,999', + 'Over £150,000' => 'Dros £150,000' + } + + InvestmentSize.all.each do |investment_size| + investment_size.update!(cy_name: mapping[investment_size.name]) + end + end + + def down + remove_column :investment_sizes, :cy_name + end +end diff --git a/db/migrate/20150423154732_add_index_on_lookup_advisers_reference_number.rb b/db/migrate/20150423154732_add_index_on_lookup_advisers_reference_number.rb new file mode 100644 index 000000000..ad97e1956 --- /dev/null +++ b/db/migrate/20150423154732_add_index_on_lookup_advisers_reference_number.rb @@ -0,0 +1,5 @@ +class AddIndexOnLookupAdvisersReferenceNumber < ActiveRecord::Migration + def change + add_index :lookup_advisers, :reference_number, unique: true + end +end diff --git a/db/migrate/20150630143001_change_percentage_fields_to_boolean_fields_on_firm.rb b/db/migrate/20150630143001_change_percentage_fields_to_boolean_fields_on_firm.rb new file mode 100644 index 000000000..d6b20ea0b --- /dev/null +++ b/db/migrate/20150630143001_change_percentage_fields_to_boolean_fields_on_firm.rb @@ -0,0 +1,35 @@ +class ChangePercentageFieldsToBooleanFieldsOnFirm < ActiveRecord::Migration + ADVICE_TYPES_ATTRIBUTES = [ + :retirement_income_products, + :pension_transfer, + :long_term_care, + :equity_release, + :inheritance_tax_and_estate_planning, + :wills_and_probate, + :other] + + def up + ADVICE_TYPES_ATTRIBUTES.each do |field| + # We add the boolean column + add_column :firms, "#{field}_flag".to_sym, :boolean, null: false, default: false + # Translate the old percentage values to the new boolean column + Firm.where("#{field}_percent > ?", 0).update_all("#{field}_flag" => true) + # And finally, remove the percentage column + remove_column :firms, "#{field}_percent".to_sym, :integer + end + end + + def down + ADVICE_TYPES_ATTRIBUTES.each do |field| + # As above, but in reverse. + # Since we've lost information, we can only migrate back by figuring + # true => 100% + # false => 0% + # Which won't validate since the fields won't add up to 100%, but it + # will work in a pinch. + add_column :firms, "#{field}_percent".to_sym, :integer + Firm.where("#{field}_flag" => true).update_all("#{field}_percent" => 100) + remove_column :firms, "#{field}_flag".to_sym, :boolean, null: false, default: false + end + end +end diff --git a/db/migrate/20150706102943_move_website_from_principals_to_firms.rb b/db/migrate/20150706102943_move_website_from_principals_to_firms.rb new file mode 100644 index 000000000..84b08d998 --- /dev/null +++ b/db/migrate/20150706102943_move_website_from_principals_to_firms.rb @@ -0,0 +1,25 @@ +class MoveWebsiteFromPrincipalsToFirms < ActiveRecord::Migration + def up + add_column :firms, :website_address, :string + + Principal.all.each do |principal| + Firm.where(fca_number: principal.fca_number) + .update_all(website_address: principal.website_address) + end + + remove_column :principals, :website_address, :string + end + + def down + add_column :principals, :website_address, :string + + # This doesn't reverse the migration cleanly, but takes the website address + # of the principal's parent firm. It will discard all websites on trading + # name firms. + Principal.all.each do |principal| + principal.update(website_address: principal.firm.try(:website_address)) + end + + remove_column :firms, :website_address, :string + end +end diff --git a/db/migrate/20150716123032_remove_other_flag_from_firms.rb b/db/migrate/20150716123032_remove_other_flag_from_firms.rb new file mode 100644 index 000000000..cba911f1c --- /dev/null +++ b/db/migrate/20150716123032_remove_other_flag_from_firms.rb @@ -0,0 +1,5 @@ +class RemoveOtherFlagFromFirms < ActiveRecord::Migration + def change + remove_column :firms, :other_flag, :string + end +end diff --git a/db/migrate/20150806143047_add_investing_checkboxes_to_firms.rb b/db/migrate/20150806143047_add_investing_checkboxes_to_firms.rb new file mode 100644 index 000000000..20bfd2b87 --- /dev/null +++ b/db/migrate/20150806143047_add_investing_checkboxes_to_firms.rb @@ -0,0 +1,6 @@ +class AddInvestingCheckboxesToFirms < ActiveRecord::Migration + def change + add_column :firms, :ethical_investing_flag, :boolean, default: false, null: false + add_column :firms, :sharia_investing_flag, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20150807134551_add_languages_to_firm.rb b/db/migrate/20150807134551_add_languages_to_firm.rb new file mode 100644 index 000000000..af705cd0a --- /dev/null +++ b/db/migrate/20150807134551_add_languages_to_firm.rb @@ -0,0 +1,5 @@ +class AddLanguagesToFirm < ActiveRecord::Migration + def change + add_column :firms, :languages, :text, array: true, null: false, default: [] + end +end diff --git a/db/migrate/20150812140642_create_offices.rb b/db/migrate/20150812140642_create_offices.rb new file mode 100644 index 000000000..0cfdb4c48 --- /dev/null +++ b/db/migrate/20150812140642_create_offices.rb @@ -0,0 +1,18 @@ +class CreateOffices < ActiveRecord::Migration + def change + create_table :offices do |t| + t.string :address_line_one, null: false + t.string :address_line_two + t.string :address_town, null: false + t.string :address_county + t.string :address_postcode, null: false + t.string :email_address + t.string :telephone_number + t.boolean :disabled_access, default: false, null: false + + t.belongs_to :firm, null: false + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150813103046_remove_confirmed_disclaimer_from_advisers.rb b/db/migrate/20150813103046_remove_confirmed_disclaimer_from_advisers.rb new file mode 100644 index 000000000..bdbb5513d --- /dev/null +++ b/db/migrate/20150813103046_remove_confirmed_disclaimer_from_advisers.rb @@ -0,0 +1,5 @@ +class RemoveConfirmedDisclaimerFromAdvisers < ActiveRecord::Migration + def change + remove_column :advisers, :confirmed_disclaimer, :boolean, null: false + end +end diff --git a/db/migrate/20150817141257_add_status_to_firms.rb b/db/migrate/20150817141257_add_status_to_firms.rb new file mode 100644 index 000000000..6ac7010af --- /dev/null +++ b/db/migrate/20150817141257_add_status_to_firms.rb @@ -0,0 +1,5 @@ +class AddStatusToFirms < ActiveRecord::Migration + def change + add_column :firms, :status, :integer + end +end diff --git a/db/migrate/20150825090822_move_firm_address_to_office.rb b/db/migrate/20150825090822_move_firm_address_to_office.rb new file mode 100644 index 000000000..7ce23d68f --- /dev/null +++ b/db/migrate/20150825090822_move_firm_address_to_office.rb @@ -0,0 +1,48 @@ +# Moving address to a main office. Please note that email and telephone number +# are remaining on Firm, however we seed the main office with the firm's +# email address and telephone number to start with. +class MoveFirmAddressToOffice < ActiveRecord::Migration + class Firm < ActiveRecord::Base; has_many :offices; end + class Office < ActiveRecord::Base; belongs_to :firm; end + + def up + Firm.where.not(address_line_one: nil).each do |firm| + next if firm.offices.any? + + firm.offices.create!( + address_line_one: firm.address_line_one, + address_line_two: firm.address_line_two, + address_town: firm.address_town, + address_county: firm.address_county, + address_postcode: firm.address_postcode, + email_address: firm.email_address, + telephone_number: firm.telephone_number + ) + end + + remove_column :firms, :address_line_one + remove_column :firms, :address_line_two + remove_column :firms, :address_town + remove_column :firms, :address_county + remove_column :firms, :address_postcode + end + + def down + add_column :firms, :address_line_one, :string + add_column :firms, :address_line_two, :string + add_column :firms, :address_town, :string + add_column :firms, :address_county, :string + add_column :firms, :address_postcode, :string + + Firm.all.select { |f| f.offices.any? }.each do |firm| + main_office = firm.offices.first + firm.update!( + address_line_one: main_office.address_line_one, + address_line_two: main_office.address_line_two, + address_town: main_office.address_town, + address_county: main_office.address_county, + address_postcode: main_office.address_postcode + ) + end + end +end diff --git a/db/migrate/20150930140851_remove_firm_email_and_phone.rb b/db/migrate/20150930140851_remove_firm_email_and_phone.rb new file mode 100644 index 000000000..8e4bf8d98 --- /dev/null +++ b/db/migrate/20150930140851_remove_firm_email_and_phone.rb @@ -0,0 +1,26 @@ +class RemoveFirmEmailAndPhone < ActiveRecord::Migration + class Firm < ActiveRecord::Base; has_many :offices, -> { order created_at: :asc }; end + class Office < ActiveRecord::Base; belongs_to :firm; end + + def up + remove_column :firms, :email_address + remove_column :firms, :telephone_number + + # Data does not need to be migrated as the offices migration will have + # already copied these two fields into an office record. + end + + def down + add_column :firms, :email_address, :string + add_column :firms, :telephone_number, :string + + # Use the equivalent main office fields to repopulate + Firm.all.select { |f| f.offices.any? }.each do |firm| + main_office = firm.offices.first + firm.update!( + email_address: main_office.email_address, + telephone_number: main_office.telephone_number + ) + end + end +end diff --git a/db/migrate/20151102113518_add_default_value_to_minimum_fixed_fee.rb b/db/migrate/20151102113518_add_default_value_to_minimum_fixed_fee.rb new file mode 100644 index 000000000..c8dc7e8ae --- /dev/null +++ b/db/migrate/20151102113518_add_default_value_to_minimum_fixed_fee.rb @@ -0,0 +1,15 @@ +class AddDefaultValueToMinimumFixedFee < ActiveRecord::Migration + class Firm < ActiveRecord::Base + end + + def up + firms = Firm.where(minimum_fixed_fee: nil) + firms.each { |f| f.update_attribute('minimum_fixed_fee', 0) } + + change_column_default :firms, :minimum_fixed_fee, 0 + end + + def down + change_column_default :firms, :minimum_fixed_fee, nil + end +end diff --git a/db/migrate/20151103161642_add_latitude_and_longitude_to_office.rb b/db/migrate/20151103161642_add_latitude_and_longitude_to_office.rb new file mode 100644 index 000000000..b5ae6edbb --- /dev/null +++ b/db/migrate/20151103161642_add_latitude_and_longitude_to_office.rb @@ -0,0 +1,6 @@ +class AddLatitudeAndLongitudeToOffice < ActiveRecord::Migration + def change + add_column :offices, :latitude, :float + add_column :offices, :longitude, :float + end +end diff --git a/db/migrate/20151111132037_remove_latitude_and_longitude_from_firm.rb b/db/migrate/20151111132037_remove_latitude_and_longitude_from_firm.rb new file mode 100644 index 000000000..ba990f6da --- /dev/null +++ b/db/migrate/20151111132037_remove_latitude_and_longitude_from_firm.rb @@ -0,0 +1,72 @@ +class RemoveLatitudeAndLongitudeFromFirm < ActiveRecord::Migration + class Firm < ActiveRecord::Base + has_many :offices, -> { order created_at: :asc } + scope :geocoded, -> { where.not(latitude: nil, longitude: nil) } + + def main_office + offices.first + end + end + + class Office < ActiveRecord::Base + belongs_to :firm + + def geocoded? + latitude.present? && longitude.present? + end + end + + def up + migrate_firm_coords_to_main_office + + remove_column :firms, :latitude + remove_column :firms, :longitude + + index_warning + end + + def down + add_column :firms, :latitude, :float + add_column :firms, :longitude, :float + + migrate_main_office_coords_to_firm + + index_warning + end + + private + + def migrate_firm_coords_to_main_office + update_count = 0 + + Firm.geocoded.includes(:offices).find_each do |f| + next unless f.offices.present? + + f.main_office.update!(latitude: f.latitude, + longitude: f.longitude) + + update_count += 1 + end + + say "Migrated #{update_count} coordinates to main offices" + end + + def migrate_main_office_coords_to_firm + update_count = 0 + Firm.includes(:offices).find_each do |f| + next unless f.offices.present? + next unless f.main_office.geocoded? + + f.update!(latitude: f.main_office.latitude, + longitude: f.main_office.longitude) + + update_count += 1 + end + + say "Migrated #{update_count} coordinates to back to firms" + end + + def index_warning + say '(!!) Changes have been made that require reindexing (run: rake firms:index)' + end +end diff --git a/db/migrate/20160205150033_add_bypass_reference_number_check_to_advisers.rb b/db/migrate/20160205150033_add_bypass_reference_number_check_to_advisers.rb new file mode 100644 index 000000000..58b014fac --- /dev/null +++ b/db/migrate/20160205150033_add_bypass_reference_number_check_to_advisers.rb @@ -0,0 +1,5 @@ +class AddBypassReferenceNumberCheckToAdvisers < ActiveRecord::Migration + def change + add_column :advisers, :bypass_reference_number_check, :boolean, default: false + end +end diff --git a/db/migrate/20160211161127_create_snapshots.rb b/db/migrate/20160211161127_create_snapshots.rb new file mode 100644 index 000000000..a5a55f3fd --- /dev/null +++ b/db/migrate/20160211161127_create_snapshots.rb @@ -0,0 +1,11 @@ +class CreateSnapshots < ActiveRecord::Migration + def change + create_table :snapshots do |t| + [:firms_with_no_minimum_fee, :firms_with_min_fee_between_1_500, :firms_with_min_fee_between_501_1000, :firms_any_pot_size, :firms_any_pot_size_min_fee_less_than_500, :registered_firms, :published_firms, :firms_offering_face_to_face_advice, :firms_offering_remote_advice, :firms_in_england, :firms_in_scotland, :firms_in_wales, :firms_in_northern_ireland, :firms_providing_retirement_income_products, :firms_providing_pension_transfer, :firms_providing_long_term_care, :firms_providing_equity_release, :firms_providing_inheritance_tax_and_estate_planning, :firms_providing_wills_and_probate, :firms_providing_ethical_investing, :firms_providing_sharia_investing, :firms_offering_languages_other_than_english, :offices_with_disabled_access, :registered_advisers, :advisers_in_england, :advisers_in_scotland, :advisers_in_wales, :advisers_in_northern_ireland, :advisers_who_travel_5_miles, :advisers_who_travel_10_miles, :advisers_who_travel_25_miles, :advisers_who_travel_50_miles, :advisers_who_travel_100_miles, :advisers_who_travel_150_miles, :advisers_who_travel_200_miles, :advisers_who_travel_250_miles, :advisers_who_travel_uk_wide, :advisers_accredited_in_solla, :advisers_accredited_in_later_life_academy, :advisers_accredited_in_iso22222, :advisers_accredited_in_bs8577, :advisers_with_qualification_in_level_4, :advisers_with_qualification_in_level_6, :advisers_with_qualification_in_chartered_financial_planner, :advisers_with_qualification_in_certified_financial_planner, :advisers_with_qualification_in_pension_transfer, :advisers_with_qualification_in_equity_release, :advisers_with_qualification_in_long_term_care_planning, :advisers_with_qualification_in_tep, :advisers_with_qualification_in_fcii, :advisers_part_of_personal_finance_society, :advisers_part_of_institute_financial_planning, :advisers_part_of_institute_financial_services, :advisers_part_of_ci_bankers_scotland, :advisers_part_of_ci_securities_and_investments, :advisers_part_of_cfa_institute, :advisers_part_of_chartered_accountants].each do |field| + t.integer field, :integer + end + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160222091312_remove_other_accreditation.rb b/db/migrate/20160222091312_remove_other_accreditation.rb new file mode 100644 index 000000000..66a7c9b8b --- /dev/null +++ b/db/migrate/20160222091312_remove_other_accreditation.rb @@ -0,0 +1,19 @@ +class RemoveOtherAccreditation < ActiveRecord::Migration + class Accreditation < ActiveRecord::Base + end + + def up + Accreditation.where(name: 'Other').destroy_all + end + + def down + # Unfortunately we can't just insert the 'Other' record, as there are parts + # of `rad` and `rad_consumer` which rely on the 'Other' record having the + # ID of '5'. This is due to translations and configuring whether or not to + # show the record. + # + # You're allowed to roll back this migration, but be aware that if you're + # using an older version of `rad` or `rad_consumer` they may throw errors + # if this record doesn't exist. + end +end diff --git a/db/migrate/20160317103053_add_sessions_table.rb b/db/migrate/20160317103053_add_sessions_table.rb new file mode 100644 index 000000000..775c31669 --- /dev/null +++ b/db/migrate/20160317103053_add_sessions_table.rb @@ -0,0 +1,12 @@ +class AddSessionsTable < ActiveRecord::Migration + def change + create_table :rad_consumer_sessions do |t| + t.string :session_id, :null => false + t.text :data + t.timestamps + end + + add_index :rad_consumer_sessions, :session_id, :unique => true + add_index :rad_consumer_sessions, :updated_at + end +end diff --git a/db/migrate/20160325155550_add_workplace_financial_advice_to_firms.rb b/db/migrate/20160325155550_add_workplace_financial_advice_to_firms.rb new file mode 100644 index 000000000..3776653e7 --- /dev/null +++ b/db/migrate/20160325155550_add_workplace_financial_advice_to_firms.rb @@ -0,0 +1,5 @@ +class AddWorkplaceFinancialAdviceToFirms < ActiveRecord::Migration + def change + add_column :firms, :workplace_financial_advice_flag, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20160328130032_add_workplace_financial_advice_to_snapshots.rb b/db/migrate/20160328130032_add_workplace_financial_advice_to_snapshots.rb new file mode 100644 index 000000000..a70e11162 --- /dev/null +++ b/db/migrate/20160328130032_add_workplace_financial_advice_to_snapshots.rb @@ -0,0 +1,5 @@ +class AddWorkplaceFinancialAdviceToSnapshots < ActiveRecord::Migration + def change + add_column :snapshots, :firms_providing_workplace_financial_advice, :integer, default: 0 + end +end diff --git a/db/migrate/20160329105636_add_non_uk_residents_to_firms.rb b/db/migrate/20160329105636_add_non_uk_residents_to_firms.rb new file mode 100644 index 000000000..0eac69241 --- /dev/null +++ b/db/migrate/20160329105636_add_non_uk_residents_to_firms.rb @@ -0,0 +1,5 @@ +class AddNonUkResidentsToFirms < ActiveRecord::Migration + def change + add_column :firms, :non_uk_residents_flag, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20160329110348_add_add_non_uk_residents_to_snapshots.rb b/db/migrate/20160329110348_add_add_non_uk_residents_to_snapshots.rb new file mode 100644 index 000000000..0f40a365b --- /dev/null +++ b/db/migrate/20160329110348_add_add_non_uk_residents_to_snapshots.rb @@ -0,0 +1,5 @@ +class AddAddNonUkResidentsToSnapshots < ActiveRecord::Migration + def change + add_column :snapshots, :firms_providing_non_uk_residents, :integer, default: 0 + end +end diff --git a/db/migrate/20161216141323_add_website_to_office.rb b/db/migrate/20161216141323_add_website_to_office.rb new file mode 100644 index 000000000..e03c2dd6f --- /dev/null +++ b/db/migrate/20161216141323_add_website_to_office.rb @@ -0,0 +1,5 @@ +class AddWebsiteToOffice < ActiveRecord::Migration + def change + add_column :offices, :website, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index b71c95111..b14d5559f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -15,7 +15,6 @@ # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - enable_extension "pg_stat_statements" create_table "accreditations", force: :cascade do |t| t.string "name", null: false @@ -87,27 +86,6 @@ t.datetime "updated_at", null: false end - create_table "fcaimport_lookup_advisers", force: :cascade do |t| - t.string "reference_number", limit: 20, null: false - t.string "name", limit: 255, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "fcaimport_lookup_firms", force: :cascade do |t| - t.integer "fca_number", null: false - t.string "registered_name", limit: 255, default: "", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "fcaimport_lookup_subsidiaries", force: :cascade do |t| - t.integer "fca_number", null: false - t.string "name", limit: 255, default: "", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - create_table "firms", force: :cascade do |t| t.integer "fca_number", null: false t.string "registered_name", null: false @@ -198,53 +176,32 @@ t.string "cy_name" end - create_table "last_week_lookup_advisers", force: :cascade do |t| + create_table "lookup_advisers", force: :cascade do |t| t.string "reference_number", null: false t.string "name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end - add_index "last_week_lookup_advisers", ["reference_number"], name: "index_lookup_advisers_on_reference_number", unique: true, using: :btree + add_index "lookup_advisers", ["reference_number"], name: "index_lookup_advisers_on_reference_number", unique: true, using: :btree - create_table "last_week_lookup_firms", force: :cascade do |t| + create_table "lookup_firms", force: :cascade do |t| t.integer "fca_number", null: false t.string "registered_name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" end - add_index "last_week_lookup_firms", ["fca_number"], name: "index_lookup_firms_on_fca_number", unique: true, using: :btree + add_index "lookup_firms", ["fca_number"], name: "index_lookup_firms_on_fca_number", unique: true, using: :btree - create_table "last_week_lookup_subsidiaries", force: :cascade do |t| + create_table "lookup_subsidiaries", force: :cascade do |t| t.integer "fca_number", null: false t.string "name", default: "", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end - add_index "last_week_lookup_subsidiaries", ["fca_number"], name: "index_lookup_subsidiaries_on_fca_number", using: :btree - - create_table "lookup_advisers", force: :cascade do |t| - t.string "reference_number", limit: 20, null: false - t.string "name", limit: 255, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "lookup_firms", force: :cascade do |t| - t.integer "fca_number", null: false - t.string "registered_name", limit: 255, default: "", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "lookup_subsidiaries", force: :cascade do |t| - t.integer "fca_number", null: false - t.string "name", limit: 255, default: "", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end + add_index "lookup_subsidiaries", ["fca_number"], name: "index_lookup_subsidiaries_on_fca_number", using: :btree create_table "offices", force: :cascade do |t| t.string "address_line_one", null: false diff --git a/lib/elastic_search_client.rb b/lib/elastic_search_client.rb new file mode 100644 index 000000000..176e89d03 --- /dev/null +++ b/lib/elastic_search_client.rb @@ -0,0 +1,52 @@ +class ElasticSearchClient + attr_reader :index, :server + + def initialize + @index = "rad_#{Rails.env}" + @server = ENV.fetch('BONSAI_URL', 'http://localhost:9200') + end + + def store(path, json) + log("PUT /#{path}\nRequest Body: #{json}") + + res = http.put(uri_for(path), JSON.generate(json)) + res.ok? + end + + def delete(path) + log("DELETE /#{path}") + + res = http.delete(uri_for(path)) + res.ok? + end + + private + + def http + @http ||= begin + HTTPClient.new.tap do |c| + c.set_auth(server, username, password) if authenticate? + end + end + end + + def log(message) + Rails.logger.debug("ElasticSearch Request: #{message}") + end + + def authenticate? + username && password + end + + def username + ENV['BONSAI_USERNAME'] + end + + def password + ENV['BONSAI_PASSWORD'] + end + + def uri_for(path) + "#{server}/#{index}/#{path}" + end +end diff --git a/lib/firm_indexer.rb b/lib/firm_indexer.rb new file mode 100644 index 000000000..03dc7dd95 --- /dev/null +++ b/lib/firm_indexer.rb @@ -0,0 +1,37 @@ +module FirmIndexer + class << self + def index_firm(firm) + if !firm.destroyed? && firm.publishable? + store_firm(firm) + else + delete_firm(firm) + end + end + + alias_method :handle_firm_changed, :index_firm + + def handle_aggregate_changed(aggregate) + # This method may be invoked as part of a cascade delete, in which case + # we should do nothing here. The firm change notification will handle + # the change. + return if associated_firm_destroyed?(aggregate) + index_firm(aggregate.firm) + end + + def associated_firm_destroyed?(aggregate) + firm = aggregate.firm + return true if (firm.nil? || firm.destroyed?) + !Firm.exists?(firm.id) + end + + private + + def store_firm(firm) + FirmRepository.new.store(firm) + end + + def delete_firm(firm) + FirmRepository.new.delete(firm.id) + end + end +end diff --git a/lib/firm_repository.rb b/lib/firm_repository.rb new file mode 100644 index 000000000..a96f8450f --- /dev/null +++ b/lib/firm_repository.rb @@ -0,0 +1,20 @@ +class FirmRepository + attr_reader :client, :serializer + + def initialize(client = ElasticSearchClient, serializer = FirmSerializer) + @client = client.new + @serializer = serializer + end + + def store(firm) + json = serializer.new(firm).as_json + path = "#{firm.model_name.plural}/#{firm.to_param}" + + client.store(path, json) + end + + def delete(id) + path = "#{Firm.model_name.plural}/#{id}" + client.delete(path) + end +end diff --git a/lib/languages.rb b/lib/languages.rb new file mode 100644 index 000000000..568f85b04 --- /dev/null +++ b/lib/languages.rb @@ -0,0 +1,8 @@ +module Languages + UK_MINORITY_LANGUAGES = %w(sco gd bfi isg).map { |l| LanguageList::LanguageInfo.find l } + EXCLUDED_LANGUAGES = %w(en).map { |l| LanguageList::LanguageInfo.find l } + AVAILABLE_LANGUAGES = ( + (LanguageList::COMMON_LANGUAGES + UK_MINORITY_LANGUAGES) - EXCLUDED_LANGUAGES + ).sort_by(&:common_name).freeze + AVAILABLE_LANGUAGES_ISO_639_3_CODES = Set.new(AVAILABLE_LANGUAGES.map(&:iso_639_3)).freeze +end diff --git a/lib/model_geocoder.rb b/lib/model_geocoder.rb new file mode 100644 index 000000000..52152cf1d --- /dev/null +++ b/lib/model_geocoder.rb @@ -0,0 +1,5 @@ +module ModelGeocoder + def self.geocode(geocodable) + Geocoder.coordinates(geocodable.full_street_address) + end +end diff --git a/lib/postcode.rb b/lib/postcode.rb new file mode 100644 index 000000000..7d9f3deb1 --- /dev/null +++ b/lib/postcode.rb @@ -0,0 +1,34 @@ +class Postcode + def filter_postcodes_by_country(postcodes, country) + map_postcodes_to_country(postcodes) + .select { |postcode, postcode_country| postcode_country == country } + .map { |postcode, postcode_country| postcode } + end + + private + + # Make sure we only request 100 at a time + def map_postcodes_to_country(postcodes) + postcodes + .uniq + .each_slice(100) + .map { |slice| map_postcodes_slice_to_country(slice) } + .reduce(&:merge) + end + + def map_postcodes_slice_to_country(postcodes) + request = Net::HTTP::Post.new('/postcodes') + request.set_form_data(postcodes: postcodes) + + response = Net::HTTP.new('api.postcodes.io').request(request) + + if response.code.to_i == 200 + result = JSON.parse(response.read_body)['result'].map { |r| r['result'] }.compact + result.each_with_object({}) do |r, obj| + obj[r['postcode']] = r['country'] + end + else + {} + end + end +end diff --git a/lib/stats.rb b/lib/stats.rb new file mode 100644 index 000000000..5cfd7463f --- /dev/null +++ b/lib/stats.rb @@ -0,0 +1,23 @@ +module Stats + def self.increment(*args) + client.increment(*args) if key + end + + def self.gauge(*args) + client.gauge(*args) if key + end + + def self.time(*args, &block) + key ? client.time(*args, &block) : block.call + end + + def self.client + $statsd ||= Statsd.new('statsd.hostedgraphite.com', 8125).tap do |n| + n.namespace = key + end + end + + def self.key + ENV['HOSTEDGRAPHITE_APIKEY'] + end +end diff --git a/spec/factories/accreditation.rb b/spec/factories/accreditation.rb new file mode 100644 index 000000000..d8802c71c --- /dev/null +++ b/spec/factories/accreditation.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :accreditation do + sequence(:name) { |n| "Accreditation #{n}" } + sequence(:order) { |n| n } + end +end diff --git a/spec/factories/adviser.rb b/spec/factories/adviser.rb new file mode 100644 index 000000000..1a3dd954c --- /dev/null +++ b/spec/factories/adviser.rb @@ -0,0 +1,27 @@ +FactoryGirl.define do + sequence(:reference_number, 10000) { |n| "ABC#{n}" } + + factory :adviser do + transient do + create_linked_lookup_advisor true + end + + reference_number + name { Faker::Name.name } + postcode { Faker::Address.postcode } + travel_distance '650' + latitude { Faker::Address.latitude.to_f.round(6) } + longitude { Faker::Address.longitude.to_f.round(6) } + firm factory: :firm_without_advisers + bypass_reference_number_check false + + after(:build) do |a, evaluator| + if a.reference_number? && evaluator.create_linked_lookup_advisor + Lookup::Adviser.create!( + reference_number: a.reference_number, + name: a.name + ) + end + end + end +end diff --git a/spec/factories/allowed_payment_method.rb b/spec/factories/allowed_payment_method.rb new file mode 100644 index 000000000..6e667958e --- /dev/null +++ b/spec/factories/allowed_payment_method.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :allowed_payment_method do + name { Faker::Lorem.sentence } + end +end diff --git a/spec/factories/firm.rb b/spec/factories/firm.rb new file mode 100644 index 000000000..b513320b7 --- /dev/null +++ b/spec/factories/firm.rb @@ -0,0 +1,98 @@ +FactoryGirl.define do + sequence(:registered_name) { |n| "Financial Advice #{n} Ltd." } + + factory :firm, aliases: [:publishable_firm, :onboarded_firm] do + fca_number + registered_name + website_address { Faker::Internet.url } + in_person_advice_methods { create_list(:in_person_advice_method, rand(1..3)) } + free_initial_meeting { [true, false].sample } + initial_meeting_duration { create(:initial_meeting_duration) } + initial_advice_fee_structures { create_list(:initial_advice_fee_structure, rand(1..3)) } + ongoing_advice_fee_structures { create_list(:ongoing_advice_fee_structure, rand(1..3)) } + allowed_payment_methods { create_list(:allowed_payment_method, rand(1..3)) } + investment_sizes { create_list(:investment_size, rand(5..10)) } + retirement_income_products_flag true + pension_transfer_flag true + long_term_care_flag true + equity_release_flag true + inheritance_tax_and_estate_planning_flag true + wills_and_probate_flag true + status :independent + + transient do + offices_count 1 + end + + after(:create) do |firm, evaluator| + create_list(:office, evaluator.offices_count, firm: firm) + firm.reload + end + + transient do + advisers_count 1 + end + + after(:create) do |firm, evaluator| + create_list(:adviser, evaluator.advisers_count, firm: firm) + end + + factory :trading_name, aliases: [:subsidiary] do + parent factory: :firm + end + + factory :firm_with_advisers, traits: [:with_advisers] + factory :firm_without_advisers, traits: [:without_advisers] + factory :firm_with_offices, traits: [:with_offices] + factory :firm_without_offices, traits: [:without_offices] + factory :firm_with_principal, traits: [:with_principal] + factory :firm_with_no_business_split, traits: [:with_no_business_split] + factory :firm_with_remote_advice, traits: [:with_remote_advice] + factory :firm_with_subsidiaries, traits: [:with_trading_names] + factory :firm_with_trading_names, traits: [:with_trading_names] + factory :invalid_firm, traits: [:invalid], aliases: [:not_onboarded_firm] + + trait :invalid do + # Invalidate the marker field without referencing it directly + __registered false + end + + trait :with_no_business_split do + retirement_income_products_flag false + pension_transfer_flag false + long_term_care_flag false + equity_release_flag false + inheritance_tax_and_estate_planning_flag false + wills_and_probate_flag false + end + + trait :with_advisers do + advisers_count 3 + end + + trait :without_advisers do + advisers_count 0 + end + + trait :with_principal do + principal { create(:principal) } + end + + trait :with_offices do + offices_count 3 + end + + trait :without_offices do + offices_count 0 + end + + trait :with_remote_advice do + other_advice_methods { create_list(:other_advice_method, rand(1..3)) } + in_person_advice_methods [] + end + + trait :with_trading_names do + subsidiaries { create_list(:trading_name, 3, fca_number: fca_number) } + end + end +end diff --git a/spec/factories/in_person_advice_method.rb b/spec/factories/in_person_advice_method.rb new file mode 100644 index 000000000..b6f88cf82 --- /dev/null +++ b/spec/factories/in_person_advice_method.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :in_person_advice_method do + sequence(:order) { |i| i } + name { Faker::Lorem.sentence } + end +end diff --git a/spec/factories/initial_advice_fee_structure.rb b/spec/factories/initial_advice_fee_structure.rb new file mode 100644 index 000000000..3f8e8a6d6 --- /dev/null +++ b/spec/factories/initial_advice_fee_structure.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :initial_advice_fee_structure do + name { Faker::Lorem.sentence } + end +end diff --git a/spec/factories/initial_meeting_duration.rb b/spec/factories/initial_meeting_duration.rb new file mode 100644 index 000000000..e7cfd4af1 --- /dev/null +++ b/spec/factories/initial_meeting_duration.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :initial_meeting_duration do + sequence(:name) { |n| (n * 15).to_s + ' mins' } + end +end diff --git a/spec/factories/investment_size.rb b/spec/factories/investment_size.rb new file mode 100644 index 000000000..b257cc3d3 --- /dev/null +++ b/spec/factories/investment_size.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :investment_size do + name { Faker::Lorem.sentence } + cy_name { Faker::Lorem.sentence } + end +end diff --git a/spec/factories/lookup_adviser.rb b/spec/factories/lookup_adviser.rb new file mode 100644 index 000000000..4a310b28c --- /dev/null +++ b/spec/factories/lookup_adviser.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :lookup_adviser, class: Lookup::Adviser do + reference_number { "XXX#{Faker::Number.number(5)}" } + name { Faker::Name.name } + end +end diff --git a/spec/factories/lookup_firm.rb b/spec/factories/lookup_firm.rb new file mode 100644 index 000000000..27ce876ed --- /dev/null +++ b/spec/factories/lookup_firm.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :lookup_firm, class: Lookup::Firm do + fca_number + registered_name { Faker::Company.name } + end +end diff --git a/spec/factories/lookup_subsidiary.rb b/spec/factories/lookup_subsidiary.rb new file mode 100644 index 000000000..f1f81ee6a --- /dev/null +++ b/spec/factories/lookup_subsidiary.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :lookup_subsidiary, class: Lookup::Subsidiary do + fca_number + name { Faker::Company.name } + end +end diff --git a/spec/factories/office.rb b/spec/factories/office.rb new file mode 100644 index 000000000..e4a3b3784 --- /dev/null +++ b/spec/factories/office.rb @@ -0,0 +1,14 @@ +FactoryGirl.define do + factory :office do + address_line_one { Faker::Address.street_address } + address_line_two { Faker::Address.secondary_address } + address_town { Faker::Address.city } + address_county { Faker::Address.state } + address_postcode 'EC1N 2TD' + email_address { Faker::Internet.email } + telephone_number '07111 333 222' + disabled_access { [true, false].sample } + latitude { Faker::Address.latitude.to_f.round(6) } + longitude { Faker::Address.longitude.to_f.round(6) } + end +end diff --git a/spec/factories/ongoing_advice_fee_structure.rb b/spec/factories/ongoing_advice_fee_structure.rb new file mode 100644 index 000000000..1b74fb5fc --- /dev/null +++ b/spec/factories/ongoing_advice_fee_structure.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :ongoing_advice_fee_structure do + name { Faker::Lorem.sentence } + end +end diff --git a/spec/factories/other_advice_method.rb b/spec/factories/other_advice_method.rb new file mode 100644 index 000000000..01136fa12 --- /dev/null +++ b/spec/factories/other_advice_method.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :other_advice_method do + name { Faker::Lorem.sentence } + cy_name { Faker::Lorem.sentence } + end +end diff --git a/spec/factories/principal.rb b/spec/factories/principal.rb new file mode 100644 index 000000000..8b619cddc --- /dev/null +++ b/spec/factories/principal.rb @@ -0,0 +1,15 @@ +FactoryGirl.define do + sequence(:fca_number, 100000) { |n| n } + + factory :principal do + fca_number + first_name { Faker::Name.first_name } + last_name { Faker::Name.last_name } + email_address { Faker::Internet.email(first_name) } + job_title { Faker::Name.title } + telephone_number '07111 333 222' + confirmed_disclaimer true + + after(:build) { |p| create(:lookup_firm, fca_number: p.fca_number) } + end +end diff --git a/spec/factories/professional_body.rb b/spec/factories/professional_body.rb new file mode 100644 index 000000000..32cac3f5c --- /dev/null +++ b/spec/factories/professional_body.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :professional_body do + sequence(:name) { |n| "Professional Body #{n}" } + sequence(:order) { |n| n - 1 } + end +end diff --git a/spec/factories/professional_standing.rb b/spec/factories/professional_standing.rb new file mode 100644 index 000000000..ff6f50026 --- /dev/null +++ b/spec/factories/professional_standing.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :professional_standing do + sequence(:name) { |n| "Professional Standing #{n}" } + sequence(:order) { |n| n - 1 } + end +end diff --git a/spec/factories/qualification.rb b/spec/factories/qualification.rb new file mode 100644 index 000000000..328f256eb --- /dev/null +++ b/spec/factories/qualification.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :qualification do + sequence(:name) { |n| "Qualification #{n}" } + sequence(:order) { |n| n } + end +end diff --git a/spec/fixtures/vcr_cassettes/england_and_scotland_postcode.yml b/spec/fixtures/vcr_cassettes/england_and_scotland_postcode.yml new file mode 100644 index 000000000..835497c20 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/england_and_scotland_postcode.yml @@ -0,0 +1,64 @@ +--- +http_interactions: +- request: + method: post + uri: http://api.postcodes.io/postcodes + body: + encoding: US-ASCII + string: postcodes=EC1N+2TD&postcodes=EC1N+2TD&postcodes=EH3+9DR&postcodes=EH3+9DR + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + Content-Type: + - application/x-www-form-urlencoded + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.8.0 + Date: + - Mon, 15 Feb 2016 15:50:03 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '2993' + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Origin + Etag: + - W/"bb1-IpjaBdaxzeHtBO0KQVrROA" + body: + encoding: UTF-8 + string: '{"status":200,"result":[{"query":"EC1N 2TD","result":{"postcode":"EC1N + 2TD","quality":1,"eastings":531336,"northings":181601,"country":"England","nhs_ha":"London","longitude":-0.108520296477884,"latitude":51.518069864138,"parliamentary_constituency":"Holborn + and St Pancras","european_electoral_region":"London","primary_care_trust":"Camden","region":"London","lsoa":"Camden + 027C","msoa":"Camden 027","incode":"2TD","outcode":"EC1N","admin_district":"Camden","parish":"Camden, + unparished area","admin_county":null,"admin_ward":"Holborn and Covent Garden","ccg":"NHS + Camden","nuts":"Camden and City of London","codes":{"admin_district":"E09000007","admin_county":"E99999999","admin_ward":"E05000138","parish":"E43000197","ccg":"E38000027","nuts":"UKI31"}}},{"query":"EC1N + 2TD","result":{"postcode":"EC1N 2TD","quality":1,"eastings":531336,"northings":181601,"country":"England","nhs_ha":"London","longitude":-0.108520296477884,"latitude":51.518069864138,"parliamentary_constituency":"Holborn + and St Pancras","european_electoral_region":"London","primary_care_trust":"Camden","region":"London","lsoa":"Camden + 027C","msoa":"Camden 027","incode":"2TD","outcode":"EC1N","admin_district":"Camden","parish":"Camden, + unparished area","admin_county":null,"admin_ward":"Holborn and Covent Garden","ccg":"NHS + Camden","nuts":"Camden and City of London","codes":{"admin_district":"E09000007","admin_county":"E99999999","admin_ward":"E05000138","parish":"E43000197","ccg":"E38000027","nuts":"UKI31"}}},{"query":"EH3 + 9DR","result":{"postcode":"EH3 9DR","quality":1,"eastings":325045,"northings":673308,"country":"Scotland","nhs_ha":"Lothian","longitude":-3.20175224339705,"latitude":55.9469462655015,"parliamentary_constituency":"Edinburgh + East","european_electoral_region":"Scotland","primary_care_trust":"Edinburgh + Community Health Partnership","region":null,"lsoa":"Tollcross - 03","msoa":"Tollcross","incode":"9DR","outcode":"EH3","admin_district":"City + of Edinburgh","parish":null,"admin_county":null,"admin_ward":"City Centre","ccg":"Edinburgh + Community Health Partnership","nuts":"Edinburgh, City of","codes":{"admin_district":"S12000036","admin_county":"S99999999","admin_ward":"S13002593","parish":"S99999999","ccg":"S03000042","nuts":"UKM25"}}},{"query":"EH3 + 9DR","result":{"postcode":"EH3 9DR","quality":1,"eastings":325045,"northings":673308,"country":"Scotland","nhs_ha":"Lothian","longitude":-3.20175224339705,"latitude":55.9469462655015,"parliamentary_constituency":"Edinburgh + East","european_electoral_region":"Scotland","primary_care_trust":"Edinburgh + Community Health Partnership","region":null,"lsoa":"Tollcross - 03","msoa":"Tollcross","incode":"9DR","outcode":"EH3","admin_district":"City + of Edinburgh","parish":null,"admin_county":null,"admin_ward":"City Centre","ccg":"Edinburgh + Community Health Partnership","nuts":"Edinburgh, City of","codes":{"admin_district":"S12000036","admin_county":"S99999999","admin_ward":"S13002593","parish":"S99999999","ccg":"S03000042","nuts":"UKM25"}}}]}' + http_version: + recorded_at: Mon, 15 Feb 2016 15:50:03 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/vcr_cassettes/geocode-no-results.yml b/spec/fixtures/vcr_cassettes/geocode-no-results.yml new file mode 100644 index 000000000..eda9c9f32 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/geocode-no-results.yml @@ -0,0 +1,54 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=1000%20Fantasy%20Ave,%20Neverland,%20ABC%20123,%20United%20Kingdom&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Wed, 18 Feb 2015 13:23:38 GMT + Expires: + - Thu, 19 Feb 2015 13:23:38 GMT + Cache-Control: + - public, max-age=86400 + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + X-Xss-Protection: + - 1; mode=block + X-Frame-Options: + - SAMEORIGIN + Alternate-Protocol: + - 80:quic,p=0.08 + Accept-Ranges: + - none + Vary: + - Accept-Encoding + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: | + { + "results" : [], + "status" : "ZERO_RESULTS" + } + http_version: + recorded_at: Wed, 18 Feb 2015 13:23:38 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/vcr_cassettes/geocode-one-result.yml b/spec/fixtures/vcr_cassettes/geocode-one-result.yml new file mode 100644 index 000000000..fb15e786a --- /dev/null +++ b/spec/fixtures/vcr_cassettes/geocode-one-result.yml @@ -0,0 +1,114 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=120%20Holborn,%20London,%20EC1N%202TD,%20United%20Kingdom&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Wed, 18 Feb 2015 13:23:38 GMT + Expires: + - Thu, 19 Feb 2015 13:23:38 GMT + Cache-Control: + - public, max-age=86400 + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + X-Xss-Protection: + - 1; mode=block + X-Frame-Options: + - SAMEORIGIN + Alternate-Protocol: + - 80:quic,p=0.08 + Accept-Ranges: + - none + Vary: + - Accept-Encoding + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: | + { + "results" : [ + { + "address_components" : [ + { + "long_name" : "120", + "short_name" : "120", + "types" : [ "street_number" ] + }, + { + "long_name" : "Holborn", + "short_name" : "Holborn", + "types" : [ "route" ] + }, + { + "long_name" : "London", + "short_name" : "London", + "types" : [ "locality", "political" ] + }, + { + "long_name" : "London", + "short_name" : "London", + "types" : [ "postal_town" ] + }, + { + "long_name" : "Greater London", + "short_name" : "Gt Lon", + "types" : [ "administrative_area_level_2", "political" ] + }, + { + "long_name" : "United Kingdom", + "short_name" : "GB", + "types" : [ "country", "political" ] + }, + { + "long_name" : "EC1N 2TD", + "short_name" : "EC1N 2TD", + "types" : [ "postal_code" ] + } + ], + "formatted_address" : "120 Holborn, London EC1N 2TD, UK", + "geometry" : { + "location" : { + "lat" : 51.5180697, + "lng" : -0.1085203 + }, + "location_type" : "ROOFTOP", + "viewport" : { + "northeast" : { + "lat" : 51.51941868029149, + "lng" : -0.107171319708498 + }, + "southwest" : { + "lat" : 51.51672071970849, + "lng" : -0.109869280291502 + } + } + }, + "place_id" : "ChIJ1y3irE0bdkgRz3w__Q4S9sI", + "types" : [ "street_address" ] + } + ], + "status" : "OK" + } + http_version: + recorded_at: Wed, 18 Feb 2015 13:23:38 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/vcr_cassettes/northern_ireland_and_england_postcode.yml b/spec/fixtures/vcr_cassettes/northern_ireland_and_england_postcode.yml new file mode 100644 index 000000000..e70a1b5fb --- /dev/null +++ b/spec/fixtures/vcr_cassettes/northern_ireland_and_england_postcode.yml @@ -0,0 +1,57 @@ +--- +http_interactions: +- request: + method: post + uri: http://api.postcodes.io/postcodes + body: + encoding: US-ASCII + string: postcodes=BT1+6DP&postcodes=BT1+6DP&postcodes=EC1N+2TD + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + Content-Type: + - application/x-www-form-urlencoded + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.8.0 + Date: + - Mon, 15 Feb 2016 16:55:48 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '2131' + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Origin + Etag: + - W/"853-M2jj/FPZ6cgA9ZP5SP2XtQ" + body: + encoding: UTF-8 + string: '{"status":200,"result":[{"query":"BT1 6DP","result":{"postcode":"BT1 + 6DP","quality":1,"eastings":333445,"northings":373966,"country":"Northern + Ireland","nhs_ha":"Health & Social Care Board","longitude":-5.93609062723222,"latitude":54.5963067969602,"parliamentary_constituency":"Belfast + South","european_electoral_region":"Northern Ireland","primary_care_trust":"Belfast","region":null,"lsoa":"Shaftesbury + 1","msoa":null,"incode":"6DP","outcode":"BT1","admin_district":"Belfast","parish":null,"admin_county":null,"admin_ward":"Central","ccg":"Belfast","nuts":"Belfast","codes":{"admin_district":"N09000003","admin_county":"N99999999","admin_ward":"N08000315","parish":"N99999999","ccg":"ZC010","nuts":"UKN01"}}},{"query":"BT1 + 6DP","result":{"postcode":"BT1 6DP","quality":1,"eastings":333445,"northings":373966,"country":"Northern + Ireland","nhs_ha":"Health & Social Care Board","longitude":-5.93609062723222,"latitude":54.5963067969602,"parliamentary_constituency":"Belfast + South","european_electoral_region":"Northern Ireland","primary_care_trust":"Belfast","region":null,"lsoa":"Shaftesbury + 1","msoa":null,"incode":"6DP","outcode":"BT1","admin_district":"Belfast","parish":null,"admin_county":null,"admin_ward":"Central","ccg":"Belfast","nuts":"Belfast","codes":{"admin_district":"N09000003","admin_county":"N99999999","admin_ward":"N08000315","parish":"N99999999","ccg":"ZC010","nuts":"UKN01"}}},{"query":"EC1N + 2TD","result":{"postcode":"EC1N 2TD","quality":1,"eastings":531336,"northings":181601,"country":"England","nhs_ha":"London","longitude":-0.108520296477884,"latitude":51.518069864138,"parliamentary_constituency":"Holborn + and St Pancras","european_electoral_region":"London","primary_care_trust":"Camden","region":"London","lsoa":"Camden + 027C","msoa":"Camden 027","incode":"2TD","outcode":"EC1N","admin_district":"Camden","parish":"Camden, + unparished area","admin_county":null,"admin_ward":"Holborn and Covent Garden","ccg":"NHS + Camden","nuts":"Camden and City of London","codes":{"admin_district":"E09000007","admin_county":"E99999999","admin_ward":"E05000138","parish":"E43000197","ccg":"E38000027","nuts":"UKI31"}}}]}' + http_version: + recorded_at: Mon, 15 Feb 2016 16:55:48 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/vcr_cassettes/postcode-no-results.yml b/spec/fixtures/vcr_cassettes/postcode-no-results.yml new file mode 100644 index 000000000..acd7b5828 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/postcode-no-results.yml @@ -0,0 +1,54 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=ABC%20123,%20United%20Kingdom&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Mon, 23 Feb 2015 10:25:12 GMT + Expires: + - Tue, 24 Feb 2015 10:25:12 GMT + Cache-Control: + - public, max-age=86400 + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + X-Xss-Protection: + - 1; mode=block + X-Frame-Options: + - SAMEORIGIN + Alternate-Protocol: + - 80:quic,p=0.08 + Accept-Ranges: + - none + Vary: + - Accept-Encoding + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: | + { + "results" : [], + "status" : "ZERO_RESULTS" + } + http_version: + recorded_at: Mon, 23 Feb 2015 10:25:12 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/vcr_cassettes/postcode-one-result.yml b/spec/fixtures/vcr_cassettes/postcode-one-result.yml new file mode 100644 index 000000000..ea0ea066b --- /dev/null +++ b/spec/fixtures/vcr_cassettes/postcode-one-result.yml @@ -0,0 +1,114 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=EC1N%202TD,%20United%20Kingdom&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Mon, 23 Feb 2015 10:26:05 GMT + Expires: + - Tue, 24 Feb 2015 10:26:05 GMT + Cache-Control: + - public, max-age=86400 + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + X-Xss-Protection: + - 1; mode=block + X-Frame-Options: + - SAMEORIGIN + Alternate-Protocol: + - 80:quic,p=0.08 + Accept-Ranges: + - none + Vary: + - Accept-Encoding + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: | + { + "results" : [ + { + "address_components" : [ + { + "long_name" : "EC1N 2TD", + "short_name" : "EC1N 2TD", + "types" : [ "postal_code" ] + }, + { + "long_name" : "London", + "short_name" : "London", + "types" : [ "locality", "political" ] + }, + { + "long_name" : "London", + "short_name" : "London", + "types" : [ "postal_town" ] + }, + { + "long_name" : "Greater London", + "short_name" : "Gt Lon", + "types" : [ "administrative_area_level_2", "political" ] + }, + { + "long_name" : "United Kingdom", + "short_name" : "GB", + "types" : [ "country", "political" ] + } + ], + "formatted_address" : "London EC1N 2TD, UK", + "geometry" : { + "bounds" : { + "northeast" : { + "lat" : 51.5182639, + "lng" : -0.1078729 + }, + "southwest" : { + "lat" : 51.5173261, + "lng" : -0.1094075 + } + }, + "location" : { + "lat" : 51.5180697, + "lng" : -0.1085203 + }, + "location_type" : "APPROXIMATE", + "viewport" : { + "northeast" : { + "lat" : 51.5191439802915, + "lng" : -0.107291219708498 + }, + "southwest" : { + "lat" : 51.5164460197085, + "lng" : -0.109989180291502 + } + } + }, + "place_id" : "ChIJ5_7trE0bdkgRKFWaw55y3rM", + "types" : [ "postal_code" ] + } + ], + "status" : "OK" + } + http_version: + recorded_at: Mon, 23 Feb 2015 10:26:05 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/vcr_cassettes/scotland_and_england_postcode.yml b/spec/fixtures/vcr_cassettes/scotland_and_england_postcode.yml new file mode 100644 index 000000000..ef0d270f8 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/scotland_and_england_postcode.yml @@ -0,0 +1,59 @@ +--- +http_interactions: +- request: + method: post + uri: http://api.postcodes.io/postcodes + body: + encoding: US-ASCII + string: postcodes=EH3+9DR&postcodes=EH3+9DR&postcodes=EC1N+2TD + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + Content-Type: + - application/x-www-form-urlencoded + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.8.0 + Date: + - Mon, 15 Feb 2016 16:53:40 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '2261' + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Origin + Etag: + - W/"8d5-WxlA2olmHaAkEIQ7Jw6olg" + body: + encoding: UTF-8 + string: '{"status":200,"result":[{"query":"EH3 9DR","result":{"postcode":"EH3 + 9DR","quality":1,"eastings":325045,"northings":673308,"country":"Scotland","nhs_ha":"Lothian","longitude":-3.20175224339705,"latitude":55.9469462655015,"parliamentary_constituency":"Edinburgh + East","european_electoral_region":"Scotland","primary_care_trust":"Edinburgh + Community Health Partnership","region":null,"lsoa":"Tollcross - 03","msoa":"Tollcross","incode":"9DR","outcode":"EH3","admin_district":"City + of Edinburgh","parish":null,"admin_county":null,"admin_ward":"City Centre","ccg":"Edinburgh + Community Health Partnership","nuts":"Edinburgh, City of","codes":{"admin_district":"S12000036","admin_county":"S99999999","admin_ward":"S13002593","parish":"S99999999","ccg":"S03000042","nuts":"UKM25"}}},{"query":"EH3 + 9DR","result":{"postcode":"EH3 9DR","quality":1,"eastings":325045,"northings":673308,"country":"Scotland","nhs_ha":"Lothian","longitude":-3.20175224339705,"latitude":55.9469462655015,"parliamentary_constituency":"Edinburgh + East","european_electoral_region":"Scotland","primary_care_trust":"Edinburgh + Community Health Partnership","region":null,"lsoa":"Tollcross - 03","msoa":"Tollcross","incode":"9DR","outcode":"EH3","admin_district":"City + of Edinburgh","parish":null,"admin_county":null,"admin_ward":"City Centre","ccg":"Edinburgh + Community Health Partnership","nuts":"Edinburgh, City of","codes":{"admin_district":"S12000036","admin_county":"S99999999","admin_ward":"S13002593","parish":"S99999999","ccg":"S03000042","nuts":"UKM25"}}},{"query":"EC1N + 2TD","result":{"postcode":"EC1N 2TD","quality":1,"eastings":531336,"northings":181601,"country":"England","nhs_ha":"London","longitude":-0.108520296477884,"latitude":51.518069864138,"parliamentary_constituency":"Holborn + and St Pancras","european_electoral_region":"London","primary_care_trust":"Camden","region":"London","lsoa":"Camden + 027C","msoa":"Camden 027","incode":"2TD","outcode":"EC1N","admin_district":"Camden","parish":"Camden, + unparished area","admin_county":null,"admin_ward":"Holborn and Covent Garden","ccg":"NHS + Camden","nuts":"Camden and City of London","codes":{"admin_district":"E09000007","admin_county":"E99999999","admin_ward":"E05000138","parish":"E43000197","ccg":"E38000027","nuts":"UKI31"}}}]}' + http_version: + recorded_at: Mon, 15 Feb 2016 16:53:40 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/vcr_cassettes/wales_and_england_postcode.yml b/spec/fixtures/vcr_cassettes/wales_and_england_postcode.yml new file mode 100644 index 000000000..9485fb854 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/wales_and_england_postcode.yml @@ -0,0 +1,59 @@ +--- +http_interactions: +- request: + method: post + uri: http://api.postcodes.io/postcodes + body: + encoding: US-ASCII + string: postcodes=CF14+4HY&postcodes=CF14+4HY&postcodes=EC1N+2TD + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + Content-Type: + - application/x-www-form-urlencoded + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.8.0 + Date: + - Mon, 15 Feb 2016 16:53:45 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '2271' + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Origin + Etag: + - W/"8df-XlM0MGB7muOz7ggKdTCdCw" + body: + encoding: UTF-8 + string: '{"status":200,"result":[{"query":"CF14 4HY","result":{"postcode":"CF14 + 4HY","quality":1,"eastings":317208,"northings":180914,"country":"Wales","nhs_ha":"Cardiff + and Vale University Health Board","longitude":-3.19467705718916,"latitude":51.5210575574106,"parliamentary_constituency":"Cardiff + North","european_electoral_region":"Wales","primary_care_trust":"Cardiff and + Vale University","region":null,"lsoa":"Cardiff 018C","msoa":"Cardiff 018","incode":"4HY","outcode":"CF14","admin_district":"Cardiff","parish":"Heath","admin_county":null,"admin_ward":"Heath","ccg":"Cardiff + and Vale University","nuts":"Cardiff and Vale of Glamorgan","codes":{"admin_district":"W06000015","admin_county":"W99999999","admin_ward":"W05000864","parish":"W04000848","ccg":"W11000029","nuts":"UKL22"}}},{"query":"EC1N + 2TD","result":{"postcode":"EC1N 2TD","quality":1,"eastings":531336,"northings":181601,"country":"England","nhs_ha":"London","longitude":-0.108520296477884,"latitude":51.518069864138,"parliamentary_constituency":"Holborn + and St Pancras","european_electoral_region":"London","primary_care_trust":"Camden","region":"London","lsoa":"Camden + 027C","msoa":"Camden 027","incode":"2TD","outcode":"EC1N","admin_district":"Camden","parish":"Camden, + unparished area","admin_county":null,"admin_ward":"Holborn and Covent Garden","ccg":"NHS + Camden","nuts":"Camden and City of London","codes":{"admin_district":"E09000007","admin_county":"E99999999","admin_ward":"E05000138","parish":"E43000197","ccg":"E38000027","nuts":"UKI31"}}},{"query":"CF14 + 4HY","result":{"postcode":"CF14 4HY","quality":1,"eastings":317208,"northings":180914,"country":"Wales","nhs_ha":"Cardiff + and Vale University Health Board","longitude":-3.19467705718916,"latitude":51.5210575574106,"parliamentary_constituency":"Cardiff + North","european_electoral_region":"Wales","primary_care_trust":"Cardiff and + Vale University","region":null,"lsoa":"Cardiff 018C","msoa":"Cardiff 018","incode":"4HY","outcode":"CF14","admin_district":"Cardiff","parish":"Heath","admin_county":null,"admin_ward":"Heath","ccg":"Cardiff + and Vale University","nuts":"Cardiff and Vale of Glamorgan","codes":{"admin_district":"W06000015","admin_county":"W99999999","admin_ward":"W05000864","parish":"W04000848","ccg":"W11000029","nuts":"UKL22"}}}]}' + http_version: + recorded_at: Mon, 15 Feb 2016 16:53:45 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/lib/firm_indexer_spec.rb b/spec/lib/firm_indexer_spec.rb new file mode 100644 index 000000000..60c8d410a --- /dev/null +++ b/spec/lib/firm_indexer_spec.rb @@ -0,0 +1,123 @@ +RSpec.describe FirmIndexer do + let(:firm_repo_instance) { double } + before do + allow(FirmRepository).to receive(:new).and_return(firm_repo_instance) + end + + def expect_store + expect(firm_repo_instance).to receive(:store).with(firm) + end + + def expect_delete + expect(firm_repo_instance).to receive(:delete).with(firm.id) + end + + def expect_no_action + expect(firm_repo_instance).to receive(:store).with(firm).exactly(0).times + expect(firm_repo_instance).to receive(:delete).with(firm.id).exactly(0).times + end + + describe '#index_firm' do + subject { described_class.index_firm(firm) } + let(:firm) { FactoryGirl.create(:publishable_firm) } + + context 'when the firm is publishable' do + it 'stores the firm in the index' do + expect_store + subject + end + end + + context 'when the firm is not publishable' do + let(:firm) { FactoryGirl.create(:firm_without_offices, :without_advisers) } + + it 'attempts to remove the firm from the index in case it was previously published' do + expect_delete + subject + end + end + + context 'when the firm has been destroyed' do + it 'deletes the firm from the index' do + expect_delete + firm.destroy + subject + end + end + end + + describe '#handle_firm_changed' do + let(:handle_firm_changed) { described_class.method(:handle_firm_changed) } + let(:index_firm) { described_class.method(:index_firm) } + + it 'delegates to #index_firm' do + expect(handle_firm_changed).to eq(index_firm) + end + end + + describe '#handle_aggregate_changed' do + let(:firm) { FactoryGirl.create(:firm_with_offices) } + let!(:aggregate) { firm.offices.first } + subject { described_class.handle_aggregate_changed(aggregate) } + + context 'when the aggregate record has changed' do + it 'stores the firm in the index' do + aggregate.update!(address_line_one: 'A change of address') + expect_store + subject + end + end + + context 'when the aggregate record has been destroyed' do + it 'stores the firm in the index' do + aggregate.destroy + expect_store + subject + end + end + + context 'when the aggregate record\'s firm has been destroyed' do + it 'does nothing' do + firm.destroy + expect_no_action + subject + end + end + end + + describe '#associated_firm_destroyed?' do + let(:firm) { FactoryGirl.create(:firm_with_offices) } + let(:aggregate) { firm.offices.first } + subject { described_class.associated_firm_destroyed?(aggregate) } + + context 'when the firm instance and db record exist' do + it { is_expected.to be(false) } + end + + context 'when firm has been destroyed before the association has been loaded' do + before do + aggregate # load the aggregate, but not the aggregate.firm association + firm.destroy + expect(aggregate.firm).to be_nil + end + + it { is_expected.to be(true) } + end + + context 'when the aggregate.firm instance has been destroyed' do + before { aggregate.firm.destroy } + it { is_expected.to be(true) } + end + + context 'when another instance of this firm has been destroyed' do + let(:other_instance) { Firm.find(firm.id) } + + before do + aggregate.firm # Load the aggregate.firm instance into memory + other_instance.destroy + end + + it { is_expected.to be(true) } + end + end +end diff --git a/spec/lib/firm_repository_spec.rb b/spec/lib/firm_repository_spec.rb new file mode 100644 index 000000000..926478377 --- /dev/null +++ b/spec/lib/firm_repository_spec.rb @@ -0,0 +1,34 @@ +RSpec.describe FirmRepository do + let(:client) { double } + let(:client_class) { double(new: client) } + + describe 'initialization' do + subject { described_class.new } + + it 'defaults `client`' do + expect(subject.client).to be_a(ElasticSearchClient) + end + + it 'defaults `serializer`' do + expect(subject.serializer).to eql(FirmSerializer) + end + end + + describe '#store' do + let(:firm) { create(:firm) } + + it 'delegates to the configured client' do + expect(client).to receive(:store).with(/firms\/\d+/, hash_including(:_id)) + + described_class.new(client_class).store(firm) + end + end + + describe '#delete' do + it 'delegates to the configured client' do + expect(client).to receive(:delete).with("firms/1") + + described_class.new(client_class).delete(1) + end + end +end diff --git a/spec/lib/languages_spec.rb b/spec/lib/languages_spec.rb new file mode 100644 index 000000000..9a00287c8 --- /dev/null +++ b/spec/lib/languages_spec.rb @@ -0,0 +1,20 @@ +RSpec.describe Languages do + describe '::AVAILABLE_LANGUAGES' do + it 'returns a list of languages' do + expect(Languages::AVAILABLE_LANGUAGES).to have(74).languages + end + + it 'does not include English' do + expect(Languages::AVAILABLE_LANGUAGES).to_not include LanguageList::LanguageInfo.find('en') + end + + it 'does include minority languages' do + expect(Languages::AVAILABLE_LANGUAGES).to include LanguageList::LanguageInfo.find('sco') + end + + it 'sorts them by common English name' do + sorted_languages = Languages::AVAILABLE_LANGUAGES.sort_by(&:common_name) + expect(Languages::AVAILABLE_LANGUAGES).to eq(sorted_languages) + end + end +end diff --git a/spec/lib/model_geocoder_spec.rb b/spec/lib/model_geocoder_spec.rb new file mode 100644 index 000000000..eedcf33b4 --- /dev/null +++ b/spec/lib/model_geocoder_spec.rb @@ -0,0 +1,46 @@ +RSpec.describe ModelGeocoder do + let(:model_class) do + Class.new do + attr_accessor :address_line_one, :address_line_two, :address_postcode + + def full_street_address + [address_line_one, address_line_two, address_postcode, 'United Kingdom'].reject(&:blank?).join(', ') + end + end + end + + let(:model) do + model_class.new.tap do |thing| + thing.address_line_one = address_line_one + thing.address_line_two = address_line_two + thing.address_postcode = address_postcode + end + end + + let(:address_line_one) { '120 Holborn' } + let(:address_line_two) { 'London' } + let(:address_postcode) { 'EC1N 2TD' } + let(:expected_coordinates) { [51.5180697, -0.1085203] } + + describe '#geocode' do + context 'when the model address can be geocoded' do + it 'returns the coordinates' do + VCR.use_cassette('geocode-one-result') do + expect(ModelGeocoder.geocode(model)).to eql(expected_coordinates) + end + end + end + + context 'when model address cannot be geocoded' do + let(:address_line_one) { '1000 Fantasy Ave' } + let(:address_line_two) { 'Neverland' } + let(:address_postcode) { 'ABC 123' } + + it 'returns nil' do + VCR.use_cassette('geocode-no-results') do + expect(ModelGeocoder.geocode(model)).to be(nil) + end + end + end + end +end diff --git a/spec/models/accreditation_spec.rb b/spec/models/accreditation_spec.rb new file mode 100644 index 000000000..fdc9e5b7f --- /dev/null +++ b/spec/models/accreditation_spec.rb @@ -0,0 +1,4 @@ +RSpec.describe Accreditation do + it_behaves_like 'reference data' + it_behaves_like 'friendly named' +end diff --git a/spec/models/adviser_spec.rb b/spec/models/adviser_spec.rb new file mode 100644 index 000000000..facf5f4c2 --- /dev/null +++ b/spec/models/adviser_spec.rb @@ -0,0 +1,285 @@ +RSpec.describe Adviser do + describe 'before validation' do + context 'when a reference number is present' do + let(:attributes) { attributes_for(:adviser) } + let(:adviser) { Adviser.new(attributes) } + + before do + Lookup::Adviser.create!( + reference_number: attributes[:reference_number], + name: 'Mr. Welp' + ) + end + + context 'when a name is not present' do + before do + adviser.name = nil + end + + it 'assigns #name from the lookup Adviser data' do + adviser.validate + + expect(adviser.name).to eq('Mr. Welp') + end + end + + context 'when a name is present' do + it 'does not override the existing name' do + adviser.validate + + expect(adviser.name).not_to eq('Mr. Welp') + end + end + end + end + + describe 'validation' do + it 'is valid with valid attributes' do + expect(build(:adviser)).to be_valid + end + + it 'orders fields correctly for dough' do + expect(build(:adviser).field_order).not_to be_empty + end + + describe 'geographical coverage' do + describe 'travel distance' do + it 'must be provided' do + expect(build(:adviser, travel_distance: nil)).to_not be_valid + end + + it 'must be within the allowed options' do + expect(build(:adviser, travel_distance: 999)).to_not be_valid + end + end + + describe 'postcode' do + it 'must be provided' do + expect(build(:adviser, postcode: nil)).to_not be_valid + end + + it 'must be a valid format' do + expect(build(:adviser, postcode: 'Z')).to_not be_valid + end + end + end + + describe 'reference number' do + context 'when `bypass_reference_number_check` is true' do + it 'is not required' do + expect(build(:adviser, bypass_reference_number_check: true, reference_number: nil)).to be_valid + end + end + + context 'when `bypass_reference_number_check` is false' do + it 'is required' do + expect(build(:adviser, reference_number: nil)).to_not be_valid + end + + it 'must be three characters and five digits exactly' do + %w(badtimes ABCDEFGH 8008135! 12345678).each do |bad| + Lookup::Adviser.create!(reference_number: bad, name: 'Mr. Derp') + + expect(build(:adviser, + reference_number: bad, + create_linked_lookup_advisor: false)).to_not be_valid + end + end + + it 'must be matched to the lookup data' do + build(:adviser, reference_number: 'ABC12345').tap do |a| + Lookup::Adviser.delete_all + + expect(a).to_not be_valid + end + end + + context 'when an adviser with the same reference number already exists' do + let(:reference_number) { 'ABC12345' } + + before do + create(:adviser, reference_number: reference_number) + end + + it 'must not be valid' do + expect(build(:adviser, + reference_number: reference_number, + create_linked_lookup_advisor: false)).to_not be_valid + end + end + end + end + end + + describe '#full_street_address' do + let(:adviser) { create(:adviser) } + subject { adviser.full_street_address } + + it { is_expected.to eql "#{adviser.postcode}, United Kingdom"} + end + + it_should_behave_like 'geocodable' do + let(:invalid_geocodable) { Adviser.new } + let(:valid_new_geocodable) { FactoryGirl.build(:adviser) } + let(:saved_geocodable) { FactoryGirl.create(:adviser) } + let(:address_field_name) { :postcode } + let(:address_field_updated_value) { 'S032 2AY' } + let(:updated_address_params) { { address_field_name => address_field_updated_value } } + end + + describe '#has_address_changes?' do + subject { FactoryGirl.create(:adviser) } + + context 'when none of the address fields have changed' do + it 'returns false' do + expect(subject.has_address_changes?).to be(false) + end + end + + context "when the model postcode field has changed" do + before do + subject.postcode = 'S032 2AY' + end + + it 'returns true' do + expect(subject.has_address_changes?).to be(true) + end + end + end + + describe '#notify_indexer' do + subject { FactoryGirl.create(:adviser) } + + it 'notifies the indexer that the office has changed' do + expect(FirmIndexer).to receive(:handle_aggregate_changed).with(subject) + subject.notify_indexer + end + end + + describe 'after_save :flag_changes_for_after_commit' do + let(:original_firm) { create(:firm) } + let(:receiving_firm) { create(:firm) } + subject { create(:adviser, firm: original_firm) } + + before do + subject.firm = receiving_firm + subject.save! + end + + context 'when the firm has changed' do + it 'stores the original firm id so it can be reindexed in an after_commit hook' do + expect(subject.old_firm_id).to eq(original_firm.id) + end + end + end + + describe 'after_commit :reindex_old_firm' do + let(:original_firm) { create(:firm) } + let(:receiving_firm) { create(:firm) } + subject { create(:adviser, firm: original_firm) } + + def save_with_commit_callback(model) + model.save! + model.run_callbacks(:commit) + end + + before { allow(FirmIndexer).to receive(:handle_aggregate_changed) } + + context 'when the associated firm has changed' do + it 'triggers reindexing of the original firm' do + expect(FirmIndexer).to receive(:handle_firm_changed).with(original_firm) + subject.firm = receiving_firm + save_with_commit_callback(subject) + end + end + + context 'when the associated firm has not changed' do + it 'does not trigger reindexing of the original firm' do + expect(FirmIndexer).not_to receive(:handle_firm_changed).with(original_firm) + subject.name = 'A different name' + save_with_commit_callback(subject) + end + end + end + + describe '.move_all_to_firm' do + let(:original_firm) { create(:firm_with_advisers) } + let(:receiving_firm) { create(:firm_with_advisers) } + + it 'moves a batch of advisers to another firm' do + advisers_to_move = original_firm.advisers.limit(2) + advisers_to_move.move_all_to_firm(receiving_firm) + + expect(advisers_to_move[0].firm).to be(receiving_firm) + expect(advisers_to_move[1].firm).to be(receiving_firm) + + receiving_firm.reload + original_firm.reload + + expect(original_firm.advisers.count).to be(1) + expect(receiving_firm.advisers.count).to be(5) + expect(receiving_firm.adviser_ids).to include(advisers_to_move[0].id, + advisers_to_move[1].id) + expect(original_firm.adviser_ids).not_to include(advisers_to_move[0].id, + advisers_to_move[1].id) + end + + context 'when one of the move operations fails' do + let(:advisers_to_move) { original_firm.advisers.limit(3) } + let(:invalid_record_index) { 1 } + + before do + advisers_to_move[invalid_record_index].reference_number = 'NOT_VALID' + advisers_to_move[invalid_record_index].save!(validate: false) + end + + it 'aborts the entire operation' do + expect(advisers_to_move[invalid_record_index]).not_to be_valid + expect { advisers_to_move.move_all_to_firm(receiving_firm) } + .to raise_error(ActiveRecord::RecordInvalid) + + receiving_firm.reload + original_firm.reload + + expect(original_firm.advisers.count).to be(3) + expect(receiving_firm.advisers.count).to be(3) + end + end + end + + describe '#on_firms_with_fca_number' do + it 'returns advisers on firm and its trading names' do + firm = FactoryGirl.create(:firm_with_advisers, advisers_count: 1) + trading_name = FactoryGirl.create(:trading_name, + :with_advisers, + advisers_count: 1, + fca_number: firm.fca_number) + advisers = [firm.advisers.first, trading_name.advisers.first] + + returned_advisers = Adviser.on_firms_with_fca_number(firm.fca_number) + expect(returned_advisers.length).to eq(2) + expect(returned_advisers).to include advisers[0] + expect(returned_advisers).to include advisers[1] + end + + it 'does not return advisers on other firms' do + firm = FactoryGirl.create(:firm_with_advisers, advisers_count: 1) + + returned_advisers = Adviser.on_firms_with_fca_number(firm.fca_number) + expect(returned_advisers).to eq firm.advisers + end + end + + describe '.sorted_by_name scope' do + let(:sorted_names) { %w(A B C D E F G H) } + let(:unsorted_names) { %w(F C G E D H A B) } + + before do + unsorted_names.each { |name| FactoryGirl.create(:adviser, name: name) } + end + + it 'sorts the result set by the name field' do + expect(Adviser.sorted_by_name.map(&:name)).to eq(sorted_names) + end + end +end diff --git a/spec/models/allowed_payment_method_spec.rb b/spec/models/allowed_payment_method_spec.rb new file mode 100644 index 000000000..4e059ccbe --- /dev/null +++ b/spec/models/allowed_payment_method_spec.rb @@ -0,0 +1,3 @@ +RSpec.describe AllowedPaymentMethod do + it_behaves_like 'reference data' +end diff --git a/spec/models/firm_factory_spec.rb b/spec/models/firm_factory_spec.rb new file mode 100644 index 000000000..d2d6b2f55 --- /dev/null +++ b/spec/models/firm_factory_spec.rb @@ -0,0 +1,191 @@ +RSpec.describe 'Firm factory' do + subject do + begin + FactoryGirl.create(factory) + rescue ActiveRecord::RecordInvalid + # If create fails we fall back to a build. We can then explicitly test + # what we expect to have happened using the `be_persisted` matcher. + FactoryGirl.build(factory) + end + end + + describe 'factory :firm (default factory)' do + let(:factory) { :firm } + + specify 'expected status' do + expect(subject).to be_persisted + expect(subject).to be_valid + expect(subject).to be_publishable + expect(subject).not_to be_trading_name + expect(subject.primary_advice_method).to be(:local) + end + + specify 'associations' do + expect(subject.principal).not_to be_present + expect(subject).to have(1).offices + expect(subject).to have(1).advisers + expect(subject).to have(:no).trading_names + end + end + + describe 'factory :firm_with_advisers' do + let(:factory) { :firm_with_advisers } + + specify 'expected status' do + expect(subject).to be_persisted + expect(subject).to be_valid + expect(subject).to be_publishable + expect(subject).not_to be_trading_name + expect(subject.primary_advice_method).to be(:local) + end + + specify 'associations' do + expect(subject.principal).not_to be_present + expect(subject).to have(1).offices + expect(subject).to have(3).advisers + expect(subject).to have(:no).trading_names + end + end + + describe 'factory :firm_without_advisers' do + let(:factory) { :firm_without_advisers } + + specify 'expected status' do + expect(subject).to be_persisted + expect(subject).to be_valid + expect(subject).not_to be_publishable + expect(subject).not_to be_trading_name + expect(subject.primary_advice_method).to be(:local) + end + + specify 'associations' do + expect(subject.principal).not_to be_present + expect(subject).to have(1).offices + expect(subject).to have(:no).advisers + expect(subject).to have(:no).trading_names + end + end + + describe 'factory :firm_with_offices' do + let(:factory) { :firm_with_offices } + + specify 'expected status' do + expect(subject).to be_persisted + expect(subject).to be_valid + expect(subject).to be_publishable + expect(subject).not_to be_trading_name + expect(subject.primary_advice_method).to be(:local) + end + + specify 'associations' do + expect(subject.principal).not_to be_present + expect(subject).to have(3).offices + expect(subject).to have(1).advisers + expect(subject).to have(:no).trading_names + end + end + + describe 'factory :firm_without_offices' do + let(:factory) { :firm_without_offices } + + specify 'expected status' do + expect(subject).to be_persisted + expect(subject).to be_valid + expect(subject).not_to be_publishable + expect(subject).not_to be_trading_name + expect(subject.primary_advice_method).to be(:local) + end + + specify 'associations' do + expect(subject.principal).not_to be_present + expect(subject).to have(:no).offices + expect(subject).to have(1).advisers + expect(subject).to have(:no).trading_names + end + end + + describe 'factory :firm_with_trading_names' do + let(:factory) { :firm_with_trading_names } + + specify 'expected status' do + expect(subject).to be_persisted + expect(subject).to be_valid + expect(subject).to be_publishable + expect(subject).not_to be_trading_name + expect(subject.primary_advice_method).to be(:local) + end + + specify 'associations' do + expect(subject.principal).not_to be_present + expect(subject).to have(1).offices + expect(subject).to have(1).advisers + + expect(subject).to have(3).trading_names + expect(subject.trading_names).to all(have_attributes(fca_number: subject.fca_number)) + expect(subject.trading_names).to all(have_attributes(parent: subject)) + end + end + + describe 'factory :firm_with_principal' do + let(:factory) { :firm_with_principal } + + specify 'expected status' do + expect(subject).to be_persisted + expect(subject).to be_valid + expect(subject).to be_publishable + expect(subject).not_to be_trading_name + expect(subject.primary_advice_method).to be(:local) + end + + specify 'associations' do + expect(subject.principal).to be_present + expect(subject.principal.fca_number).to eq(subject.fca_number) + # @todo fails. Creates principal with 2 main firms !!! + # Note: we need to wait until rad_consumer is updated to run with the latest version of this Gem + # so that we can check that nothing gets broken by fixing this + # expect(subject.principal.firm).to eq(subject) + + expect(subject).to have(1).offices + expect(subject).to have(1).advisers + expect(subject).to have(:no).trading_names + end + end + + describe 'factory :firm_with_remote_advice' do + let(:factory) { :firm_with_remote_advice } + + specify 'expected status' do + expect(subject).to be_persisted + expect(subject).to be_valid + expect(subject).to be_publishable + expect(subject).not_to be_trading_name + expect(subject.primary_advice_method).to be(:remote) + end + + specify 'associations' do + expect(subject.principal).not_to be_present + expect(subject).to have(1).offices + expect(subject).to have(1).advisers + expect(subject).to have(:no).trading_names + end + end + + describe 'factory :invalid_firm' do + let(:factory) { :invalid_firm } + + specify 'expected status' do + expect(subject).not_to be_persisted + expect(subject).not_to be_valid + expect(subject).not_to be_publishable + expect(subject).not_to be_trading_name + expect(subject.primary_advice_method).to be(:local) + end + + specify 'associations' do + expect(subject.principal).not_to be_present + expect(subject).to have(:no).offices + expect(subject).to have(:no).advisers + expect(subject).to have(:no).trading_names + end + end +end diff --git a/spec/models/firm_spec.rb b/spec/models/firm_spec.rb new file mode 100644 index 000000000..99e18e83c --- /dev/null +++ b/spec/models/firm_spec.rb @@ -0,0 +1,606 @@ +RSpec.describe Firm do + subject(:firm) { build(:firm) } + + describe 'languages_used' do + context 'when there are no firms' do + it 'has no languages' do + expect(Firm.languages_used).to be_empty + end + end + + context 'when there are firms with no languages set' do + it 'has no languages' do + create(:firm, languages: []) + expect(Firm.languages_used).to be_empty + end + end + + context 'when there are a multiple languages on multiple firms' do + it 'has has multiple languages' do + create(:firm, languages: ['sco', 'swe']) + create(:firm, languages: ['nor', 'lat']) + expect(Firm.languages_used).to eq(['lat', 'nor', 'sco', 'swe']) + end + + it 'has has duplicate languages' do + create(:firm, languages: ['sco', 'swe']) + create(:firm, languages: ['nor', 'swe']) + expect(Firm.languages_used).to eq(['nor', 'sco', 'swe']) + end + end + end + + describe 'default behaviour' do + it 'sets ethical_investing_flag to false' do + expect(Firm.new.ethical_investing_flag).to be_falsey + end + + it 'sets sharia_investing_flag to false' do + expect(Firm.new.sharia_investing_flag).to be_falsey + end + + it 'sets workplace_financial_advice_flag to false' do + expect(Firm.new.workplace_financial_advice_flag).to be_falsey + end + + it 'sets non_uk_residents_flag to false' do + expect(Firm.new.non_uk_residents_flag).to be_falsey + end + + it 'sets languages to an array with empty set' do + expect(Firm.new.languages).to eq [] + end + end + + describe '#registered?' do + def set_marker_field(firm, value) + firm.send("#{Firm::REGISTERED_MARKER_FIELD}=", value) + end + + it 'is false if the REGISTERED_MARKER_FIELD field is nil' do + set_marker_field(firm, nil) + expect(firm).not_to be_registered + end + + it 'is true if the REGISTERED_MARKER_FIELD field has a valid value' do + Firm::REGISTERED_MARKER_FIELD_VALID_VALUES.each do |value| + set_marker_field(firm, value) + expect(firm).to be_registered + end + end + end + + describe '#postcode_searchable?' do + it 'delegates to #in_person_advice?' do + expect(firm).to be_postcode_searchable + end + end + + describe '#in_person_advice?' do + context 'when the firm offers in person advice' do + it 'is true' do + expect(firm).to be_in_person_advice + end + end + + context 'when the firm does not offer in person advice' do + it 'is false' do + firm.in_person_advice_methods.clear + + expect(firm).to_not be_in_person_advice + end + end + end + + describe 'subsidaries' do + context 'when the firm has a parent' do + it 'is classed as a subsidiary' do + expect(build(:subsidiary)).to be_subsidiary + end + end + + describe '#subsidiaries' do + it 'exposes subsidiaries' do + subsidiary = create(:subsidiary) + expect(subsidiary.parent.subsidiaries).to contain_exactly(subsidiary) + end + end + end + + describe '#offices' do + let(:firm) { create(:firm, offices_count: 0) } + let!(:unsorted_offices) do + [ + FactoryGirl.create(:office, firm: firm, address_line_one: 'fourth', created_at: Time.zone.now), + FactoryGirl.create(:office, firm: firm, address_line_one: 'second', created_at: 2.days.ago), + FactoryGirl.create(:office, firm: firm, address_line_one: 'first', created_at: 3.days.ago), + FactoryGirl.create(:office, firm: firm, address_line_one: 'third', created_at: 1.day.ago) + ] + end + + describe 'default sort order' do + subject { firm.reload.offices.map(&:address_line_one) } + it { is_expected.to eq(%w{first second third fourth}) } + end + end + + describe '#main_office' do + let(:firm) { create(:firm, offices_count: 0) } + subject { firm.main_office } + + context 'when the firm has no offices' do + it { is_expected.to be_nil } + end + + context 'when the firm has offices' do + before { FactoryGirl.create_list(:office, 3, firm: firm) } + # We implement using #first (which runs one query) but test against + # offices[0]. Both should return the same value or things are not + # correct. + it { is_expected.to eq(firm.offices[0]) } + end + end + + describe '#publishable?' do + let(:firm) { FactoryGirl.create(:firm) } + subject { firm.publishable? } + + context 'when the firm is valid, has a main office and is not missing advisers' do + it { is_expected.to be_truthy } + end + + context 'when the firm is not valid' do + let(:firm) do + FactoryGirl.build(:invalid_firm).tap { |f| f.save(validate: false) } + end + + it { is_expected.to be_falsey } + end + + context 'when the firm has no main office' do + let(:firm) { FactoryGirl.create(:firm, offices_count: 0) } + + it { is_expected.to be_falsey } + end + + context 'when the firm is missing advisers' do + before { allow(firm).to receive(:missing_advisers?).and_return(true) } + + it { is_expected.to be_falsey } + end + end + + describe '#missing_advisers?' do + let(:factory) { :firm } + subject { FactoryGirl.create(factory, advisers_count: advisers_count).missing_advisers? } + + context 'when the firm offers face-to-face advice' do + context 'when the firm has advisers' do + let(:advisers_count) { 1 } + it { is_expected.to be_falsey } + end + + context 'when the firm has no advisers' do + let(:advisers_count) { 0 } + it { is_expected.to be_truthy } + end + end + + context 'when the firm offers remote advice' do + let(:factory) { :firm_with_remote_advice } + + context 'when the firm has advisers' do + let(:advisers_count) { 1 } + it { is_expected.to be_falsey } + end + + context 'when the firm has no advisers' do + let(:advisers_count) { 0 } + it { is_expected.to be_falsey } + end + end + end + + describe 'validation' do + it 'is valid with valid attributes' do + expect(firm).to be_valid + end + + it 'orders fields correctly for dough' do + expect(firm.field_order).not_to be_empty + end + + describe 'Website address' do + context 'when provided' do + it 'must not exceed 100 characters' do + expect(build(:firm, website_address: "#{'a' * 100}.com")).not_to be_valid + end + + it 'must contain at least one .' do + expect(build(:firm, website_address: 'http://examplecom')).not_to be_valid + expect(build(:firm, website_address: 'http://example.com')).to be_valid + end + + it 'must not contain spaces' do + expect(build(:firm, website_address: 'http://example site.com')).not_to be_valid + end + + it 'does not require the protocol to be present' do + expect(build(:firm, website_address: 'www.example.com')).to be_valid + end + + it 'must require the protocol to be http or https if provided' do + expect(build(:firm, website_address: 'http://www.example.com')).to be_valid + expect(build(:firm, website_address: 'https://www.example.com')).to be_valid + expect(build(:firm, website_address: 'ftp://www.example.com')).not_to be_valid + end + + it 'allows paths to be in the address' do + expect(build(:firm, website_address: 'www.example.com/user')).to be_valid + end + end + end + + describe 'languages' do + context 'when it contains valid language strings' do + before { firm.languages = ['fra', 'deu'] } + it { is_expected.to be_valid } + end + + context 'when it contains invalid language strings' do + before { firm.languages = ['no_language', 'fra'] } + it { is_expected.to be_invalid } + end + + context 'when it is empty' do + before { firm.languages = [] } + it { is_expected.to be_valid } + end + + context 'when it contains blank values' do + before { firm.languages = [''] } + it 'filters them out pre-validation' do + firm.valid? + expect(firm.languages).to be_empty + end + end + + context 'when it contains duplicate values' do + before { firm.languages = ['fra', 'fra', 'deu'] } + it 'filters them out pre-validation' do + firm.valid? + expect(firm.languages).to eq ['fra', 'deu'] + end + end + end + + describe 'in person advice methods' do + # Make the record generally valid for either remote or local types vvv + before { firm.other_advice_methods = create_list(:other_advice_method, rand(1..3)) } + + context 'when none assigned' do + before { firm.in_person_advice_methods = [] } + + context 'when the user selects remote advice' do + before { firm.primary_advice_method = :remote } + it { is_expected.to be_valid } + + it 'clears in-person advice methods' do + subject.valid? + expect(subject.in_person_advice_methods).to be_empty + end + end + + context 'when the user selects local advice' do + before { firm.primary_advice_method = :local } + it { is_expected.not_to be_valid } + + it 'preserves remote advice methods' do + subject.valid? + expect(subject.other_advice_methods).to_not be_empty + end + end + end + end + + describe 'other (remote) advice methods' do + context 'when none assigned' do + before { firm.other_advice_methods = [] } + + context 'when the user selects remote advice' do + before { firm.primary_advice_method = :remote } + it { is_expected.not_to be_valid } + end + + context 'when the user selects local advice' do + before { firm.primary_advice_method = :local } + it { is_expected.to be_valid } + end + end + end + + describe 'remote or local advice method' do + context 'when none assigned' do + before { firm.other_advice_methods = [] } + before { firm.in_person_advice_methods = [] } + before { firm.primary_advice_method = nil } + + it { is_expected.not_to be_valid } + end + end + + describe 'free initial meeting' do + context 'when missing' do + before { firm.free_initial_meeting = nil } + + it { is_expected.not_to be_valid } + end + + context 'when set to true' do + before { firm.free_initial_meeting = true } + + describe 'initial meeting duration' do + before { firm.initial_meeting_duration = nil } + + context 'when missing' do + it { is_expected.not_to be_valid } + end + end + end + end + + describe 'initial advice fee structures' do + context 'when none assigned' do + before { firm.initial_advice_fee_structures = [] } + + it { is_expected.not_to be_valid } + end + end + + describe 'ongoing advice fee structures' do + context 'when none assigned' do + before { firm.ongoing_advice_fee_structures = [] } + + it { is_expected.not_to be_valid } + end + end + + describe 'allowed payment methods' do + context 'when none assigned' do + before { firm.allowed_payment_methods = [] } + + it { is_expected.not_to be_valid } + end + end + + describe 'minimum fixed fee' do + context 'default value' do + it { expect(Firm.new.minimum_fixed_fee).to eq(0) } + end + + context 'when not numeric' do + before { firm.minimum_fixed_fee = 'not-numeric' } + + it { is_expected.not_to be_valid } + end + + context 'when blank' do + before { firm.minimum_fixed_fee = nil } + + it { is_expected.not_to be_valid } + end + + context 'when less than zero' do + before { firm.minimum_fixed_fee = -1 } + + it { is_expected.to be_invalid } + end + + context 'when zero' do + before { firm.minimum_fixed_fee = 0 } + + it { is_expected.to be_valid } + end + + context 'when more than zero' do + before { firm.minimum_fixed_fee = 1 } + + it { is_expected.to be_valid } + end + end + + describe 'investment size' do + context 'when none assigned' do + before { firm.investment_sizes = [] } + + it { is_expected.not_to be_valid } + end + end + + describe 'advice types' do + context 'when none assigned' do + before do + Firm::ADVICE_TYPES_ATTRIBUTES.each do |attribute| + firm[attribute] = false + end + end + + it { is_expected.not_to be_valid } + end + end + + describe 'status' do + context 'without a status' do + it 'is valid' do + expect(build(:firm, status: nil)).to be_valid + end + end + + context 'with a garbage status' do + it 'throws an exception' do + expect { build(:firm, status: :horse) }.to raise_error(ArgumentError) + end + end + + context 'with status "independent"' do + it 'is valid' do + expect(build(:firm, status: :independent)).to be_valid + end + end + + context 'with status "restricted"' do + it 'is valid' do + expect(build(:firm, status: :restricted)).to be_valid + end + end + end + end + + describe '#notify_indexer' do + it 'notifies the firm indexer that the firm has changed' do + expect(FirmIndexer).to receive(:handle_firm_changed).with(subject) + subject.notify_indexer + end + end + + describe 'after_commit' do + before { expect(subject).to receive(:notify_indexer) } + + context 'when a new firm is saved' do + subject { FactoryGirl.build(:firm) } + + it 'calls notify_indexer' do + subject.save + subject.run_callbacks(:commit) + end + end + + context 'when a firm is updated' do + subject { FactoryGirl.create(:firm) } + + it 'calls notify_indexer' do + subject.update_attributes(registered_name: 'A new name') + subject.run_callbacks(:commit) + end + end + + context 'when a firm is destroyed' do + subject { FactoryGirl.create(:firm) } + + it 'calls notify_indexer' do + firm.destroy + subject.run_callbacks(:commit) + end + end + end + + describe 'destruction' do + context 'when the firm has advisers' do + let(:firm) { create(:firm_with_advisers) } + + it 'cascades destroy to advisers' do + adviser = firm.advisers.first + firm.destroy + expect(Adviser.where(id: adviser.id)).to be_empty + end + end + + context 'when the firm has subsidiaries' do + let(:firm) { create(:firm_with_trading_names) } + + it 'cascades to destroy the subsidiaries too' do + subsidiary = firm.subsidiaries.first + firm.destroy + expect(Firm.where(id: subsidiary.id)).to be_empty + end + end + + context 'when the firm has offices' do + let(:firm) { create(:firm_with_offices, offices_count: 1) } + + it 'cascades to destroy the offices too' do + office = firm.offices.first + firm.destroy + expect(Office.where(id: office.id)).to be_empty + end + end + + context 'when the firm has a principal' do + let(:firm) { create(:firm_with_principal) } + + it 'does not destroy the principal' do + principal = firm.principal + firm.destroy + expect(Principal.where(token: principal.id)).not_to be_empty + end + end + end + + describe '.sorted_by_registered_name scope' do + let(:sorted_names) { %w(A B C D E F G H) } + let(:unsorted_names) { %w(F C G E D H A B) } + + before do + unsorted_names.each { |name| FactoryGirl.create(:firm, registered_name: name) } + end + + it 'sorts the result set by the registered_name field' do + expect(Firm.sorted_by_registered_name.map(&:registered_name)).to eq(sorted_names) + end + end + + describe '#advice_types' do + it 'returns a hash of advice types' do + expect(subject.advice_types).to eq({ + retirement_income_products_flag: subject.retirement_income_products_flag, + pension_transfer_flag: subject.pension_transfer_flag, + long_term_care_flag: subject.long_term_care_flag, + equity_release_flag: subject.equity_release_flag, + inheritance_tax_and_estate_planning_flag: subject.inheritance_tax_and_estate_planning_flag, + wills_and_probate_flag: subject.wills_and_probate_flag + }) + end + end + + describe '#remote_advice' do + let(:firm) do + FactoryGirl.create(:firm, + in_person_advice_methods: in_person_advice_methods, + other_advice_methods: other_advice_methods) + end + subject { firm.primary_advice_method } + let(:in_person_advice_methods) { [create(:in_person_advice_method)] } + let(:other_advice_methods) { [] } + + context 'when instance variable is set' do + before { firm.primary_advice_method = :dog } + it { is_expected.to be :dog } + end + + context 'when in-person advice methods are set' do + let(:in_person_advice_methods) { FactoryGirl.create_list :in_person_advice_method, 1 } + + context 'when remote advice methods are set' do + let(:other_advice_methods) { FactoryGirl.create_list :other_advice_method, 1 } + it { is_expected.to be :local } + end + + context 'when remote advice methods are not set' do + it { is_expected.to be :local } + end + end + + context 'when in-person advice methods are not set' do + context 'when remote advice methods are set' do + let(:other_advice_methods) { FactoryGirl.create_list :other_advice_method, 1 } + let(:in_person_advice_methods) { [] } + it { is_expected.to be :remote } + end + + context 'when remote advice methods are not set' do + before { firm.in_person_advice_methods = [] } + it { is_expected.to be nil } + end + end + end +end diff --git a/spec/models/in_person_advice_method_spec.rb b/spec/models/in_person_advice_method_spec.rb new file mode 100644 index 000000000..0a6d48f7c --- /dev/null +++ b/spec/models/in_person_advice_method_spec.rb @@ -0,0 +1,4 @@ +RSpec.describe InPersonAdviceMethod do + it_behaves_like 'reference data' + it_behaves_like 'friendly named' +end diff --git a/spec/models/initial_advice_fee_structure_spec.rb b/spec/models/initial_advice_fee_structure_spec.rb new file mode 100644 index 000000000..b95136945 --- /dev/null +++ b/spec/models/initial_advice_fee_structure_spec.rb @@ -0,0 +1,3 @@ +RSpec.describe InitialAdviceFeeStructure do + it_behaves_like 'reference data' +end diff --git a/spec/models/initial_meeting_duration_spec.rb b/spec/models/initial_meeting_duration_spec.rb new file mode 100644 index 000000000..6e6ec8e79 --- /dev/null +++ b/spec/models/initial_meeting_duration_spec.rb @@ -0,0 +1,3 @@ +RSpec.describe InitialMeetingDuration do + it_behaves_like 'reference data' +end diff --git a/spec/models/investment_size_spec.rb b/spec/models/investment_size_spec.rb new file mode 100644 index 000000000..3637ec1a9 --- /dev/null +++ b/spec/models/investment_size_spec.rb @@ -0,0 +1,40 @@ +RSpec.describe InvestmentSize do + it_behaves_like 'reference data' + it_behaves_like 'translatable' + it_behaves_like 'friendly named' + + context 'identifying the lowest defined value' do + let(:lower_limit_ordinal) { 1 } + let(:upper_limit_ordinal) { 5 } + let(:ordinals) { (lower_limit_ordinal..upper_limit_ordinal).to_a } + before do + # Reversing the ordinals so the default sort order is not the same as the + # order of creation and so hopefully prove we are not dependent on DB + # allocated IDs in any way + ordinals.reverse.each do |ordinal| + FactoryGirl.create(:investment_size, order: ordinal) + end + end + + describe '.lowest' do + it 'returns the investment size with the lowest order value' do + expect(described_class.lowest.order).to eq(lower_limit_ordinal) + end + end + + describe '#lowest?' do + let(:record) { described_class.find_by(order: ordinal) } + subject { record.lowest? } + + context 'when the subject is the lowest value' do + let(:ordinal) { lower_limit_ordinal } + it { is_expected.to be(true) } + end + + context 'when the subject is not the lowest value' do + let(:ordinal) { upper_limit_ordinal } + it { is_expected.to be(false) } + end + end + end +end diff --git a/spec/models/lookup/adviser_spec.rb b/spec/models/lookup/adviser_spec.rb new file mode 100644 index 000000000..aa50c71f3 --- /dev/null +++ b/spec/models/lookup/adviser_spec.rb @@ -0,0 +1,21 @@ +RSpec.describe Lookup::Adviser do + describe 'validation' do + it 'is valid with valid attributes' do + expect( + described_class.new(reference_number: 'AAA00001', name: 'Ben Lovell') + ).to be_valid + end + + describe 'Reference number rules' do + it 'must be 8 characters' do + expect(described_class.new(reference_number: '123A45')).to_not be_valid + end + end + + describe 'Name rules' do + it 'must be provided' do + expect(described_class.new(reference_number: 'AAA00001')).to_not be_valid + end + end + end +end diff --git a/spec/models/lookup/firm_spec.rb b/spec/models/lookup/firm_spec.rb new file mode 100644 index 000000000..fcc8daa5f --- /dev/null +++ b/spec/models/lookup/firm_spec.rb @@ -0,0 +1,19 @@ +RSpec.describe Lookup::Firm do + describe 'validation' do + it 'is valid with valid attributes' do + expect( + described_class.new(fca_number: 123456, registered_name: 'Ben Lovell Ltd') + ).to be_valid + end + + describe 'FCA Number rules' do + it 'accepts numeric' do + expect(described_class.new(fca_number: '123A45')).to_not be_valid + end + + it 'accepts only 6 digits' do + expect(described_class.new(fca_number: 1234567)).to_not be_valid + end + end + end +end diff --git a/spec/models/office_spec.rb b/spec/models/office_spec.rb new file mode 100644 index 000000000..c00c50404 --- /dev/null +++ b/spec/models/office_spec.rb @@ -0,0 +1,351 @@ +RSpec.describe Office do + include FieldLengthValidationHelpers + + let(:firm) { FactoryGirl.create(:firm_with_offices, id: 123) } + subject(:office) { firm.offices.first } + + it_should_behave_like 'geocodable' do + let(:invalid_geocodable) { Office.new } + let(:valid_new_geocodable) { FactoryGirl.build(:office, firm: firm) } + let(:saved_geocodable) { office } + let(:address_field_name) { :address_postcode } + let(:address_field_updated_value) { 'SO32 2AY' } + let(:updated_address_params) { { address_line_one: 'A new place' } } + end + + describe '#notify_indexer' do + it 'notifies the indexer that the office has changed' do + expect(FirmIndexer).to receive(:handle_aggregate_changed).with(subject) + subject.notify_indexer + end + end + + describe 'after_commit' do + before { expect(subject).to receive(:notify_indexer) } + + context 'when a new office is saved' do + subject { FactoryGirl.build(:office, firm: firm) } + + it 'calls notify_indexer' do + subject.save + subject.run_callbacks(:commit) + end + end + + context 'when an office is updated' do + it 'calls notify_indexer' do + subject.update_attributes(address_line_one: 'A new street') + subject.run_callbacks(:commit) + end + end + + context 'when an office is destroyed' do + it 'calls notify_indexer' do + office.destroy + subject.run_callbacks(:commit) + end + end + end + + describe '#has_address_changes?' do + context 'when none of the address fields have changed' do + it 'returns false' do + expect(subject.has_address_changes?).to be(false) + end + end + + described_class::ADDRESS_FIELDS.each do |field| + context "when the model #{field} field has changed" do + before do + subject.send("#{field}=", 'changed') + end + + it 'returns true' do + expect(subject.has_address_changes?).to be(true) + end + end + end + end + + describe '#telephone_number=' do + context 'when `nil`' do + before { office.telephone_number = nil } + + it 'returns `nil`' do + expect(office.telephone_number).to be_nil + end + end + + context 'when provided' do + it 'removes whitespace from the front and back' do + office.telephone_number = ' 07715 930 457 ' + expect(office.telephone_number).to eq('07715 930 457') + end + + it 'removes extraneous whitespace' do + office.telephone_number = '07715 930 457' + expect(office.telephone_number).to eq('07715 930 457') + end + end + end + + describe '#telephone_number' do + context 'when `nil`' do + before { office.telephone_number = nil } + + it 'returns `nil`' do + expect(office.telephone_number).to be_nil + end + end + + context 'when provided' do + it 'removes whitespace from the front and back' do + office.update_column(:telephone_number, ' 07715 930 457 ') + expect(office.telephone_number).to eq('07715 930 457') + end + + it 'removes extraneous whitespace' do + office.update_column(:telephone_number, '07715 930 457') + expect(office.telephone_number).to eq('07715 930 457') + end + + it 'formats if there is no whitespace at all' do + office.update_column(:telephone_number, '07715930457') + expect(office.telephone_number).to eq('07715 930457') + end + end + end + + describe 'validation' do + it 'is valid with valid attributes' do + expect(office).to be_valid + end + + it 'orders fields correctly for dough' do + expect(office.field_order).not_to be_empty + end + + describe 'email address' do + context 'when not present' do + before { office.email_address = nil } + + it { is_expected.to_not be_valid } + end + + context 'when badly formatted' do + before { office.email_address = 'not-valid' } + + it { is_expected.to_not be_valid } + end + + context 'length' do + specify { expect_length_of(office, :email_address, 50).to be_valid } + specify { expect_length_of(office, :email_address, 51).not_to be_valid } + end + end + + describe 'telephone number' do + # See http://www.area-codes.org.uk/formatting.php#programmers for background info + + context 'invalid inputs' do + context 'when not present' do + before { office.telephone_number = nil } + + it { is_expected.to_not be_valid } + end + + context 'when the input characters other than spaces or numbers' do + before { office.telephone_number = 'not-numbers-or-spaces' } + + it { is_expected.to_not be_valid } + end + + context 'when the area code is not specified' do + before { office.telephone_number = '917561' } + + it { is_expected.to_not be_valid } + end + + context 'when the prefix is not a 0' do + before { office.telephone_number = '7816917560' } + + it { is_expected.to_not be_valid } + end + + context 'when the prefix is not a real area code' do + # Currently 04 and 06 are not valid prefixes + before { office.telephone_number = '04816 917560' } + + it { is_expected.to_not be_valid } + end + + context 'when the prefix is a +nn country code' do + before { office.telephone_number = '+44 7816917560' } + + it { is_expected.to_not be_valid } + end + + context 'when the prefix is a 00nn international access code' do + before { office.telephone_number = '0044 7816917560' } + + it { is_expected.to_not be_valid } + end + + context 'when the prefix is a nn international access code' do + before { office.telephone_number = '447816917560' } + + it { is_expected.to_not be_valid } + end + end + + context 'valid inputs' do + context 'when it has no spaces' do + before { office.telephone_number = '07816917561' } + + it { is_expected.to be_valid } + end + + context 'when it has spaces' do + before { office.telephone_number = '07816 917 561' } + + it { is_expected.to be_valid } + end + end + end + + describe 'address line 1' do + context 'when missing' do + before { office.address_line_one = nil } + + it { is_expected.not_to be_valid } + end + + context 'length' do + specify { expect_length_of(office, :address_line_one, 100).to be_valid } + specify { expect_length_of(office, :address_line_one, 101).not_to be_valid } + end + end + + describe 'address town' do + context 'when missing' do + before { office.address_town = nil } + + it { is_expected.not_to be_valid } + end + + context 'length' do + specify { expect_length_of(office, :address_town, 100).to be_valid } + specify { expect_length_of(office, :address_town, 101).not_to be_valid } + end + end + + describe 'address county' do + context 'when missing' do + before { office.address_county = nil } + + it { is_expected.to be_valid } + end + + context 'length' do + specify { expect_length_of(office, :address_county, 100).to be_valid } + specify { expect_length_of(office, :address_county, 101).not_to be_valid } + end + end + + describe 'address postcode' do + context 'when missing' do + before { office.address_postcode = nil } + + it { is_expected.not_to be_valid } + end + + context 'when invalid' do + before { office.address_postcode = 'not-valid' } + + it { is_expected.not_to be_valid } + end + + context 'when not all upper cased' do + before { office.address_postcode = 'eh3 9dr' } + + it 'upcases it before saving' do + expect(office).to be_valid + office.save! + expect(office.address_postcode).to eq('EH3 9DR') + end + end + + context 'when not in the correct format' do + it 'corrects the format before saving' do + office.address_postcode = 'EH39DR' + expect(office).to be_valid + office.save! + expect(office.address_postcode).to eq('EH3 9DR') + end + end + + context 'format' do + it 'is invalid without a full postcode' do + office.address_postcode = 'W12' + expect(office).to be_invalid + expect(office.errors[:address_postcode]).to include('is invalid') + end + + it 'is invalid with an invalid full postcode' do + office.address_postcode = '1234 567' + expect(office).to be_invalid + expect(office.errors[:address_postcode]).to include('is invalid') + expect(office.address_postcode).to eq('1234 567') + end + + it 'is valid with a valid full postcode' do + office.address_postcode = 'EH3 9DR' + expect(office).to be_valid + end + + it 'is valid with a valid full postcode even without spaces' do + office.address_postcode = 'EH39DR' + expect(office).to be_valid + end + end + end + + describe 'disabled access' do + context 'when missing' do + before { office.disabled_access = nil } + + it { is_expected.not_to be_valid } + end + + context 'when true' do + before { office.disabled_access = true } + + it { is_expected.to be_valid } + end + + context 'when false' do + before { office.disabled_access = false } + + it { is_expected.to be_valid } + end + end + end + + describe '#full_street_address' do + subject { office.full_street_address } + + it { is_expected.to eql "#{office.address_postcode}, United Kingdom"} + + context 'when line two is nil' do + before { office.address_line_two = nil } + + it { is_expected.to eql "#{office.address_postcode}, United Kingdom"} + end + + context 'when line two is an empty string' do + before { office.address_line_two = '' } + + it { is_expected.to eql "#{office.address_postcode}, United Kingdom"} + end + end +end diff --git a/spec/models/ongoing_advice_fee_structure_spec.rb b/spec/models/ongoing_advice_fee_structure_spec.rb new file mode 100644 index 000000000..2024e7a34 --- /dev/null +++ b/spec/models/ongoing_advice_fee_structure_spec.rb @@ -0,0 +1,3 @@ +RSpec.describe OngoingAdviceFeeStructure do + it_behaves_like 'reference data' +end diff --git a/spec/models/other_advice_method_spec.rb b/spec/models/other_advice_method_spec.rb new file mode 100644 index 000000000..627c1761a --- /dev/null +++ b/spec/models/other_advice_method_spec.rb @@ -0,0 +1,6 @@ +RSpec.describe OtherAdviceMethod do + it_behaves_like 'reference data' + it_behaves_like 'translatable' + it_behaves_like 'friendly named' + it_behaves_like 'system named' +end diff --git a/spec/models/principal_spec.rb b/spec/models/principal_spec.rb new file mode 100644 index 000000000..a13aed067 --- /dev/null +++ b/spec/models/principal_spec.rb @@ -0,0 +1,239 @@ +RSpec.describe Principal do + let(:principal) { create(:principal) } + let(:trading_name) { create(:firm, parent: principal.firm, fca_number: principal.fca_number) } + + describe '#firm' do + let(:parent_firm) { Firm.find_by(fca_number: principal.fca_number, parent: nil) } + + it 'fetches the parent firm' do + expect(principal.firm).to eq(parent_firm) + end + end + + describe '#main_firm_with_trading_names' do + it 'fetches all firms associated with the principal' do + expect(principal.main_firm_with_trading_names).to contain_exactly(principal.firm, trading_name) + end + end + + describe '#lookup_firm' do + it 'returns my associated lookup firm' do + expect(principal.lookup_firm).to be + end + end + + describe '#subsidiaries?' do + context 'when my firm has subsidiaries' do + before { principal.lookup_firm.subsidiaries.create! } + + it 'is truthy' do + expect(principal.subsidiaries?).to be_truthy + end + end + + context 'when my firm has no subsidiaries' do + it 'is falsey' do + expect(principal.subsidiaries?).to be_falsey + end + end + end + + describe '#full_name' do + it 'returns a string containing the first and last name together' do + expect(principal.full_name).to eq("#{principal.first_name} #{principal.last_name}") + end + end + + it 'uses the token for ID purposes' do + expect(principal.id).to eq(principal.token) + expect(Principal.find(principal.token)).to eq(principal) + end + + context 'upon creation' do + it 'generates an 8 character, 4 byte token' do + expect(principal.token.length).to eq(8) + end + + it 'creates the associated Firm' do + expect(principal.firm.fca_number).to eq(principal.lookup_firm.fca_number) + end + end + + describe 'validation' do + it 'is valid with valid attributes' do + expect(principal).to be_valid + end + + it 'must confirm accuracy' do + expect(build(:principal, confirmed_disclaimer: false)).to_not be_valid + end + + describe 'FCA number' do + it 'must be a present' do + build(:principal).tap do |p| + p.fca_number = nil + expect(p).to_not be_valid + end + end + + it 'must be a 6 digit number' do + build(:principal).tap do |p| + p.fca_number = 12345 + expect(p).to_not be_valid + end + end + + it 'must match a `Lookup::Firm`' do + Lookup::Firm.find_by(fca_number: principal.fca_number).destroy + + expect(principal).to_not be_valid + end + + it 'must be unique' do + build(:principal).tap do |p| + p.fca_number = principal.fca_number + expect(p).to_not be_valid + end + end + end + + describe 'Email address' do + it 'must be present' do + expect(build(:principal, email_address: '')).to_not be_valid + end + + it 'must be unique' do + dupe = build(:principal, email_address: principal.email_address) + expect(dupe).to_not be_valid + end + + it 'must be a reasonably valid format' do + %w(zzz abc@abc a@a.).each do |bad| + principal.email_address = bad + expect(principal).to_not be_valid + end + end + + it 'must not exceed 50 characters' do + long_email = "a@#{'a' * 50}.com" + expect(build(:principal, email_address: long_email)).to_not be_valid + end + end + + describe 'First name' do + it 'must be present' do + expect(build(:principal, first_name: '')).to_not be_valid + end + + it 'must be at least 2 characters' do + expect(build(:principal, first_name: 'A')).to_not be_valid + end + + it 'must not exceed 80 characters' do + expect(build(:principal, first_name: 'A' * 81)).to_not be_valid + end + end + + describe 'Last name' do + it 'must be present' do + expect(build(:principal, last_name: '')).to_not be_valid + end + + it 'must be at least 2 characters' do + expect(build(:principal, last_name: 'A')).to_not be_valid + end + + it 'must not exceed 80 characters' do + expect(build(:principal, last_name: 'A' * 81)).to_not be_valid + end + end + + describe 'Job title' do + it 'must be present' do + expect(build(:principal, job_title: '')).to_not be_valid + end + + it 'must be at least 2 characters' do + expect(build(:principal, job_title: 'A')).to_not be_valid + end + + it 'must not exceed 80 characters' do + expect(build(:principal, job_title: 'A' * 81)).to_not be_valid + end + end + + describe 'Telephone number' do + it 'must be present' do + expect(build(:principal, telephone_number: '')).to_not be_valid + end + + it 'must not exceed 50 characters' do + expect(build(:principal, telephone_number: '1' * 51)).to_not be_valid + end + + it 'must contain only digits and spaces' do + expect(build(:principal, telephone_number: '1 abcd')).to_not be_valid + end + end + end + + describe 'dough #field_order' do + let(:fields) do + [ + :fca_number, + :first_name, + :last_name, + :job_title, + :email_address, + :telephone_number, + :confirmed_disclaimer + ] + end + + it 'orders fields per the identification form' do + expect(principal.field_order).to contain_exactly(*fields) + end + end + + describe '#destroy' do + it 'cascades to the firm' do + firm = principal.firm + principal.destroy + expect(Firm.where(id: firm.id)).to be_empty + end + end + + describe '#onboarded?' do + context 'when no firms are publishable' do + before do + FactoryGirl.build(:invalid_firm, + fca_number: principal.fca_number, + parent: principal.firm).save(validate: false) + + expect(principal.main_firm_with_trading_names).to have(2).items + expect(principal.main_firm_with_trading_names) + .to all(have_attributes(publishable?: false)) + end + + it 'returns false' do + expect(principal.onboarded?).to be(false) + end + end + + context 'when one firm is publishable' do + before do + FactoryGirl.create(:publishable_firm, + fca_number: principal.fca_number, + parent: principal.firm) + + expect(principal.main_firm_with_trading_names).to have(2).items + expect(principal.firm).not_to be_publishable + expect(principal.firm.trading_names.first).to be_publishable + end + + it 'returns true' do + expect(principal.onboarded?).to be(true) + end + end + end +end diff --git a/spec/models/qualification_spec.rb b/spec/models/qualification_spec.rb new file mode 100644 index 000000000..a7c1e6571 --- /dev/null +++ b/spec/models/qualification_spec.rb @@ -0,0 +1,4 @@ +RSpec.describe Qualification do + it_behaves_like 'reference data' + it_behaves_like 'friendly named' +end diff --git a/spec/models/snapshot_spec.rb b/spec/models/snapshot_spec.rb new file mode 100644 index 000000000..6d0f69674 --- /dev/null +++ b/spec/models/snapshot_spec.rb @@ -0,0 +1,763 @@ +RSpec.describe Snapshot do + let(:england_postcode) { 'EC1N 2TD' } + let(:scotland_postcode) { 'EH3 9DR' } + let(:wales_postcode) { 'CF14 4HY' } + let(:northern_ireland_postcode) { 'BT1 6DP' } + + describe '#save_and_run' do + it 'runs all queries and sets the count of their result' do + query_methods = subject.metrics_in_order.map { |metric| "query_#{metric}" } + query_methods.each_with_index do |query_method, index| + allow(subject).to receive(query_method).and_return(Array.new(index)) + end + + subject.run_queries_and_save + + subject.metrics_in_order.each_with_index do |metric, index| + expect(subject.send(metric)).to eq(index) + end + end + end + + describe '#query_firms_with_no_minimum_fee' do + before do + FactoryGirl.create(:firm, minimum_fixed_fee: 0) + FactoryGirl.create(:firm, minimum_fixed_fee: 0) + FactoryGirl.create(:firm, minimum_fixed_fee: 500) + end + + it { expect(subject.query_firms_with_no_minimum_fee.count).to eq(2) } + end + + describe '#query_firms_with_min_fee_between_1_500' do + before do + FactoryGirl.create(:firm, minimum_fixed_fee: 0) + FactoryGirl.create(:firm, minimum_fixed_fee: 1) + FactoryGirl.create(:firm, minimum_fixed_fee: 500) + FactoryGirl.create(:firm, minimum_fixed_fee: 501) + end + + it { expect(subject.query_firms_with_min_fee_between_1_500.count).to eq(2) } + end + + describe '#query_firms_with_min_fee_between_501_1000' do + before do + FactoryGirl.create(:firm, minimum_fixed_fee: 500) + FactoryGirl.create(:firm, minimum_fixed_fee: 501) + FactoryGirl.create(:firm, minimum_fixed_fee: 750) + FactoryGirl.create(:firm, minimum_fixed_fee: 1000) + FactoryGirl.create(:firm, minimum_fixed_fee: 1001) + end + + it { expect(subject.query_firms_with_min_fee_between_501_1000.count).to eq(3) } + end + + describe '#query_firms_any_pot_size' do + before do + under_50k_size = FactoryGirl.create(:investment_size, name: 'Under £50,000') + other_size = FactoryGirl.create(:investment_size) + FactoryGirl.create(:firm, investment_sizes: [under_50k_size]) + FactoryGirl.create(:firm, investment_sizes: [under_50k_size, other_size]) + FactoryGirl.create(:firm, investment_sizes: [other_size]) + end + + it { expect(subject.query_firms_any_pot_size.count).to eq(2) } + end + + describe '#query_firms_any_pot_size_min_fee_less_than_500' do + before do + under_50k_size = FactoryGirl.create(:investment_size, name: 'Under £50,000') + other_size = FactoryGirl.create(:investment_size) + FactoryGirl.create(:firm, minimum_fixed_fee: 0, investment_sizes: [under_50k_size]) + FactoryGirl.create(:firm, minimum_fixed_fee: 499, investment_sizes: [other_size]) + FactoryGirl.create(:firm, minimum_fixed_fee: 499, investment_sizes: [under_50k_size]) + FactoryGirl.create(:firm, minimum_fixed_fee: 500, investment_sizes: [under_50k_size]) + end + + it { expect(subject.query_firms_any_pot_size_min_fee_less_than_500.count).to eq(2) } + end + + describe '#query_registered_firms' do + before do + FactoryGirl.create(:firm) + FactoryGirl.create(:firm) + FactoryGirl.build(:firm, Firm::REGISTERED_MARKER_FIELD => nil).tap { |f| f.save(validate: false) } + end + + it { expect(subject.query_registered_firms.count).to eq(2) } + end + + describe '#query_published_firms' do + before do + FactoryGirl.create(:firm) + FactoryGirl.create(:firm, :without_offices) + end + + it { expect(subject.query_published_firms.count).to eq(1) } + end + + describe '#query_firms_offering_face_to_face_advice' do + before do + FactoryGirl.create(:firm) + FactoryGirl.create(:firm) + FactoryGirl.create(:firm, :with_remote_advice) + end + + it { expect(subject.query_firms_offering_face_to_face_advice.count).to eq(2) } + end + + describe '#query_firms_offering_remote_advice' do + before do + FactoryGirl.create(:firm, :with_remote_advice) + FactoryGirl.create(:firm, :with_remote_advice) + FactoryGirl.create(:firm) + end + + it { expect(subject.query_firms_offering_remote_advice.count).to eq(2) } + end + + describe '#query_firms_in_england' do + let(:firm1) { FactoryGirl.create(:firm) } + let(:firm2) { FactoryGirl.create(:firm) } + let(:firm3) { FactoryGirl.create(:firm) } + + before do + firm1.offices.first.update(address_postcode: england_postcode) + firm2.offices.first.update(address_postcode: england_postcode) + firm3.offices.first.update(address_postcode: scotland_postcode) + end + + it do + VCR.use_cassette("england_and_scotland_postcode") do + firms = subject.query_firms_in_england + expect(firms).to match([firm1, firm2]) + expect(firms).not_to include(firm3) + end + end + end + + describe '#query_firms_in_scotland' do + let(:firm1) { FactoryGirl.create(:firm) } + let(:firm2) { FactoryGirl.create(:firm) } + let(:firm3) { FactoryGirl.create(:firm) } + + before do + firm1.offices.first.update(address_postcode: scotland_postcode) + firm2.offices.first.update(address_postcode: scotland_postcode) + firm3.offices.first.update(address_postcode: england_postcode) + end + + it do + VCR.use_cassette("scotland_and_england_postcode") do + firms = subject.query_firms_in_scotland + expect(firms).to match([firm1, firm2]) + expect(firms).not_to include(firm3) + end + end + end + + describe '#query_firms_in_wales' do + let(:firm1) { FactoryGirl.create(:firm) } + let(:firm2) { FactoryGirl.create(:firm) } + let(:firm3) { FactoryGirl.create(:firm) } + + before do + firm1.offices.first.update(address_postcode: wales_postcode) + firm2.offices.first.update(address_postcode: wales_postcode) + firm3.offices.first.update(address_postcode: england_postcode) + end + + it do + VCR.use_cassette("wales_and_england_postcode") do + firms = subject.query_firms_in_wales + expect(firms).to match([firm1, firm2]) + expect(firms).not_to include(firm3) + end + end + end + + describe '#query_firms_in_northern_ireland' do + let(:firm1) { FactoryGirl.create(:firm) } + let(:firm2) { FactoryGirl.create(:firm) } + let(:firm3) { FactoryGirl.create(:firm) } + + before do + firm1.offices.first.update(address_postcode: northern_ireland_postcode) + firm2.offices.first.update(address_postcode: northern_ireland_postcode) + firm3.offices.first.update(address_postcode: england_postcode) + end + + it do + VCR.use_cassette("northern_ireland_and_england_postcode") do + firms = subject.query_firms_in_northern_ireland + expect(firms).to match([firm1, firm2]) + expect(firms).not_to include(firm3) + end + end + end + + describe '#query_firms_providing_retirement_income_products' do + before do + FactoryGirl.create(:firm) + FactoryGirl.create(:firm) + FactoryGirl.create(:firm, retirement_income_products_flag: false) + end + + it { expect(subject.query_firms_providing_retirement_income_products.count).to eq(2) } + end + + describe '#query_firms_providing_pension_transfer' do + before do + FactoryGirl.create(:firm) + FactoryGirl.create(:firm) + FactoryGirl.create(:firm, pension_transfer_flag: false) + end + + it { expect(subject.query_firms_providing_pension_transfer.count).to eq(2) } + end + + describe '#query_firms_providing_long_term_care' do + before do + FactoryGirl.create(:firm) + FactoryGirl.create(:firm) + FactoryGirl.create(:firm, long_term_care_flag: false) + end + + it { expect(subject.query_firms_providing_long_term_care.count).to eq(2) } + end + + describe '#query_firms_providing_equity_release' do + before do + FactoryGirl.create(:firm) + FactoryGirl.create(:firm) + FactoryGirl.create(:firm, equity_release_flag: false) + end + + it { expect(subject.query_firms_providing_equity_release.count).to eq(2) } + end + + describe '#query_firms_providing_inheritance_tax_and_estate_planning' do + before do + FactoryGirl.create(:firm) + FactoryGirl.create(:firm) + FactoryGirl.create(:firm, inheritance_tax_and_estate_planning_flag: false) + end + + it { expect(subject.query_firms_providing_inheritance_tax_and_estate_planning.count).to eq(2) } + end + + describe '#query_firms_providing_wills_and_probate' do + before do + FactoryGirl.create(:firm) + FactoryGirl.create(:firm) + FactoryGirl.create(:firm, wills_and_probate_flag: false) + end + + it { expect(subject.query_firms_providing_wills_and_probate.count).to eq(2) } + end + + describe '#query_firms_providing_ethical_investing' do + before do + FactoryGirl.create(:firm, ethical_investing_flag: true) + FactoryGirl.create(:firm, ethical_investing_flag: true) + FactoryGirl.create(:firm, ethical_investing_flag: false) + end + + it { expect(subject.query_firms_providing_ethical_investing.count).to eq(2) } + end + + describe '#query_firms_providing_sharia_investing' do + before do + FactoryGirl.create(:firm, sharia_investing_flag: true) + FactoryGirl.create(:firm, sharia_investing_flag: true) + FactoryGirl.create(:firm, sharia_investing_flag: false) + end + + it { expect(subject.query_firms_providing_sharia_investing.count).to eq(2) } + end + + describe '#query_firms_providing_workplace_financial_advice' do + before do + FactoryGirl.create(:firm, workplace_financial_advice_flag: true) + FactoryGirl.create(:firm, workplace_financial_advice_flag: false) + FactoryGirl.create(:firm, workplace_financial_advice_flag: true) + end + + it { expect(subject.query_firms_providing_workplace_financial_advice.count).to eq(2) } + end + + describe '#query_firms_providing_non_uk_residents' do + before do + FactoryGirl.create(:firm, non_uk_residents_flag: false) + FactoryGirl.create(:firm, non_uk_residents_flag: true) + FactoryGirl.create(:firm, non_uk_residents_flag: true) + end + + it { expect(subject.query_firms_providing_non_uk_residents.count).to eq(2) } + end + + describe '#query_firms_offering_languages_other_than_english' do + before do + FactoryGirl.create(:firm, languages: []) + FactoryGirl.create(:firm, languages: ['fra']) + FactoryGirl.create(:firm, languages: ['fra']) + end + + it { expect(subject.query_firms_offering_languages_other_than_english.count).to eq(2) } + end + + describe '#query_offices_with_disabled_access' do + before do + firm1 = FactoryGirl.create(:firm, offices_count: 1) + firm1.offices.first.update(disabled_access: false) + + firm2 = FactoryGirl.create(:firm, offices_count: 1) + firm2.offices.first.update(disabled_access: true) + + firm3 = FactoryGirl.create(:firm, :without_advisers, offices_count: 1) + firm3.offices.first.update(disabled_access: true) + end + + it { expect(subject.query_offices_with_disabled_access.count).to eq(1) } + end + + describe '#query_registered_advisers' do + before do + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser) + end + + it { expect(subject.query_registered_advisers.count).to eq(2) } + end + + describe '#query_advisers_in_england' do + let!(:adviser1) { FactoryGirl.create(:adviser, postcode: england_postcode) } + let!(:adviser2) { FactoryGirl.create(:adviser, postcode: england_postcode) } + let!(:adviser3) { FactoryGirl.create(:adviser, postcode: scotland_postcode) } + + it do + VCR.use_cassette("england_and_scotland_postcode") do + advisers = subject.query_advisers_in_england + expect(advisers).to match([adviser1, adviser2]) + expect(advisers).not_to include(adviser3) + end + end + end + + describe '#query_advisers_in_scotland' do + let!(:adviser1) { FactoryGirl.create(:adviser, postcode: scotland_postcode) } + let!(:adviser2) { FactoryGirl.create(:adviser, postcode: scotland_postcode) } + let!(:adviser3) { FactoryGirl.create(:adviser, postcode: england_postcode) } + + it do + VCR.use_cassette("scotland_and_england_postcode") do + advisers = subject.query_advisers_in_scotland + expect(advisers).to match([adviser1, adviser2]) + expect(advisers).not_to include(adviser3) + end + end + end + + describe '#query_advisers_in_wales' do + let!(:adviser1) { FactoryGirl.create(:adviser, postcode: wales_postcode) } + let!(:adviser2) { FactoryGirl.create(:adviser, postcode: wales_postcode) } + let!(:adviser3) { FactoryGirl.create(:adviser, postcode: england_postcode) } + + it do + VCR.use_cassette("wales_and_england_postcode") do + advisers = subject.query_advisers_in_wales + expect(advisers).to match([adviser1, adviser2]) + expect(advisers).not_to include(adviser3) + end + end + end + + describe '#query_advisers_in_northern_ireland' do + let!(:adviser1) { FactoryGirl.create(:adviser, postcode: northern_ireland_postcode) } + let!(:adviser2) { FactoryGirl.create(:adviser, postcode: northern_ireland_postcode) } + let!(:adviser3) { FactoryGirl.create(:adviser, postcode: england_postcode) } + + it do + VCR.use_cassette("northern_ireland_and_england_postcode") do + advisers = subject.query_advisers_in_northern_ireland + expect(advisers).to match([adviser1, adviser2]) + expect(advisers).not_to include(adviser3) + end + end + end + + describe '#query_advisers_who_travel_5_miles' do + before do + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['5 miles']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['5 miles']) + end + + it { expect(subject.query_advisers_who_travel_5_miles.count).to eq(2) } + end + + describe '#query_advisers_who_travel_10_miles' do + before do + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['10 miles']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['10 miles']) + end + + it { expect(subject.query_advisers_who_travel_10_miles.count).to eq(2) } + end + + describe '#query_advisers_who_travel_25_miles' do + before do + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['25 miles']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['25 miles']) + end + + it { expect(subject.query_advisers_who_travel_25_miles.count).to eq(2) } + end + + describe '#query_advisers_who_travel_50_miles' do + before do + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['50 miles']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['50 miles']) + end + + it { expect(subject.query_advisers_who_travel_50_miles.count).to eq(2) } + end + + describe '#query_advisers_who_travel_100_miles' do + before do + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['100 miles']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['100 miles']) + end + + it { expect(subject.query_advisers_who_travel_100_miles.count).to eq(2) } + end + + describe '#query_advisers_who_travel_150_miles' do + before do + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['150 miles']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['150 miles']) + end + + it { expect(subject.query_advisers_who_travel_150_miles.count).to eq(2) } + end + + describe '#query_advisers_who_travel_200_miles' do + before do + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['200 miles']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['200 miles']) + end + + it { expect(subject.query_advisers_who_travel_200_miles.count).to eq(2) } + end + + describe '#query_advisers_who_travel_250_miles' do + before do + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['250 miles']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['250 miles']) + end + + it { expect(subject.query_advisers_who_travel_250_miles.count).to eq(2) } + end + + describe '#query_advisers_who_travel_uk_wide' do + before do + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['5 miles']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['UK wide']) + FactoryGirl.create(:adviser, travel_distance: TravelDistance.all['UK wide']) + end + + it { expect(subject.query_advisers_who_travel_uk_wide.count).to eq(2) } + end + + describe '#query_advisers_accredited_in_solla' do + before do + accreditation = FactoryGirl.create(:accreditation, name: 'SOLLA') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, accreditations: [accreditation]) + FactoryGirl.create(:adviser, accreditations: [accreditation]) + end + + it { expect(subject.query_advisers_accredited_in_solla.count).to eq(2) } + end + + describe '#query_advisers_accredited_in_later_life_academy' do + before do + accreditation = FactoryGirl.create(:accreditation, name: 'Later Life Academy') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, accreditations: [accreditation]) + FactoryGirl.create(:adviser, accreditations: [accreditation]) + end + + it { expect(subject.query_advisers_accredited_in_later_life_academy.count).to eq(2) } + end + + describe '#query_advisers_accredited_in_iso22222' do + before do + accreditation = FactoryGirl.create(:accreditation, name: 'ISO 22222') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, accreditations: [accreditation]) + FactoryGirl.create(:adviser, accreditations: [accreditation]) + end + + it { expect(subject.query_advisers_accredited_in_iso22222.count).to eq(2) } + end + + describe '#query_advisers_accredited_in_bs8577' do + before do + accreditation = FactoryGirl.create(:accreditation, name: 'British Standard in Financial Planning BS8577') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, accreditations: [accreditation]) + FactoryGirl.create(:adviser, accreditations: [accreditation]) + end + + it { expect(subject.query_advisers_accredited_in_bs8577.count).to eq(2) } + end + + describe '#query_advisers_with_qualification_in_level_4' do + before do + qualification = FactoryGirl.create(:qualification, name: 'Level 4 (DipPFS, DipFA® or equivalent)') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, qualifications: [qualification]) + FactoryGirl.create(:adviser, qualifications: [qualification]) + end + + it { expect(subject.query_advisers_with_qualification_in_level_4.count).to eq(2) } + end + + describe '#query_advisers_with_qualification_in_level_6' do + before do + qualification = FactoryGirl.create(:qualification, name: 'Level 6 (APFS, Adv DipFA®)') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, qualifications: [qualification]) + FactoryGirl.create(:adviser, qualifications: [qualification]) + end + + it { expect(subject.query_advisers_with_qualification_in_level_6.count).to eq(2) } + end + + describe '#query_advisers_with_qualification_in_chartered_financial_planner' do + before do + qualification = FactoryGirl.create(:qualification, name: 'Chartered Financial Planner') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, qualifications: [qualification]) + FactoryGirl.create(:adviser, qualifications: [qualification]) + end + + it { expect(subject.query_advisers_with_qualification_in_chartered_financial_planner.count).to eq(2) } + end + + describe '#query_advisers_with_qualification_in_certified_financial_planner' do + before do + qualification = FactoryGirl.create(:qualification, name: 'Certified Financial Planner') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, qualifications: [qualification]) + FactoryGirl.create(:adviser, qualifications: [qualification]) + end + + it { expect(subject.query_advisers_with_qualification_in_certified_financial_planner.count).to eq(2) } + end + + describe '#query_advisers_with_qualification_in_pension_transfer' do + before do + qualification = FactoryGirl.create(:qualification, name: 'Pension transfer qualifications - holder of G60, AF3, AwPETR®, or equivalent') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, qualifications: [qualification]) + FactoryGirl.create(:adviser, qualifications: [qualification]) + end + + it { expect(subject.query_advisers_with_qualification_in_pension_transfer.count).to eq(2) } + end + + describe '#query_advisers_with_qualification_in_equity_release' do + before do + qualification = FactoryGirl.create(:qualification, name: 'Equity release qualifications i.e. holder of Certificate in Equity Release or equivalent') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, qualifications: [qualification]) + FactoryGirl.create(:adviser, qualifications: [qualification]) + end + + it { expect(subject.query_advisers_with_qualification_in_equity_release.count).to eq(2) } + end + + describe '#query_advisers_with_qualification_in_long_term_care_planning' do + before do + qualification = FactoryGirl.create(:qualification, name: 'Long term care planning qualifications i.e. holder of CF8, CeLTCI®. or equivalent') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, qualifications: [qualification]) + FactoryGirl.create(:adviser, qualifications: [qualification]) + end + + it { expect(subject.query_advisers_with_qualification_in_long_term_care_planning.count).to eq(2) } + end + + describe '#query_advisers_with_qualification_in_tep' do + before do + qualification = FactoryGirl.create(:qualification, name: 'Holder of Trust and Estate Practitioner qualification (TEP) i.e. full member of STEP') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, qualifications: [qualification]) + FactoryGirl.create(:adviser, qualifications: [qualification]) + end + + it { expect(subject.query_advisers_with_qualification_in_tep.count).to eq(2) } + end + + describe '#query_advisers_with_qualification_in_fcii' do + before do + qualification = FactoryGirl.create(:qualification, name: 'Fellow of the Chartered Insurance Institute (FCII)') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, qualifications: [qualification]) + FactoryGirl.create(:adviser, qualifications: [qualification]) + end + + it { expect(subject.query_advisers_with_qualification_in_fcii.count).to eq(2) } + end + + describe '#query_advisers_part_of_personal_finance_society' do + before do + professional_body = FactoryGirl.create(:professional_body, name: 'Personal Finance Society / Chartered Insurance Institute') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + end + + it { expect(subject.query_advisers_part_of_personal_finance_society.count).to eq(2) } + end + + describe '#query_advisers_part_of_institute_financial_planning' do + before do + professional_body = FactoryGirl.create(:professional_body, name: 'Institute of Financial Planning') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + end + + it { expect(subject.query_advisers_part_of_institute_financial_planning.count).to eq(2) } + end + + describe '#query_advisers_part_of_institute_financial_services' do + before do + professional_body = FactoryGirl.create(:professional_body, name: 'Institute of Financial Services') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + end + + it { expect(subject.query_advisers_part_of_institute_financial_services.count).to eq(2) } + end + + describe '#query_advisers_part_of_ci_bankers_scotland' do + before do + professional_body = FactoryGirl.create(:professional_body, name: 'The Chartered Institute of Bankers in Scotland') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + end + + it { expect(subject.query_advisers_part_of_ci_bankers_scotland.count).to eq(2) } + end + + describe '#query_advisers_part_of_ci_securities_and_investments' do + before do + professional_body = FactoryGirl.create(:professional_body, name: 'The Chartered Institute for Securities and Investments') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + end + + it { expect(subject.query_advisers_part_of_ci_securities_and_investments.count).to eq(2) } + end + + describe '#query_advisers_part_of_cfa_institute' do + before do + professional_body = FactoryGirl.create(:professional_body, name: 'CFA Institute') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + end + + it { expect(subject.query_advisers_part_of_cfa_institute.count).to eq(2) } + end + + describe '#query_advisers_part_of_chartered_accountants' do + before do + professional_body = FactoryGirl.create(:professional_body, name: 'Institute of Chartered Accountants for England and Wales') + FactoryGirl.create(:adviser) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + FactoryGirl.create(:adviser, professional_bodies: [professional_body]) + end + + it { expect(subject.query_advisers_part_of_chartered_accountants.count).to eq(2) } + end + + describe '#metrics_in_order' do + it 'has the configured order' do + expect(subject.metrics_in_order).to eq([ + :firms_with_no_minimum_fee, + :firms_with_min_fee_between_1_500, + :firms_with_min_fee_between_501_1000, + :firms_any_pot_size, + :firms_any_pot_size_min_fee_less_than_500, + :registered_firms, + :published_firms, + :firms_offering_face_to_face_advice, + :firms_offering_remote_advice, + :firms_in_england, + :firms_in_scotland, + :firms_in_wales, + :firms_in_northern_ireland, + :firms_providing_retirement_income_products, + :firms_providing_pension_transfer, + :firms_providing_long_term_care, + :firms_providing_equity_release, + :firms_providing_inheritance_tax_and_estate_planning, + :firms_providing_wills_and_probate, + :firms_providing_ethical_investing, + :firms_providing_sharia_investing, + :firms_providing_workplace_financial_advice, + :firms_providing_non_uk_residents, + :firms_offering_languages_other_than_english, + :offices_with_disabled_access, + :registered_advisers, + :advisers_in_england, + :advisers_in_scotland, + :advisers_in_wales, + :advisers_in_northern_ireland, + :advisers_who_travel_5_miles, + :advisers_who_travel_10_miles, + :advisers_who_travel_25_miles, + :advisers_who_travel_50_miles, + :advisers_who_travel_100_miles, + :advisers_who_travel_150_miles, + :advisers_who_travel_200_miles, + :advisers_who_travel_250_miles, + :advisers_who_travel_uk_wide, + :advisers_accredited_in_solla, + :advisers_accredited_in_later_life_academy, + :advisers_accredited_in_iso22222, + :advisers_accredited_in_bs8577, + :advisers_with_qualification_in_level_4, + :advisers_with_qualification_in_level_6, + :advisers_with_qualification_in_chartered_financial_planner, + :advisers_with_qualification_in_certified_financial_planner, + :advisers_with_qualification_in_pension_transfer, + :advisers_with_qualification_in_equity_release, + :advisers_with_qualification_in_long_term_care_planning, + :advisers_with_qualification_in_tep, + :advisers_with_qualification_in_fcii, + :advisers_part_of_personal_finance_society, + :advisers_part_of_institute_financial_planning, + :advisers_part_of_institute_financial_services, + :advisers_part_of_ci_bankers_scotland, + :advisers_part_of_ci_securities_and_investments, + :advisers_part_of_cfa_institute, :advisers_part_of_chartered_accountants + ]) + end + end +end diff --git a/spec/serializers/firm_serializer_spec.rb b/spec/serializers/firm_serializer_spec.rb new file mode 100644 index 000000000..41ecc169f --- /dev/null +++ b/spec/serializers/firm_serializer_spec.rb @@ -0,0 +1,141 @@ +RSpec.describe FirmSerializer do + let(:firm) do + FactoryGirl.create(:firm_with_principal) + end + + describe 'the serialized json' do + subject { described_class.new(firm).as_json } + + it 'exposes `_id`' do + expect(subject[:_id]).to eql(firm.id) + end + + it 'exposes `registered_name`' do + expect(subject[:registered_name]).to eql(firm.registered_name) + end + + it 'exposes `postcode_searchable`' do + expect(subject[:postcode_searchable]).to eql(firm.postcode_searchable?) + end + + it 'exposes `telephone_number`' do + expect(subject[:telephone_number]).to eql(firm.main_office.telephone_number) + end + + it 'exposes `website_address`' do + expect(subject[:website_address]).to eql(firm.website_address) + end + + it 'exposes `email_address`' do + expect(subject[:email_address]).to eql(firm.main_office.email_address) + end + + it 'exposes `free_initial_meeting`' do + expect(subject[:free_initial_meeting]).to eql(firm.free_initial_meeting) + end + + it 'exposes `minimum_fixed_fee`' do + expect(subject[:minimum_fixed_fee]).to eql(firm.minimum_fixed_fee) + end + + it 'exposes `retirement_income_products`' do + expect(subject[:retirement_income_products]).to eq(firm.retirement_income_products_flag ? 100 : 0) + end + + it 'exposes `pension_transfer`' do + expect(subject[:pension_transfer]).to eq(firm.pension_transfer_flag ? 100 : 0) + end + + it 'exposes `options_when_paying_for_care`' do + expect(subject[:options_when_paying_for_care]).to eq(firm.long_term_care_flag ? 100 : 0) + end + + it 'exposes `equity_release`' do + expect(subject[:equity_release]).to eq(firm.equity_release_flag ? 100 : 0) + end + + it 'exposes `inheritance_tax_planning`' do + expect(subject[:inheritance_tax_planning]).to eq(firm.inheritance_tax_and_estate_planning_flag ? 100 : 0) + end + + it 'exposes `wills_and_probate`' do + expect(subject[:wills_and_probate]).to eq(firm.wills_and_probate_flag ? 100 : 0) + end + + it 'exposes `other_advice_method_ids`' do + expect(subject[:other_advice_methods]).to eql(firm.other_advice_method_ids) + end + + it 'exposes `investment_sizes`' do + expect(subject[:investment_sizes]).to eql(firm.investment_size_ids) + end + + it 'exposes `in_person_advice_methods`' do + expect(subject[:in_person_advice_methods]).to eql(firm.in_person_advice_method_ids) + end + + it 'exposes `adviser_qualification_ids`' do + expect(subject[:adviser_qualification_ids]).to eql(firm.qualification_ids) + end + + it 'exposes `adviser_creditation_ids`' do + expect(subject[:adviser_accreditation_ids]).to eql(firm.accreditation_ids) + end + + it 'exposes "ethical_investing_flag"' do + expect(subject[:ethical_investing_flag]).to eql(firm.ethical_investing_flag) + end + + it 'exposes "sharia_investing_flag"' do + expect(subject[:sharia_investing_flag]).to eql(firm.sharia_investing_flag) + end + + it 'exposes "workplace_financial_advice_flag"' do + expect(subject[:workplace_financial_advice_flag]).to eql(firm.workplace_financial_advice_flag) + end + + it 'exposes "non_uk_residents_flag"' do + expect(subject[:non_uk_residents_flag]).to eql(firm.non_uk_residents_flag) + end + + describe 'advisers' do + before { create(:adviser, firm: firm, latitude: nil, longitude: nil) } + + it 'only includes geocoded records' do + expect(subject[:advisers].count).to eq(1) + end + end + + describe 'offices' do + before { create(:office, firm: firm) } + + it 'includes offices (main and additionally created one)' do + expect(subject[:offices].count).to eq(2) + end + + context 'when there are offices that have not been geocoded' do + before { firm.offices.last.update!(latitude: nil, longitude: nil) } + + it 'only includes geocoded records' do + expect(subject[:offices].count).to eq(1) + end + end + end + + describe 'languages' do + context 'when languages have been selected' do + before { firm.languages = ['fra', 'deu'] } + + it 'serializes them' do + expect(subject[:languages]).to eq(['fra', 'deu']) + end + end + + context 'when no languages have been selected' do + it 'serializes an empty list' do + expect(subject[:languages]).to eq([]) + end + end + end + end +end diff --git a/spec/serializers/serializers/adviser_serializer_spec.rb b/spec/serializers/serializers/adviser_serializer_spec.rb new file mode 100644 index 000000000..e91913658 --- /dev/null +++ b/spec/serializers/serializers/adviser_serializer_spec.rb @@ -0,0 +1,57 @@ +RSpec.describe AdviserSerializer do + let(:adviser) { create(:adviser) } + + describe 'the serialized json' do + subject { described_class.new(adviser).as_json } + + it 'exposes `_id`' do + expect(subject[:_id]).to eql(adviser.id) + end + + it 'exposes `name`' do + expect(subject[:name]).to eql(adviser.name) + end + + it 'exposes `postcode`' do + expect(subject[:postcode]).to eql(adviser.postcode) + end + + it 'exposes `range`' do + expect(subject[:range]).to eql(adviser.travel_distance) + end + + it 'exposes `location`' do + expect(subject[:location][:lat]).to eql(adviser.latitude) + expect(subject[:location][:lon]).to eql(adviser.longitude) + end + + it 'exposes `range_location`' do + expect(subject[:range_location][:coordinates]).to eq([adviser.longitude, adviser.latitude]) + expect(subject[:range_location][:radius]).to eq('650miles') + end + + context 'qualifications' do + let(:qualifications) { [ + Qualification.create(id: 1, order: 1, name: 'First Qualification'), + Qualification.create(id: 2, order: 2, name: 'Second Qualification') + ] } + let(:adviser) { create(:adviser, qualifications: qualifications ) } + + it 'exposes `qualification_ids`' do + expect(subject[:qualification_ids]).to eq([qualifications.first.id, qualifications.last.id]) + end + end + + context 'accreditations' do + let(:accreditations) { [ + Accreditation.create!(id: 1, name: 'First Accreditation', order: 1), + Accreditation.create!(id: 2, name: 'Second Accreditation', order: 2) + ] } + let(:adviser) { create(:adviser, accreditations: accreditations ) } + + it 'exposes `qualification_ids`' do + expect(subject[:accreditation_ids]).to eq([accreditations.first.id, accreditations.last.id]) + end + end + end +end diff --git a/spec/serializers/serializers/firm_serializer_spec.rb b/spec/serializers/serializers/firm_serializer_spec.rb new file mode 100644 index 000000000..41ecc169f --- /dev/null +++ b/spec/serializers/serializers/firm_serializer_spec.rb @@ -0,0 +1,141 @@ +RSpec.describe FirmSerializer do + let(:firm) do + FactoryGirl.create(:firm_with_principal) + end + + describe 'the serialized json' do + subject { described_class.new(firm).as_json } + + it 'exposes `_id`' do + expect(subject[:_id]).to eql(firm.id) + end + + it 'exposes `registered_name`' do + expect(subject[:registered_name]).to eql(firm.registered_name) + end + + it 'exposes `postcode_searchable`' do + expect(subject[:postcode_searchable]).to eql(firm.postcode_searchable?) + end + + it 'exposes `telephone_number`' do + expect(subject[:telephone_number]).to eql(firm.main_office.telephone_number) + end + + it 'exposes `website_address`' do + expect(subject[:website_address]).to eql(firm.website_address) + end + + it 'exposes `email_address`' do + expect(subject[:email_address]).to eql(firm.main_office.email_address) + end + + it 'exposes `free_initial_meeting`' do + expect(subject[:free_initial_meeting]).to eql(firm.free_initial_meeting) + end + + it 'exposes `minimum_fixed_fee`' do + expect(subject[:minimum_fixed_fee]).to eql(firm.minimum_fixed_fee) + end + + it 'exposes `retirement_income_products`' do + expect(subject[:retirement_income_products]).to eq(firm.retirement_income_products_flag ? 100 : 0) + end + + it 'exposes `pension_transfer`' do + expect(subject[:pension_transfer]).to eq(firm.pension_transfer_flag ? 100 : 0) + end + + it 'exposes `options_when_paying_for_care`' do + expect(subject[:options_when_paying_for_care]).to eq(firm.long_term_care_flag ? 100 : 0) + end + + it 'exposes `equity_release`' do + expect(subject[:equity_release]).to eq(firm.equity_release_flag ? 100 : 0) + end + + it 'exposes `inheritance_tax_planning`' do + expect(subject[:inheritance_tax_planning]).to eq(firm.inheritance_tax_and_estate_planning_flag ? 100 : 0) + end + + it 'exposes `wills_and_probate`' do + expect(subject[:wills_and_probate]).to eq(firm.wills_and_probate_flag ? 100 : 0) + end + + it 'exposes `other_advice_method_ids`' do + expect(subject[:other_advice_methods]).to eql(firm.other_advice_method_ids) + end + + it 'exposes `investment_sizes`' do + expect(subject[:investment_sizes]).to eql(firm.investment_size_ids) + end + + it 'exposes `in_person_advice_methods`' do + expect(subject[:in_person_advice_methods]).to eql(firm.in_person_advice_method_ids) + end + + it 'exposes `adviser_qualification_ids`' do + expect(subject[:adviser_qualification_ids]).to eql(firm.qualification_ids) + end + + it 'exposes `adviser_creditation_ids`' do + expect(subject[:adviser_accreditation_ids]).to eql(firm.accreditation_ids) + end + + it 'exposes "ethical_investing_flag"' do + expect(subject[:ethical_investing_flag]).to eql(firm.ethical_investing_flag) + end + + it 'exposes "sharia_investing_flag"' do + expect(subject[:sharia_investing_flag]).to eql(firm.sharia_investing_flag) + end + + it 'exposes "workplace_financial_advice_flag"' do + expect(subject[:workplace_financial_advice_flag]).to eql(firm.workplace_financial_advice_flag) + end + + it 'exposes "non_uk_residents_flag"' do + expect(subject[:non_uk_residents_flag]).to eql(firm.non_uk_residents_flag) + end + + describe 'advisers' do + before { create(:adviser, firm: firm, latitude: nil, longitude: nil) } + + it 'only includes geocoded records' do + expect(subject[:advisers].count).to eq(1) + end + end + + describe 'offices' do + before { create(:office, firm: firm) } + + it 'includes offices (main and additionally created one)' do + expect(subject[:offices].count).to eq(2) + end + + context 'when there are offices that have not been geocoded' do + before { firm.offices.last.update!(latitude: nil, longitude: nil) } + + it 'only includes geocoded records' do + expect(subject[:offices].count).to eq(1) + end + end + end + + describe 'languages' do + context 'when languages have been selected' do + before { firm.languages = ['fra', 'deu'] } + + it 'serializes them' do + expect(subject[:languages]).to eq(['fra', 'deu']) + end + end + + context 'when no languages have been selected' do + it 'serializes an empty list' do + expect(subject[:languages]).to eq([]) + end + end + end + end +end diff --git a/spec/serializers/serializers/office_serializer_spec.rb b/spec/serializers/serializers/office_serializer_spec.rb new file mode 100644 index 000000000..ac1d3cfd0 --- /dev/null +++ b/spec/serializers/serializers/office_serializer_spec.rb @@ -0,0 +1,20 @@ +RSpec.describe OfficeSerializer do + let(:office) { create(:firm).main_office } + + subject { described_class.new(office).as_json } + + describe 'the serialized json' do + specify { expect(subject[:_id]).to eq(office.id) } + specify { expect(subject[:address_line_one]).to eq(office.address_line_one) } + specify { expect(subject[:address_line_two]).to eq(office.address_line_two) } + specify { expect(subject[:address_town]).to eq(office.address_town) } + specify { expect(subject[:address_county]).to eq(office.address_county) } + specify { expect(subject[:address_postcode]).to eq(office.address_postcode) } + specify { expect(subject[:email_address]).to eq(office.email_address) } + specify { expect(subject[:telephone_number]).to eq(office.telephone_number) } + specify { expect(subject[:disabled_access]).to eq(office.disabled_access) } + specify { expect(subject[:location][:lat]).to eql(office.latitude) } + specify { expect(subject[:location][:lon]).to eql(office.longitude) } + specify { expect(subject[:website]).to eql(office.website) } + end +end diff --git a/spec/support/field_length_validation_helpers.rb b/spec/support/field_length_validation_helpers.rb new file mode 100644 index 000000000..c63991722 --- /dev/null +++ b/spec/support/field_length_validation_helpers.rb @@ -0,0 +1,10 @@ +module FieldLengthValidationHelpers + # USAGE: expect_length_of(subject, :field_name, 50).to be_valid + def expect_length_of(subject, field, length, fill_char: 'x') + num_chars_to_fill = (length - subject.send(field).length) + new_value = subject.send(field) + (fill_char * num_chars_to_fill) + subject.send("#{field}=", new_value) + + expect(subject) + end +end diff --git a/spec/support/shared_examples/friendly_named.rb b/spec/support/shared_examples/friendly_named.rb new file mode 100644 index 000000000..89af47f0f --- /dev/null +++ b/spec/support/shared_examples/friendly_named.rb @@ -0,0 +1,14 @@ +RSpec.shared_examples 'friendly named' do + let(:type) { described_class.model_name.i18n_key } + let(:method) { create(type, order: 1) } + + before do + I18n.backend.store_translations :en, type => { ordinal: { '1': 'Phone' } } + end + + subject { described_class.friendly_name(method.id) } + + it 'returns the shortened equivalent name' do + expect(subject).to eq('Phone') + end +end diff --git a/spec/support/shared_examples/geocodable_examples.rb b/spec/support/shared_examples/geocodable_examples.rb new file mode 100644 index 000000000..bf0357749 --- /dev/null +++ b/spec/support/shared_examples/geocodable_examples.rb @@ -0,0 +1,284 @@ +RSpec.shared_examples 'geocodable' do + describe 'the interface the geocodable must implement' do + subject { valid_new_geocodable } + + # needed for the ModelGeocoder + it { is_expected.to respond_to(:full_street_address) } + + # used by #needs_to_be_geocoded? + it { is_expected.to respond_to(:has_address_changes?) } + + # used by #geocode + it { is_expected.to respond_to(:add_geocoding_failed_error) } + end + + def modify_address(subject) + subject.send("#{address_field_name}=", address_field_updated_value) + expect(subject).to be_changed + end + + describe '#latitude=' do + let(:latitude) { Faker::Address.latitude } + + before { subject.latitude = latitude } + + it 'casts the value to a float rounded to six decimal places' do + expect(subject.latitude).to eql(latitude.to_f.round(6)) + end + + context 'when the value is nil' do + let(:latitude) { nil } + + it 'does not cast the value' do + expect(subject.latitude).to be_nil + end + end + end + + describe '#longitude=' do + let(:longitude) { Faker::Address.longitude } + + before { subject.longitude = longitude } + + it 'casts the value to a float rounded to six decimal places' do + expect(subject.longitude).to eql(longitude.to_f.round(6)) + end + + context 'when the value is nil' do + let(:longitude) { nil } + + it 'does not cast the value' do + expect(subject.longitude).to be_nil + end + end + end + + describe '#geocoded?' do + context 'when the subject has lat/long' do + before do + subject.latitude, subject.longitude = [1.0, 1.0] + end + + it 'is classed as geocoded' do + expect(subject.geocoded?).to be(true) + end + end + + context 'when the subject does not have lat/long' do + before do + subject.latitude, subject.longitude = [nil, nil] + end + + it 'is not classed as geocoded' do + expect(subject.geocoded?).to be(false) + end + end + end + + describe '#geocode' do + context 'when the subject is not valid' do + subject { invalid_geocodable } + + it 'does not call the geocoder' do + expect(ModelGeocoder).not_to receive(:geocode) + subject.geocode + end + + it 'returns false' do + expect(subject.geocode).to be(false) + end + end + + context 'when the subject is valid' do + subject { valid_new_geocodable } + + context 'when the subject does not need to be geocoded' do + before do + subject.coordinates = [1.0, 1.0] + subject.save! + end + + it 'does not call the geocoder' do + expect(ModelGeocoder).not_to receive(:geocode) + subject.geocode + end + + it 'returns true' do + expect(subject.geocode).to be(true) + end + end + + context 'when the subject needs to be geocoded' do + before do + subject.coordinates = nil + end + + context 'when geocoding succeeds' do + before do + allow(ModelGeocoder).to receive(:geocode).and_return([1.0, 1.0]) + end + + it 'returns true' do + expect(subject.geocode).to be(true) + end + + specify 'subject.errors.count is 0' do + subject.geocode + expect(subject.errors.count).to be(0) + end + + context 'no persistence' do + before do + subject.save! + expect(subject).not_to be_changed + expect(subject.coordinates).to eq([nil, nil]) + subject.geocode + end + + it 'sets the new coordinates on the in-memory instance' do + expect(subject.coordinates).to eq([1.0, 1.0]) + end + + it 'does not persist the changed fields' do + expect(subject).to be_changed + expect(subject.reload.coordinates).to eq([nil, nil]) + end + end + end + + context 'when geocoding fails' do + before do + allow(ModelGeocoder).to receive(:geocode).and_return(nil) + end + + it 'adds an error to subject.errors' do + subject.geocode + expect(subject.errors).to have_key(:geocoding) + end + + it 'returns false' do + expect(subject.geocode).to be(false) + end + + context 'no persistence' do + before do + subject.coordinates = [1.0, 1.0] + subject.save! + modify_address(subject) + subject.geocode + end + + it 'blanks out the in-memory instance coordinates' do + expect(subject.coordinates).to eq([nil, nil]) + end + + it 'does not persist the changed fields' do + expect(subject).to be_changed + expect(subject.reload.coordinates).to eq([1.0, 1.0]) + end + end + end + end + end + end + + describe '#needs_to_be_geocoded?' do + subject { saved_geocodable } + + context 'when the model has not been geocoded' do + before do + subject.coordinates = nil + expect(subject).not_to be_geocoded + end + + it 'returns true' do + expect(subject.needs_to_be_geocoded?).to be(true) + end + end + + context 'when the model has been geocoded' do + before do + subject.coordinates = [1.0, 1.0] + subject.save! + expect(subject).to be_geocoded + end + + context 'when the model address fields have not changed' do + before do + expect(subject).not_to have_address_changes + end + + it 'returns false' do + expect(subject.needs_to_be_geocoded?).to be(false) + end + end + + context 'when the model address fields have changed' do + before do + modify_address(subject) + expect(subject).to have_address_changes + end + + it 'returns true' do + expect(subject.needs_to_be_geocoded?).to be(true) + end + end + end + end + + describe '#save_with_geocoding' do + before { allow(saved_geocodable).to receive(:geocode).and_return(result_of_geocoding) } + subject { saved_geocodable.save_with_geocoding } + + context 'when geocoding fails' do + let(:result_of_geocoding) { false } + + it { is_expected.to be(false) } + + it 'does not call save' do + expect(saved_geocodable).not_to receive(:save) + subject + end + end + + context 'when geocoding succeeds' do + let(:result_of_geocoding) { true } + let(:result_of_saving) { true } + before { allow(saved_geocodable).to receive(:save).and_return(result_of_saving) } + + it 'calls save' do + expect(saved_geocodable).to receive(:save) + subject + end + + context 'when saving fails' do + let(:result_of_saving) { false } + it { is_expected.to be(false) } + end + + context 'when saving succeeds' do + it { is_expected.to be(true) } + end + end + end + + describe '#update_with_geocoding' do + subject { saved_geocodable.update_with_geocoding(updated_address_params) } + + it 'updates the geocodable with new attributes' do + allow(saved_geocodable).to receive(:save_with_geocoding) + subject + expect(saved_geocodable.changed_attributes).to include(updated_address_params.keys.first) + end + + it 'calls #save_with_geocoding' do + expect(saved_geocodable).to receive(:save_with_geocoding) + subject + end + + it 'returns the return value of #save_with_geocoding' do + allow(saved_geocodable).to receive(:save_with_geocoding).and_return(:return_marker) + expect(subject).to eq(:return_marker) + end + end +end diff --git a/spec/support/shared_examples/reference_data.rb b/spec/support/shared_examples/reference_data.rb new file mode 100644 index 000000000..834f7b7ee --- /dev/null +++ b/spec/support/shared_examples/reference_data.rb @@ -0,0 +1,14 @@ +RSpec.shared_examples 'reference data' do + let(:factory) { described_class.model_name.singular.to_sym } + subject(:model) { build(factory) } + + it 'is valid with valid attributes' do + expect(model).to be_valid + end + + context 'when name is not present' do + before { model.name = nil } + + it { is_expected.to_not be_valid } + end +end diff --git a/spec/support/shared_examples/system_named.rb b/spec/support/shared_examples/system_named.rb new file mode 100644 index 000000000..ace406f4e --- /dev/null +++ b/spec/support/shared_examples/system_named.rb @@ -0,0 +1,15 @@ +RSpec.shared_examples 'system named' do + let(:type) { described_class.model_name.i18n_key } + let(:method) { create(type, order: 1) } + + before do + fail unless described_class::SYSTEM_NAMES + stub_const("#{described_class.model_name}::SYSTEM_NAMES", { 1 => :phone }) + end + + subject { described_class.system_name(method.id) } + + it 'returns the corresponding system name' do + expect(subject).to eq(:phone) + end +end diff --git a/spec/support/shared_examples/translatable_examples.rb b/spec/support/shared_examples/translatable_examples.rb new file mode 100644 index 000000000..7231b6509 --- /dev/null +++ b/spec/support/shared_examples/translatable_examples.rb @@ -0,0 +1,32 @@ +RSpec.shared_examples 'translatable' do + let(:factory) { described_class.model_name.singular.to_sym } + let(:model) { build(factory) } + + describe '#en_name' do + it 'returns the value for name' do + expect(model.en_name).to eql(model.name) + end + end + + describe '#localized_name' do + context 'when locale is "cy"' do + around do |example| + I18n.with_locale(:cy) { example.run } + end + + it 'returns the value for cy_name' do + expect(model.localized_name).to eql(model.cy_name) + end + end + + context 'when locale is "en"' do + around do |example| + I18n.with_locale(:en) { example.run } + end + + it 'returns the value for name' do + expect(model.localized_name).to eql(model.name) + end + end + end +end From b99fdf9b0a61ad5d40aa225af5c301b51bbd765c Mon Sep 17 00:00:00 2001 From: Giuseppe Lobraico Date: Thu, 13 Dec 2018 17:11:45 +0000 Subject: [PATCH 2/2] Bump rubocop and address complaints Also: use cops from mas-standards This requires `rubocop` to be bumped to `0.54.0` --- .gitignore | 1 + .rubocop.yml | 52 +++--- .rubocop_todo.yml | 157 ++++++++++++++++++ Gemfile | 14 +- Gemfile.lock | 19 +-- app/helpers/admin/firms_helper.rb | 16 +- app/jobs/fca_import_job.rb | 2 +- app/models/accreditation.rb | 2 +- app/models/adviser.rb | 33 ++-- app/models/allowed_payment_method.rb | 2 +- app/models/fca_import.rb | 2 +- app/models/firm.rb | 93 ++++++----- app/models/geocodable.rb | 4 +- app/models/in_person_advice_method.rb | 2 +- app/models/initial_advice_fee_structure.rb | 2 +- app/models/initial_meeting_duration.rb | 2 +- app/models/investment_size.rb | 4 +- app/models/lookup/adviser.rb | 4 +- app/models/lookup/firm.rb | 4 +- app/models/lookup/subsidiary.rb | 4 +- app/models/office.rb | 71 ++++---- app/models/ongoing_advice_fee_structure.rb | 2 +- app/models/other_advice_method.rb | 4 +- app/models/principal.rb | 53 +++--- app/models/qualification.rb | 2 +- app/models/snapshot/adviser_queries.rb | 8 +- app/models/snapshot/firm_queries.rb | 24 +-- app/models/snapshot/metrics_in_order.rb | 118 ++++++------- app/serializers/adviser_serializer.rb | 10 +- app/serializers/firm_serializer.rb | 47 +++--- app/serializers/office_serializer.rb | 1 - lib/cloud/storage.rb | 2 +- lib/firm_indexer.rb | 4 +- lib/languages.rb | 6 +- lib/postcode.rb | 7 +- lib/river/runners/tempfile.rb | 2 - .../trading_names_controller_spec.rb | 1 - spec/factories/adviser.rb | 2 +- spec/factories/firm.rb | 2 +- spec/factories/principal.rb | 2 +- spec/helpers/self_service/nav_helper_spec.rb | 2 +- spec/lib/firm_repository_spec.rb | 2 +- spec/models/adviser_spec.rb | 10 +- spec/models/firm_spec.rb | 40 +++-- spec/models/lookup/firm_spec.rb | 4 +- spec/models/office_spec.rb | 6 +- spec/models/principal_spec.rb | 24 +-- spec/models/snapshot_spec.rb | 136 +++++++-------- spec/serializers/firm_serializer_spec.rb | 4 +- .../serializers/adviser_serializer_spec.rb | 24 +-- .../serializers/firm_serializer_spec.rb | 4 +- .../self_service/offices_table_row_section.rb | 2 +- .../shared_examples/geocodable_examples.rb | 6 +- spec/support/shared_examples/system_named.rb | 4 +- 54 files changed, 622 insertions(+), 433 deletions(-) create mode 100644 .rubocop_todo.yml diff --git a/.gitignore b/.gitignore index 8e012c9b6..f1521b43c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .bundle .DS_Store +.rubocop-* /log/*.log /tmp /vendor/assets/bower_components diff --git a/.rubocop.yml b/.rubocop.yml index d071f0551..0768c4b09 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,17 +1,27 @@ +inherit_from: + - .rubocop_todo.yml + - https://raw.githubusercontent.com/moneyadviceservice/mas-standards/master/.rubocop.yml + Documentation: Enabled: false Style/FrozenStringLiteralComment: Enabled: false Metrics/BlockLength: Exclude: - - 'spec/**/*' + - 'spec/**/*' +Metrics/ClassLength: + Exclude: + - 'app/models/firm.rb' Metrics/LineLength: Max: 80 IgnoredPatterns: ['^\s*class\s'] Exclude: - - 'spec/**/*' + - 'spec/**/*' + - 'app/models/snapshot/*.rb' Metrics/MethodLength: Max: 15 + Exclude: + - 'app/models/snapshot/*.rb' Style/ClassAndModuleChildren: Enabled: false Style/CollectionMethods: @@ -42,24 +52,24 @@ AllCops: TargetRailsVersion: 4.2 DisplayCopNames: true Exclude: - - 'bin/**/*' - - 'config/**/*' - - 'db/**/*' - - 'Guardfile' - - 'lib/tasks/**/*' - - 'node_modules/**/*' - - 'script/**/*' - - 'spec/spec_helper.rb' - - 'spec/cassettes/**/*' - - 'features/**/*' - - 'spec/features/**/*' - - 'tmp/**/*' - - 'vendor/**/*' - - 'Rakefile' - - 'spec/dummy/config/**/*' - - 'spec/dummy/db/**/*' - - 'spec/dummy/script/**/*' - # Project specific exclusions - - 'lib/fca/**/*' + - 'bin/**/*' + - 'config/**/*' + - 'db/**/*' + - 'Guardfile' + - 'lib/tasks/**/*' + - 'node_modules/**/*' + - 'script/**/*' + - 'spec/spec_helper.rb' + - 'spec/cassettes/**/*' + - 'features/**/*' + - 'spec/features/**/*' + - 'tmp/**/*' + - 'vendor/**/*' + - 'Rakefile' + - 'spec/dummy/config/**/*' + - 'spec/dummy/db/**/*' + - 'spec/dummy/script/**/*' + # Project specific exclusions + - 'lib/fca/**/*' # Project specific settings diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 000000000..0aa1bc668 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,157 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2019-01-08 10:21:45 +0000 using RuboCop version 0.54.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: Categories, ExpectedOrder. +# ExpectedOrder: module_inclusion, constants, public_class_methods, initializer, public_methods, protected_methods, private_methods +Layout/ClassStructure: + Exclude: + - 'app/controllers/self_service/abstract_firms_controller.rb' + +# Offense count: 2 +Lint/AmbiguousOperator: + Exclude: + - 'app/models/firm.rb' + +# Offense count: 2 +Lint/DuplicateMethods: + Exclude: + - 'app/forms/admin/move_advisers_form.rb' + - 'app/models/firm.rb' + +# Offense count: 1 +Lint/NestedMethodDefinition: + Exclude: + - 'app/models/friendly_namable.rb' + +# Offense count: 2 +Lint/ReturnInVoidContext: + Exclude: + - 'app/models/office.rb' + +# Offense count: 3 +Metrics/AbcSize: + Max: 17 + +# Offense count: 3 +Naming/MemoizedInstanceVariableName: + Exclude: + - 'app/jobs/fca_import_job.rb' + - 'app/models/snapshot.rb' + +# Offense count: 2 +# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros. +# NamePrefix: is_, has_, have_ +# NamePrefixBlacklist: is_, has_, have_ +# NameWhitelist: is_a? +# MethodDefinitionMacros: define_method, define_singleton_method +Naming/PredicateName: + Exclude: + - 'spec/**/*' + - 'app/models/adviser.rb' + - 'app/models/office.rb' + +# Offense count: 6 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: io, id, to +Naming/UncommunicativeMethodParamName: + Exclude: + - 'app/controllers/admin/lookup/fca_import_controller.rb' + - 'app/helpers/admin/lookup/fca_import_helper.rb' + - 'lib/adviser_list_csv.rb' + - 'lib/river/core.rb' + - 'lib/river/runners.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +Rails/ActiveRecordAliases: + Exclude: + - 'app/controllers/admin/principals_controller.rb' + - 'app/controllers/admin/users_controller.rb' + - 'spec/controllers/admin/firms_controller_spec.rb' + - 'spec/controllers/selfservice/firms_controller_spec.rb' + - 'spec/controllers/selfservice/trading_names_controller_spec.rb' + - 'spec/models/firm_spec.rb' + - 'spec/models/office_spec.rb' + - 'spec/support/contexts/advisers_controller.rb' + - 'spec/support/contexts/offices_controller.rb' + +# Offense count: 16 +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/HasAndBelongsToMany: + Exclude: + - 'app/models/adviser.rb' + - 'app/models/allowed_payment_method.rb' + - 'app/models/firm.rb' + - 'app/models/in_person_advice_method.rb' + - 'app/models/initial_advice_fee_structure.rb' + - 'app/models/investment_size.rb' + - 'app/models/ongoing_advice_fee_structure.rb' + - 'app/models/other_advice_method.rb' + +# Offense count: 2 +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/HasManyOrHasOneDependent: + Exclude: + - 'app/models/initial_meeting_duration.rb' + - 'app/models/lookup/firm.rb' + +# Offense count: 8 +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/InverseOf: + Exclude: + - 'app/models/firm.rb' + - 'app/models/lookup/firm.rb' + - 'app/models/principal.rb' + - 'app/models/user.rb' + +# Offense count: 3 +# Configuration parameters: Blacklist. +# Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters +Rails/SkipsModelValidations: + Exclude: + - 'spec/models/office_spec.rb' + +# Offense count: 8 +# Cop supports --auto-correct. +Style/EmptyLineAfterGuardClause: + Exclude: + - 'app/controllers/self_service/principals_controller.rb' + - 'app/forms/new_principal_form.rb' + - 'app/helpers/self_service/self_service_helper.rb' + - 'app/models/firm.rb' + - 'lib/firm_indexer.rb' + - 'spec/support/self_service/firm_edit_page.rb' + - 'spec/support/shared_examples/system_named.rb' + +# Offense count: 1 +# Configuration parameters: AllowedVariables. +Style/GlobalVars: + Exclude: + - 'lib/stats.rb' + +# Offense count: 3 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Exclude: + - 'app/models/adviser.rb' + - 'app/models/office.rb' + - 'app/models/principal.rb' + +# Offense count: 5 +# Configuration parameters: SuspiciousParamNames. +# SuspiciousParamNames: options, opts, args, params, parameters +Style/OptionHash: + Exclude: + - 'app/presenters/self_service/status_presenter.rb' + - 'spec/controllers/selfservice/firms_controller_spec.rb' + - 'spec/controllers/selfservice/trading_names_controller_spec.rb' diff --git a/Gemfile b/Gemfile index d6b0f8ce0..7edad2661 100644 --- a/Gemfile +++ b/Gemfile @@ -25,16 +25,13 @@ gem 'dough-ruby', github: 'moneyadviceservice/dough', require: 'dough', tag: 'v5.12.0.267' +gem 'geocoder', '~> 1.4.7' +gem 'httpclient', '~> 2.8.3' gem 'jquery-rails' gem 'kaminari' +gem 'language_list', '~> 1.2.1' gem 'letter_opener', group: :development gem 'mailjet' -gem 'uk_postcode', '~> 2.1.2' -gem 'uk_phone_numbers', '~> 0.1.1' -gem 'language_list', '~> 1.2.1' -gem 'httpclient', '~> 2.8.3' -gem 'geocoder', '~> 1.4.7' -gem 'statsd-ruby', '~> 1.4.0' gem 'oga' gem 'pg', '0.21.0' gem 'rails_email_validator' @@ -54,7 +51,10 @@ gem 'sidekiq-unique-jobs' gem 'sidetiq' gem 'sinatra', require: false gem 'slack-ruby-client' +gem 'statsd-ruby', '~> 1.4.0' gem 'uglifier', '>= 1.3.0' +gem 'uk_phone_numbers', '~> 0.1.1' +gem 'uk_postcode', '~> 2.1.2' gem 'unicorn' group :test, :development do @@ -66,7 +66,7 @@ group :test, :development do gem 'pry-byebug' gem 'pry-rails' gem 'rspec-rails' - gem 'rubocop', '0.49.0' + gem 'rubocop', '0.54.0' gem 'timecop' end diff --git a/Gemfile.lock b/Gemfile.lock index b78ac856a..e698d25eb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -244,7 +244,7 @@ GEM ruby-ll (~> 2.1) orm_adapter (0.5.0) parallel (1.12.1) - parser (2.5.0.5) + parser (2.5.3.0) ast (~> 2.4.0) pg (0.21.0) poltergeist (1.17.0) @@ -253,7 +253,7 @@ GEM websocket-driver (>= 0.2.0) polyamorous (1.3.3) activerecord (>= 3.0) - powerpack (0.1.1) + powerpack (0.1.2) pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -294,8 +294,7 @@ GEM activesupport (= 4.2.10) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.2.2) - rake + rainbow (3.0.0) raindrops (0.19.0) rake (11.3.0) ransack (1.8.8) @@ -335,17 +334,17 @@ GEM rspec-mocks (~> 3.7.0) rspec-support (~> 3.7.0) rspec-support (3.7.1) - rubocop (0.49.0) + rubocop (0.54.0) parallel (~> 1.10) - parser (>= 2.3.3.1, < 3.0) + parser (>= 2.5) powerpack (~> 0.1) - rainbow (>= 1.99.1, < 3.0) + rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-ll (2.1.2) ansi ast - ruby-progressbar (1.9.0) + ruby-progressbar (1.10.0) rubyzip (1.2.1) safe_yaml (1.0.4) sass (3.2.19) @@ -408,7 +407,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.3.0) + unicode-display_width (1.4.1) unicorn (5.4.0) kgio (~> 2.6) raindrops (~> 0.7) @@ -468,7 +467,7 @@ DEPENDENCIES rollbar rspec-collection_matchers rspec-rails - rubocop (= 0.49.0) + rubocop (= 0.54.0) rubyzip sass-rails sidekiq (~> 3.3.4) diff --git a/app/helpers/admin/firms_helper.rb b/app/helpers/admin/firms_helper.rb index 836cf54d1..37ed90c5f 100644 --- a/app/helpers/admin/firms_helper.rb +++ b/app/helpers/admin/firms_helper.rb @@ -1,17 +1,15 @@ -# coding: utf-8 - module Admin::FirmsHelper def render_questionnaire_response(firm, attribute_name) value = firm.send(attribute_name) if value.present? - if value.respond_to?(:collect) - output = value.collect(&:name).to_sentence - elsif value.respond_to?(:name) - output = value.name - else - output = render_literal_or_fee_or_percentage(value, attribute_name) - end + output = if value.respond_to?(:collect) + value.collect(&:name).to_sentence + elsif value.respond_to?(:name) + value.name + else + render_literal_or_fee_or_percentage(value, attribute_name) + end output else 'not set' diff --git a/app/jobs/fca_import_job.rb b/app/jobs/fca_import_job.rb index b2ec45b2a..77f4bea32 100644 --- a/app/jobs/fca_import_job.rb +++ b/app/jobs/fca_import_job.rb @@ -60,7 +60,7 @@ def notify_slack(text) as_user: true, text: " #{text}" ) - rescue + rescue StandardError logger.error 'An error occured while trying to post msg' retry if (tries += 1) <= 3 end diff --git a/app/models/accreditation.rb b/app/models/accreditation.rb index 6f1c7c96f..3a6c2d2a4 100644 --- a/app/models/accreditation.rb +++ b/app/models/accreditation.rb @@ -1,7 +1,7 @@ class Accreditation < ActiveRecord::Base include FriendlyNamable - validates_presence_of :name + validates :name, presence: true default_scope { order(:order) } end diff --git a/app/models/adviser.rb b/app/models/adviser.rb index d301a842e..37f1f728a 100644 --- a/app/models/adviser.rb +++ b/app/models/adviser.rb @@ -15,19 +15,19 @@ class Adviser < ActiveRecord::Base before_validation :upcase_postcode validates :travel_distance, - presence: true, - inclusion: { in: TravelDistance.all.values } + presence: true, + inclusion: { in: TravelDistance.all.values } validates :postcode, - presence: true, - format: { with: /\A[A-Z\d]{1,4} ?[A-Z\d]{1,3}\z/ } + presence: true, + format: { with: /\A[A-Z\d]{1,4} ?[A-Z\d]{1,3}\z/ } validates :reference_number, - presence: true, - uniqueness: true, - format: { - with: /\A[A-Z]{3}[0-9]{5}\z/ - }, unless: :bypass_reference_number_check? + presence: true, + uniqueness: true, + format: { + with: /\A[A-Z]{3}[0-9]{5}\z/ + }, unless: :bypass_reference_number_check? validate :match_reference_number, unless: :bypass_reference_number_check? @@ -63,14 +63,17 @@ def has_address_changes? end def add_geocoding_failed_error - errors.add(:geocoding, I18n.t("#{model_name.i18n_key}.geocoding.failure_message")) + errors.add( + :geocoding, + I18n.t("#{model_name.i18n_key}.geocoding.failure_message") + ) end def field_order - [ - :reference_number, - :postcode, - :travel_distance + %i[ + reference_number + postcode + travel_distance ] end @@ -93,7 +96,7 @@ def upcase_postcode end def assign_name - self.name = self.name || Lookup::Adviser.find_by( + self.name = name || Lookup::Adviser.find_by( reference_number: reference_number ).try(:name) end diff --git a/app/models/allowed_payment_method.rb b/app/models/allowed_payment_method.rb index 8d89f62b1..9e4ecbc61 100644 --- a/app/models/allowed_payment_method.rb +++ b/app/models/allowed_payment_method.rb @@ -1,7 +1,7 @@ class AllowedPaymentMethod < ActiveRecord::Base has_and_belongs_to_many :firms - validates_presence_of :name + validates :name, presence: true default_scope { order(:order) } end diff --git a/app/models/fca_import.rb b/app/models/fca_import.rb index e56ee3e7b..9ee31e7e7 100644 --- a/app/models/fca_import.rb +++ b/app/models/fca_import.rb @@ -61,7 +61,7 @@ def cancelled? def exec(sql) result = ActiveRecord::Base.connection.execute(sql) result[0]['total'].to_i - rescue + rescue StandardError 0 end end diff --git a/app/models/firm.rb b/app/models/firm.rb index b53929f6f..ee0997d28 100644 --- a/app/models/firm.rb +++ b/app/models/firm.rb @@ -1,18 +1,19 @@ class Firm < ActiveRecord::Base FREE_INITIAL_MEETING_VALID_VALUES = [true, false].freeze - # We use a scalar required field as a marker to detect a record saved with validation + # We use a scalar required field as a marker to detect a record saved with + # validation REGISTERED_MARKER_FIELD = :free_initial_meeting REGISTERED_MARKER_FIELD_VALID_VALUES = FREE_INITIAL_MEETING_VALID_VALUES - ADVICE_TYPES_ATTRIBUTES = [ - :retirement_income_products_flag, - :pension_transfer_flag, - :long_term_care_flag, - :equity_release_flag, - :inheritance_tax_and_estate_planning_flag, - :wills_and_probate_flag - ] + ADVICE_TYPES_ATTRIBUTES = %i[ + retirement_income_products_flag + pension_transfer_flag + long_term_care_flag + equity_release_flag + inheritance_tax_and_estate_planning_flag + wills_and_probate_flag + ].freeze scope :registered, -> { where.not(REGISTERED_MARKER_FIELD => nil) } scope :sorted_by_registered_name, -> { order(:registered_name) } @@ -34,8 +35,12 @@ def self.languages_used has_many :advisers, dependent: :destroy has_many :offices, -> { order created_at: :asc }, dependent: :destroy - has_many :subsidiaries, class_name: 'Firm', foreign_key: :parent_id, dependent: :destroy - has_many :trading_names, class_name: 'Firm', foreign_key: :parent_id, dependent: :destroy + has_many :subsidiaries, class_name: 'Firm', + foreign_key: :parent_id, + dependent: :destroy + has_many :trading_names, class_name: 'Firm', + foreign_key: :parent_id, + dependent: :destroy has_many :qualifications, -> { reorder('').uniq }, through: :advisers has_many :accreditations, -> { reorder('').uniq }, through: :advisers @@ -48,62 +53,62 @@ def self.languages_used before_validation :deduplicate_languages validates :website_address, - allow_blank: true, - length: { maximum: 100 }, - format: { with: /\A(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]+/ } + allow_blank: true, + length: { maximum: 100 }, + format: { with: /\A(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]+/ } validates :free_initial_meeting, - inclusion: { in: FREE_INITIAL_MEETING_VALID_VALUES } + inclusion: { in: FREE_INITIAL_MEETING_VALID_VALUES } validates :initial_meeting_duration, - presence: true, - if: ->{ free_initial_meeting? } + presence: true, + if: -> { free_initial_meeting? } validates :initial_advice_fee_structures, - length: { minimum: 1 } + length: { minimum: 1 } validates :ongoing_advice_fee_structures, - length: { minimum: 1 } + length: { minimum: 1 } validates :allowed_payment_methods, - length: { minimum: 1 } + length: { minimum: 1 } validates :minimum_fixed_fee, - allow_blank: false, - numericality: { - only_integer: true, - greater_than_or_equal_to: 0, - less_than: 2147483648 # max value for postgres integer type - } + allow_blank: false, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0, + less_than: 2_147_483_648 # max value for postgres integer type + } validates :in_person_advice_methods, - presence: true, - if: ->{ primary_advice_method == :local } + presence: true, + if: -> { primary_advice_method == :local } validates :other_advice_methods, - presence: true, - if: ->{ primary_advice_method == :remote } + presence: true, + if: -> { primary_advice_method == :remote } validates *ADVICE_TYPES_ATTRIBUTES, - inclusion: { in: [true, false] } + inclusion: { in: [true, false] } validates :primary_advice_method, - presence: true + presence: true validate :languages do - unless languages.all? { |lang| Languages::AVAILABLE_LANGUAGES_ISO_639_3_CODES.include?(lang) } + unless languages.all? do |lang| + Languages::AVAILABLE_LANGUAGES_ISO_639_3_CODES.include?(lang) + end errors.add(:languages, :invalid) end end validate do - unless advice_types.values.any? - errors.add(:advice_types, :invalid) - end + errors.add(:advice_types, :invalid) unless advice_types.values.any? end validates :investment_sizes, - length: { minimum: 1 } + length: { minimum: 1 } after_commit :notify_indexer @@ -117,16 +122,16 @@ def notify_indexer # record ever been saved with validation enabled? def registered? # false is a valid value so we cannot use `.present?` - !(send(REGISTERED_MARKER_FIELD).nil?) + !send(REGISTERED_MARKER_FIELD).nil? end if Rails.env.test? # A helper to shield tests from modifying the marker field directly def __set_registered(state) - new_value = (state) ? REGISTERED_MARKER_FIELD_VALID_VALUES.first : nil + new_value = state ? REGISTERED_MARKER_FIELD_VALID_VALUES.first : nil send("#{REGISTERED_MARKER_FIELD}=", new_value) end - alias_method :__registered=, :__set_registered + alias __registered= __set_registered end enum status: { independent: 1, restricted: 2 } @@ -134,14 +139,15 @@ def __set_registered(state) def in_person_advice? in_person_advice_methods.present? end - alias :postcode_searchable? :in_person_advice? + alias postcode_searchable? in_person_advice? def trading_name? parent.present? end - alias_method :subsidiary?, :trading_name? + alias subsidiary? trading_name? + # rubocop:disable Metrics/MethodLength def field_order [ :website_address, @@ -167,6 +173,7 @@ def field_order :investment_sizes ] end + # rubocop:enable Metrics/MethodLength def advice_types ADVICE_TYPES_ATTRIBUTES.map { |a| [a, self[a]] }.to_h @@ -196,8 +203,6 @@ def infer_primary_advice_method :local elsif other_advice_methods.any? :remote - else - nil end end diff --git a/app/models/geocodable.rb b/app/models/geocodable.rb index 66717dbca..eade9bf97 100644 --- a/app/models/geocodable.rb +++ b/app/models/geocodable.rb @@ -5,12 +5,12 @@ def self.included(model) def latitude=(value) value = value.to_f.round(6) unless value.nil? - write_attribute(:latitude, value) + self[:latitude] = value end def longitude=(value) value = value.to_f.round(6) unless value.nil? - write_attribute(:longitude, value) + self[:longitude] = value end def geocoded? diff --git a/app/models/in_person_advice_method.rb b/app/models/in_person_advice_method.rb index b261d5b17..490ec4f20 100644 --- a/app/models/in_person_advice_method.rb +++ b/app/models/in_person_advice_method.rb @@ -3,7 +3,7 @@ class InPersonAdviceMethod < ActiveRecord::Base has_and_belongs_to_many :firms - validates_presence_of :name + validates :name, presence: true default_scope { order(:order) } end diff --git a/app/models/initial_advice_fee_structure.rb b/app/models/initial_advice_fee_structure.rb index 7b50e08f9..0e7e6d978 100644 --- a/app/models/initial_advice_fee_structure.rb +++ b/app/models/initial_advice_fee_structure.rb @@ -1,7 +1,7 @@ class InitialAdviceFeeStructure < ActiveRecord::Base has_and_belongs_to_many :firms - validates_presence_of :name + validates :name, presence: true default_scope { order(:order) } end diff --git a/app/models/initial_meeting_duration.rb b/app/models/initial_meeting_duration.rb index a0ac8f029..e52f215a1 100644 --- a/app/models/initial_meeting_duration.rb +++ b/app/models/initial_meeting_duration.rb @@ -1,7 +1,7 @@ class InitialMeetingDuration < ActiveRecord::Base has_many :firms - validates_presence_of :name + validates :name, presence: true default_scope { order(:order) } end diff --git a/app/models/investment_size.rb b/app/models/investment_size.rb index 3647771ac..dc585eeaa 100644 --- a/app/models/investment_size.rb +++ b/app/models/investment_size.rb @@ -4,7 +4,7 @@ class InvestmentSize < ActiveRecord::Base has_and_belongs_to_many :firms - validates_presence_of :name + validates :name, presence: true default_scope { order(:order) } @@ -13,6 +13,6 @@ def self.lowest end def lowest? - self == self.class.lowest + self.class.lowest == self end end diff --git a/app/models/lookup/adviser.rb b/app/models/lookup/adviser.rb index 3a916fc93..7e51e7236 100644 --- a/app/models/lookup/adviser.rb +++ b/app/models/lookup/adviser.rb @@ -1,7 +1,7 @@ module Lookup class Adviser < ActiveRecord::Base - validates_length_of :reference_number, is: 8 - validates_presence_of :name + validates :reference_number, length: { is: 8 } + validates :name, presence: true def self.table_name "lookup_#{super}" diff --git a/app/models/lookup/firm.rb b/app/models/lookup/firm.rb index a1165624f..9537c55cd 100644 --- a/app/models/lookup/firm.rb +++ b/app/models/lookup/firm.rb @@ -3,8 +3,8 @@ class Firm < ActiveRecord::Base has_many :subsidiaries, primary_key: :fca_number, foreign_key: :fca_number validates :fca_number, - length: { is: 6 }, - numericality: { only_integer: true } + length: { is: 6 }, + numericality: { only_integer: true } def subsidiaries? subsidiaries.present? diff --git a/app/models/lookup/subsidiary.rb b/app/models/lookup/subsidiary.rb index 64db5411e..31f563cc9 100644 --- a/app/models/lookup/subsidiary.rb +++ b/app/models/lookup/subsidiary.rb @@ -1,8 +1,8 @@ module Lookup class Subsidiary < ActiveRecord::Base validates :fca_number, - length: { is: 6 }, - numericality: { only_integer: true } + length: { is: 6 }, + numericality: { only_integer: true } def self.table_name "lookup_#{super}" diff --git a/app/models/office.rb b/app/models/office.rb index d3799aef7..b2ecc91b9 100644 --- a/app/models/office.rb +++ b/app/models/office.rb @@ -4,39 +4,39 @@ class Office < ActiveRecord::Base include Geocodable - ADDRESS_FIELDS = [ - :address_line_one, - :address_line_two, - :address_town, - :address_county, - :address_postcode + ADDRESS_FIELDS = %i[ + address_line_one + address_line_two + address_town + address_county + address_postcode ].freeze belongs_to :firm validates :email_address, - presence: false, - length: { maximum: 50 }, - format: { with: /.+@.+\..+/ } + presence: false, + length: { maximum: 50 }, + format: { with: /.+@.+\..+/ } validate :telephone_number_is_valid validates :address_line_one, - presence: true, - length: { maximum: 100 } + presence: true, + length: { maximum: 100 } validates :address_line_two, - length: { maximum: 100 } + length: { maximum: 100 } validate :postcode_is_valid validates :address_town, - presence: true, - length: { maximum: 100 } + presence: true, + length: { maximum: 100 } validates :address_county, - presence: false, - length: { maximum: 100 } + presence: false, + length: { maximum: 100 } validates :disabled_access, inclusion: { in: [true, false] } @@ -47,16 +47,16 @@ def notify_indexer end def field_order - [ - :address_line_one, - :address_line_two, - :address_town, - :address_county, - :address_postcode, - :email_address, - :telephone_number, - :disabled_access, - :website + %i[ + address_line_one + address_line_two + address_town + address_county + address_postcode + email_address + telephone_number + disabled_access + website ] end @@ -65,7 +65,7 @@ def telephone_number=(new_phone_number) end def telephone_number - return format_telephone_number(cleanup_telephone_number(super)) + format_telephone_number(cleanup_telephone_number(super)) end # The Geocodable interface expect an object that responds to @@ -82,18 +82,21 @@ def has_address_changes? end def add_geocoding_failed_error - errors.add(:geocoding, I18n.t("#{model_name.i18n_key}.geocoding.failure_message")) + errors.add( + :geocoding, + I18n.t("#{model_name.i18n_key}.geocoding.failure_message") + ) end def address_postcode=(postcode) - return super unless postcode.present? + return super if postcode.blank? parsed_postcode = UKPostcode.parse(postcode) return super unless parsed_postcode.full_valid? new_postcode = "#{parsed_postcode.outcode} #{parsed_postcode.incode}" - write_attribute(:address_postcode, new_postcode) + self[:address_postcode] = new_postcode end private @@ -109,8 +112,12 @@ def postcode_is_valid end def telephone_number_is_valid - if telephone_number.nil? || !UKPhoneNumbers.valid?(telephone_number.gsub(' ', '')) - errors.add(:telephone_number, I18n.t("#{model_name.i18n_key}.telephone_number.invalid_format")) + if telephone_number.nil? || + !UKPhoneNumbers.valid?(telephone_number.delete(' ')) + errors.add( + :telephone_number, + I18n.t("#{model_name.i18n_key}.telephone_number.invalid_format") + ) end end diff --git a/app/models/ongoing_advice_fee_structure.rb b/app/models/ongoing_advice_fee_structure.rb index 35a3d26ad..a91da1396 100644 --- a/app/models/ongoing_advice_fee_structure.rb +++ b/app/models/ongoing_advice_fee_structure.rb @@ -1,7 +1,7 @@ class OngoingAdviceFeeStructure < ActiveRecord::Base has_and_belongs_to_many :firms - validates_presence_of :name + validates :name, presence: true default_scope { order(:order) } end diff --git a/app/models/other_advice_method.rb b/app/models/other_advice_method.rb index 044c8c07e..78e961731 100644 --- a/app/models/other_advice_method.rb +++ b/app/models/other_advice_method.rb @@ -6,11 +6,11 @@ class OtherAdviceMethod < ActiveRecord::Base SYSTEM_NAMES = { 1 => :phone, 2 => :online - } + }.freeze has_and_belongs_to_many :firms - validates_presence_of :name + validates :name, presence: true default_scope { order(:order) } end diff --git a/app/models/principal.rb b/app/models/principal.rb index 26c20fb3f..97613be48 100644 --- a/app/models/principal.rb +++ b/app/models/principal.rb @@ -5,31 +5,31 @@ class Principal < ActiveRecord::Base after_create :associate_firm has_one :firm, - -> { where(parent_id: nil) }, - primary_key: :fca_number, - foreign_key: :fca_number, - dependent: :destroy + -> { where(parent_id: nil) }, + primary_key: :fca_number, + foreign_key: :fca_number, + dependent: :destroy validates :fca_number, - presence: true, - uniqueness: true, - length: { is: 6 }, - numericality: { only_integer: true } + presence: true, + uniqueness: true, + length: { is: 6 }, + numericality: { only_integer: true } validates :email_address, - presence: true, - uniqueness: true, - length: { maximum: 50 }, - format: { with: /.+@.+\..+/ } + presence: true, + uniqueness: true, + length: { maximum: 50 }, + format: { with: /.+@.+\..+/ } validates :first_name, :last_name, :job_title, presence: true, length: 2..80 validates :telephone_number, - presence: true, - length: { maximum: 50 }, - format: { with: /\A[0-9 ]+\z/ } + presence: true, + length: { maximum: 50 }, + format: { with: /\A[0-9 ]+\z/ } - validates_acceptance_of :confirmed_disclaimer, accept: true + validates :confirmed_disclaimer, acceptance: { accept: true } validate :match_fca_number, if: :fca_number? @@ -48,14 +48,14 @@ def lookup_firm delegate :subsidiaries?, to: :lookup_firm def field_order - [ - :fca_number, - :first_name, - :last_name, - :job_title, - :email_address, - :telephone_number, - :confirmed_disclaimer + %i[ + fca_number + first_name + last_name + job_title + email_address + telephone_number + confirmed_disclaimer ] end @@ -85,13 +85,14 @@ def find_subsidiary(subsidiary) end def associate_firm - Firm.new(fca_number: lookup_firm.fca_number, registered_name: lookup_firm.registered_name).tap do |f| + Firm.new(fca_number: lookup_firm.fca_number, + registered_name: lookup_firm.registered_name).tap do |f| f.save!(validate: false) end end def match_fca_number - unless Lookup::Firm.exists?(fca_number: self.fca_number) + unless Lookup::Firm.exists?(fca_number: fca_number) errors.add( :fca_number, I18n.t('registration.principal.fca_number_unmatched') diff --git a/app/models/qualification.rb b/app/models/qualification.rb index 770ac2ea7..e2f24d5c8 100644 --- a/app/models/qualification.rb +++ b/app/models/qualification.rb @@ -1,7 +1,7 @@ class Qualification < ActiveRecord::Base include FriendlyNamable - validates_presence_of :name + validates :name, presence: true default_scope { order(:order) } end diff --git a/app/models/snapshot/adviser_queries.rb b/app/models/snapshot/adviser_queries.rb index 232aa8d02..0fdcc4fb4 100644 --- a/app/models/snapshot/adviser_queries.rb +++ b/app/models/snapshot/adviser_queries.rb @@ -19,10 +19,10 @@ def query_advisers_in_northern_ireland advisers_in_country(Adviser.all, 'Northern Ireland') end - TravelDistance.all.keys.each do |val| - method_name = val.downcase.gsub(' ', '_') + TravelDistance.all.each_key do |key| + method_name = key.downcase.tr(' ', '_') define_method "query_advisers_who_travel_#{method_name}" do - advisers_who_travel(val) + advisers_who_travel(key) end end @@ -109,7 +109,7 @@ def query_advisers_part_of_chartered_accountants private def advisers_in_country(advisers, country) - postcodes = advisers.map { |adviser| adviser.postcode } + postcodes = advisers.map(&:postcode) country_postcodes = Postcode.new.filter_postcodes_by_country(postcodes, country) advisers.select { |adviser| country_postcodes.include?(adviser.postcode) } end diff --git a/app/models/snapshot/firm_queries.rb b/app/models/snapshot/firm_queries.rb index 593756672..bd7766eb6 100644 --- a/app/models/snapshot/firm_queries.rb +++ b/app/models/snapshot/firm_queries.rb @@ -4,11 +4,11 @@ def query_firms_with_no_minimum_fee end def query_firms_with_min_fee_between_1_500 - publishable_firms.select { |f| (1..500).include?(f.minimum_fixed_fee) } + publishable_firms.select { |f| (1..500).cover?(f.minimum_fixed_fee) } end def query_firms_with_min_fee_between_501_1000 - publishable_firms.select { |f| (501..1000).include?(f.minimum_fixed_fee) } + publishable_firms.select { |f| (501..1000).cover?(f.minimum_fixed_fee) } end def query_firms_any_pot_size @@ -55,43 +55,43 @@ def query_firms_in_northern_ireland end def query_firms_providing_retirement_income_products - publishable_firms.select { |f| f.retirement_income_products_flag? } + publishable_firms.select(&:retirement_income_products_flag?) end def query_firms_providing_pension_transfer - publishable_firms.select { |f| f.pension_transfer_flag? } + publishable_firms.select(&:pension_transfer_flag?) end def query_firms_providing_long_term_care - publishable_firms.select { |f| f.long_term_care_flag? } + publishable_firms.select(&:long_term_care_flag?) end def query_firms_providing_equity_release - publishable_firms.select { |f| f.equity_release_flag? } + publishable_firms.select(&:equity_release_flag?) end def query_firms_providing_inheritance_tax_and_estate_planning - publishable_firms.select { |f| f.inheritance_tax_and_estate_planning_flag? } + publishable_firms.select(&:inheritance_tax_and_estate_planning_flag?) end def query_firms_providing_wills_and_probate - publishable_firms.select { |f| f.wills_and_probate_flag? } + publishable_firms.select(&:wills_and_probate_flag?) end def query_firms_providing_ethical_investing - publishable_firms.select { |f| f.ethical_investing_flag? } + publishable_firms.select(&:ethical_investing_flag?) end def query_firms_providing_workplace_financial_advice - publishable_firms.select { |f| f.workplace_financial_advice_flag? } + publishable_firms.select(&:workplace_financial_advice_flag?) end def query_firms_providing_non_uk_residents - publishable_firms.select { |f| f.non_uk_residents_flag? } + publishable_firms.select(&:non_uk_residents_flag?) end def query_firms_providing_sharia_investing - publishable_firms.select { |f| f.sharia_investing_flag? } + publishable_firms.select(&:sharia_investing_flag?) end def query_firms_offering_languages_other_than_english diff --git a/app/models/snapshot/metrics_in_order.rb b/app/models/snapshot/metrics_in_order.rb index d1024b003..d57b508d2 100644 --- a/app/models/snapshot/metrics_in_order.rb +++ b/app/models/snapshot/metrics_in_order.rb @@ -1,64 +1,64 @@ module Snapshot::MetricsInOrder def metrics_in_order - [ - :firms_with_no_minimum_fee, - :firms_with_min_fee_between_1_500, - :firms_with_min_fee_between_501_1000, - :firms_any_pot_size, - :firms_any_pot_size_min_fee_less_than_500, - :registered_firms, - :published_firms, - :firms_offering_face_to_face_advice, - :firms_offering_remote_advice, - :firms_in_england, - :firms_in_scotland, - :firms_in_wales, - :firms_in_northern_ireland, - :firms_providing_retirement_income_products, - :firms_providing_pension_transfer, - :firms_providing_long_term_care, - :firms_providing_equity_release, - :firms_providing_inheritance_tax_and_estate_planning, - :firms_providing_wills_and_probate, - :firms_providing_ethical_investing, - :firms_providing_sharia_investing, - :firms_providing_workplace_financial_advice, - :firms_providing_non_uk_residents, - :firms_offering_languages_other_than_english, - :offices_with_disabled_access, - :registered_advisers, - :advisers_in_england, - :advisers_in_scotland, - :advisers_in_wales, - :advisers_in_northern_ireland, - :advisers_who_travel_5_miles, - :advisers_who_travel_10_miles, - :advisers_who_travel_25_miles, - :advisers_who_travel_50_miles, - :advisers_who_travel_100_miles, - :advisers_who_travel_150_miles, - :advisers_who_travel_200_miles, - :advisers_who_travel_250_miles, - :advisers_who_travel_uk_wide, - :advisers_accredited_in_solla, - :advisers_accredited_in_later_life_academy, - :advisers_accredited_in_iso22222, - :advisers_accredited_in_bs8577, - :advisers_with_qualification_in_level_4, - :advisers_with_qualification_in_level_6, - :advisers_with_qualification_in_chartered_financial_planner, - :advisers_with_qualification_in_certified_financial_planner, - :advisers_with_qualification_in_pension_transfer, - :advisers_with_qualification_in_equity_release, - :advisers_with_qualification_in_long_term_care_planning, - :advisers_with_qualification_in_tep, - :advisers_with_qualification_in_fcii, - :advisers_part_of_personal_finance_society, - :advisers_part_of_institute_financial_planning, - :advisers_part_of_institute_financial_services, - :advisers_part_of_ci_bankers_scotland, - :advisers_part_of_ci_securities_and_investments, - :advisers_part_of_cfa_institute, :advisers_part_of_chartered_accountants + %i[ + firms_with_no_minimum_fee + firms_with_min_fee_between_1_500 + firms_with_min_fee_between_501_1000 + firms_any_pot_size + firms_any_pot_size_min_fee_less_than_500 + registered_firms + published_firms + firms_offering_face_to_face_advice + firms_offering_remote_advice + firms_in_england + firms_in_scotland + firms_in_wales + firms_in_northern_ireland + firms_providing_retirement_income_products + firms_providing_pension_transfer + firms_providing_long_term_care + firms_providing_equity_release + firms_providing_inheritance_tax_and_estate_planning + firms_providing_wills_and_probate + firms_providing_ethical_investing + firms_providing_sharia_investing + firms_providing_workplace_financial_advice + firms_providing_non_uk_residents + firms_offering_languages_other_than_english + offices_with_disabled_access + registered_advisers + advisers_in_england + advisers_in_scotland + advisers_in_wales + advisers_in_northern_ireland + advisers_who_travel_5_miles + advisers_who_travel_10_miles + advisers_who_travel_25_miles + advisers_who_travel_50_miles + advisers_who_travel_100_miles + advisers_who_travel_150_miles + advisers_who_travel_200_miles + advisers_who_travel_250_miles + advisers_who_travel_uk_wide + advisers_accredited_in_solla + advisers_accredited_in_later_life_academy + advisers_accredited_in_iso22222 + advisers_accredited_in_bs8577 + advisers_with_qualification_in_level_4 + advisers_with_qualification_in_level_6 + advisers_with_qualification_in_chartered_financial_planner + advisers_with_qualification_in_certified_financial_planner + advisers_with_qualification_in_pension_transfer + advisers_with_qualification_in_equity_release + advisers_with_qualification_in_long_term_care_planning + advisers_with_qualification_in_tep + advisers_with_qualification_in_fcii + advisers_part_of_personal_finance_society + advisers_part_of_institute_financial_planning + advisers_part_of_institute_financial_services + advisers_part_of_ci_bankers_scotland + advisers_part_of_ci_securities_and_investments + advisers_part_of_cfa_institute advisers_part_of_chartered_accountants ] end end diff --git a/app/serializers/adviser_serializer.rb b/app/serializers/adviser_serializer.rb index a0d4c4148..5e7bdc4e9 100644 --- a/app/serializers/adviser_serializer.rb +++ b/app/serializers/adviser_serializer.rb @@ -1,6 +1,12 @@ class AdviserSerializer < ActiveModel::Serializer - - attributes :_id, :name, :postcode, :range, :location, :range_location, :qualification_ids, :accreditation_ids + attributes :_id, + :name, + :postcode, + :range, + :location, + :range_location, + :qualification_ids, + :accreditation_ids def _id object.id diff --git a/app/serializers/firm_serializer.rb b/app/serializers/firm_serializer.rb index c859a8d90..2665ca665 100644 --- a/app/serializers/firm_serializer.rb +++ b/app/serializers/firm_serializer.rb @@ -1,29 +1,28 @@ class FirmSerializer < ActiveModel::Serializer - attributes :_id, - :registered_name, - :postcode_searchable, - :telephone_number, - :website_address, - :email_address, - :free_initial_meeting, - :minimum_fixed_fee, - :retirement_income_products, - :pension_transfer, - :options_when_paying_for_care, - :equity_release, - :inheritance_tax_planning, - :wills_and_probate, - :other_advice_methods, - :investment_sizes, - :in_person_advice_methods, - :adviser_qualification_ids, - :adviser_accreditation_ids, - :ethical_investing_flag, - :sharia_investing_flag, - :workplace_financial_advice_flag, - :non_uk_residents_flag, - :languages + :registered_name, + :postcode_searchable, + :telephone_number, + :website_address, + :email_address, + :free_initial_meeting, + :minimum_fixed_fee, + :retirement_income_products, + :pension_transfer, + :options_when_paying_for_care, + :equity_release, + :inheritance_tax_planning, + :wills_and_probate, + :other_advice_methods, + :investment_sizes, + :in_person_advice_methods, + :adviser_qualification_ids, + :adviser_accreditation_ids, + :ethical_investing_flag, + :sharia_investing_flag, + :workplace_financial_advice_flag, + :non_uk_residents_flag, + :languages has_many :advisers has_many :offices diff --git a/app/serializers/office_serializer.rb b/app/serializers/office_serializer.rb index 2e4738133..acad880e0 100644 --- a/app/serializers/office_serializer.rb +++ b/app/serializers/office_serializer.rb @@ -1,5 +1,4 @@ class OfficeSerializer < ActiveModel::Serializer - attributes :_id, :address_line_one, :address_line_two, :address_town, :address_county, :address_postcode, :email_address, :telephone_number, :disabled_access, :location, :website diff --git a/lib/cloud/storage.rb b/lib/cloud/storage.rb index a26479412..da4d3fea6 100644 --- a/lib/cloud/storage.rb +++ b/lib/cloud/storage.rb @@ -45,7 +45,7 @@ def client def find_provider_class(name) klass = Object.const_get("::Cloud::Providers::#{name.to_s.capitalize}") klass - rescue + rescue StandardError raise ArgumentError.new('Bad cloud provider name') end end diff --git a/lib/firm_indexer.rb b/lib/firm_indexer.rb index 03dc7dd95..fa4bcfce4 100644 --- a/lib/firm_indexer.rb +++ b/lib/firm_indexer.rb @@ -8,7 +8,7 @@ def index_firm(firm) end end - alias_method :handle_firm_changed, :index_firm + alias handle_firm_changed index_firm def handle_aggregate_changed(aggregate) # This method may be invoked as part of a cascade delete, in which case @@ -20,7 +20,7 @@ def handle_aggregate_changed(aggregate) def associated_firm_destroyed?(aggregate) firm = aggregate.firm - return true if (firm.nil? || firm.destroyed?) + return true if firm.nil? || firm.destroyed? !Firm.exists?(firm.id) end diff --git a/lib/languages.rb b/lib/languages.rb index 568f85b04..e897c5073 100644 --- a/lib/languages.rb +++ b/lib/languages.rb @@ -1,8 +1,10 @@ module Languages - UK_MINORITY_LANGUAGES = %w(sco gd bfi isg).map { |l| LanguageList::LanguageInfo.find l } - EXCLUDED_LANGUAGES = %w(en).map { |l| LanguageList::LanguageInfo.find l } + # rubocop:disable Metrics/LineLength + UK_MINORITY_LANGUAGES = %w[sco gd bfi isg].map { |l| LanguageList::LanguageInfo.find l } + EXCLUDED_LANGUAGES = %w[en].map { |l| LanguageList::LanguageInfo.find l } AVAILABLE_LANGUAGES = ( (LanguageList::COMMON_LANGUAGES + UK_MINORITY_LANGUAGES) - EXCLUDED_LANGUAGES ).sort_by(&:common_name).freeze AVAILABLE_LANGUAGES_ISO_639_3_CODES = Set.new(AVAILABLE_LANGUAGES.map(&:iso_639_3)).freeze + # rubocop:enable Metrics/LineLength end diff --git a/lib/postcode.rb b/lib/postcode.rb index 7d9f3deb1..aef436ae9 100644 --- a/lib/postcode.rb +++ b/lib/postcode.rb @@ -1,8 +1,8 @@ class Postcode def filter_postcodes_by_country(postcodes, country) map_postcodes_to_country(postcodes) - .select { |postcode, postcode_country| postcode_country == country } - .map { |postcode, postcode_country| postcode } + .select { |_postcode, postcode_country| postcode_country == country } + .map { |postcode, _postcode_country| postcode } end private @@ -23,7 +23,8 @@ def map_postcodes_slice_to_country(postcodes) response = Net::HTTP.new('api.postcodes.io').request(request) if response.code.to_i == 200 - result = JSON.parse(response.read_body)['result'].map { |r| r['result'] }.compact + body = response.read_body + result = JSON.parse(body)['result'].map { |r| r['result'] }.compact result.each_with_object({}) do |r, obj| obj[r['postcode']] = r['country'] end diff --git a/lib/river/runners/tempfile.rb b/lib/river/runners/tempfile.rb index 5551ef3a3..26f29dbcd 100644 --- a/lib/river/runners/tempfile.rb +++ b/lib/river/runners/tempfile.rb @@ -4,7 +4,6 @@ module River module Runners class Tempfile class << self - # rubocop:disable Metrics/AbcSize def call(commands, context) rd = $stdin wr = $stdout @@ -21,7 +20,6 @@ def call(commands, context) wr.close unless wr.closed? || (wr == $stdout) commands end - # rubocop:enable Metrics/AbcSize private diff --git a/spec/controllers/selfservice/trading_names_controller_spec.rb b/spec/controllers/selfservice/trading_names_controller_spec.rb index 3af04b156..571920a24 100644 --- a/spec/controllers/selfservice/trading_names_controller_spec.rb +++ b/spec/controllers/selfservice/trading_names_controller_spec.rb @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- RSpec.describe SelfService::TradingNamesController, type: :controller do let(:principal) { FactoryGirl.create(:principal) } let(:firm) do diff --git a/spec/factories/adviser.rb b/spec/factories/adviser.rb index 1a3dd954c..b0d5f752c 100644 --- a/spec/factories/adviser.rb +++ b/spec/factories/adviser.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - sequence(:reference_number, 10000) { |n| "ABC#{n}" } + sequence(:reference_number, 10_000) { |n| "ABC#{n}" } factory :adviser do transient do diff --git a/spec/factories/firm.rb b/spec/factories/firm.rb index b513320b7..428ce2976 100644 --- a/spec/factories/firm.rb +++ b/spec/factories/firm.rb @@ -1,7 +1,7 @@ FactoryGirl.define do sequence(:registered_name) { |n| "Financial Advice #{n} Ltd." } - factory :firm, aliases: [:publishable_firm, :onboarded_firm] do + factory :firm, aliases: %i[publishable_firm onboarded_firm] do fca_number registered_name website_address { Faker::Internet.url } diff --git a/spec/factories/principal.rb b/spec/factories/principal.rb index 8b619cddc..69336bf08 100644 --- a/spec/factories/principal.rb +++ b/spec/factories/principal.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - sequence(:fca_number, 100000) { |n| n } + sequence(:fca_number, 100_000) { |n| n } factory :principal do fca_number diff --git a/spec/helpers/self_service/nav_helper_spec.rb b/spec/helpers/self_service/nav_helper_spec.rb index 0f23e0c00..fd70ee3d9 100644 --- a/spec/helpers/self_service/nav_helper_spec.rb +++ b/spec/helpers/self_service/nav_helper_spec.rb @@ -27,7 +27,7 @@ module SelfService let(:url_path) { 'fake_url_path' } let(:extra_css_classes) { 'extra_css_class1 extra_css_class2' } - subject { helper.tab_link(url_path, extra_css_classes, &->() { content }) } + subject { helper.tab_link(url_path, extra_css_classes, &-> { content }) } it 'creates a link for the given url_path' do expect(subject).to match(/href="#{url_path}"/) diff --git a/spec/lib/firm_repository_spec.rb b/spec/lib/firm_repository_spec.rb index 926478377..652f934fe 100644 --- a/spec/lib/firm_repository_spec.rb +++ b/spec/lib/firm_repository_spec.rb @@ -26,7 +26,7 @@ describe '#delete' do it 'delegates to the configured client' do - expect(client).to receive(:delete).with("firms/1") + expect(client).to receive(:delete).with('firms/1') described_class.new(client_class).delete(1) end diff --git a/spec/models/adviser_spec.rb b/spec/models/adviser_spec.rb index facf5f4c2..a3694291c 100644 --- a/spec/models/adviser_spec.rb +++ b/spec/models/adviser_spec.rb @@ -77,7 +77,7 @@ end it 'must be three characters and five digits exactly' do - %w(badtimes ABCDEFGH 8008135! 12345678).each do |bad| + %w[badtimes ABCDEFGH 8008135! 12345678].each do |bad| Lookup::Adviser.create!(reference_number: bad, name: 'Mr. Derp') expect(build(:adviser, @@ -115,7 +115,7 @@ let(:adviser) { create(:adviser) } subject { adviser.full_street_address } - it { is_expected.to eql "#{adviser.postcode}, United Kingdom"} + it { is_expected.to eql "#{adviser.postcode}, United Kingdom" } end it_should_behave_like 'geocodable' do @@ -136,7 +136,7 @@ end end - context "when the model postcode field has changed" do + context 'when the model postcode field has changed' do before do subject.postcode = 'S032 2AY' end @@ -271,8 +271,8 @@ def save_with_commit_callback(model) end describe '.sorted_by_name scope' do - let(:sorted_names) { %w(A B C D E F G H) } - let(:unsorted_names) { %w(F C G E D H A B) } + let(:sorted_names) { %w[A B C D E F G H] } + let(:unsorted_names) { %w[F C G E D H A B] } before do unsorted_names.each { |name| FactoryGirl.create(:adviser, name: name) } diff --git a/spec/models/firm_spec.rb b/spec/models/firm_spec.rb index 99e18e83c..5be65dbc5 100644 --- a/spec/models/firm_spec.rb +++ b/spec/models/firm_spec.rb @@ -17,15 +17,15 @@ context 'when there are a multiple languages on multiple firms' do it 'has has multiple languages' do - create(:firm, languages: ['sco', 'swe']) - create(:firm, languages: ['nor', 'lat']) - expect(Firm.languages_used).to eq(['lat', 'nor', 'sco', 'swe']) + create(:firm, languages: %w[sco swe]) + create(:firm, languages: %w[nor lat]) + expect(Firm.languages_used).to eq(%w[lat nor sco swe]) end it 'has has duplicate languages' do - create(:firm, languages: ['sco', 'swe']) - create(:firm, languages: ['nor', 'swe']) - expect(Firm.languages_used).to eq(['nor', 'sco', 'swe']) + create(:firm, languages: %w[sco swe]) + create(:firm, languages: %w[nor swe]) + expect(Firm.languages_used).to eq(%w[nor sco swe]) end end end @@ -120,7 +120,7 @@ def set_marker_field(firm, value) describe 'default sort order' do subject { firm.reload.offices.map(&:address_line_one) } - it { is_expected.to eq(%w{first second third fourth}) } + it { is_expected.to eq(%w[first second third fourth]) } end end @@ -243,12 +243,12 @@ def set_marker_field(firm, value) describe 'languages' do context 'when it contains valid language strings' do - before { firm.languages = ['fra', 'deu'] } + before { firm.languages = %w[fra deu] } it { is_expected.to be_valid } end context 'when it contains invalid language strings' do - before { firm.languages = ['no_language', 'fra'] } + before { firm.languages = %w[no_language fra] } it { is_expected.to be_invalid } end @@ -266,10 +266,10 @@ def set_marker_field(firm, value) end context 'when it contains duplicate values' do - before { firm.languages = ['fra', 'fra', 'deu'] } + before { firm.languages = %w[fra fra deu] } it 'filters them out pre-validation' do firm.valid? - expect(firm.languages).to eq ['fra', 'deu'] + expect(firm.languages).to eq %w[fra deu] end end end @@ -537,8 +537,8 @@ def set_marker_field(firm, value) end describe '.sorted_by_registered_name scope' do - let(:sorted_names) { %w(A B C D E F G H) } - let(:unsorted_names) { %w(F C G E D H A B) } + let(:sorted_names) { %w[A B C D E F G H] } + let(:unsorted_names) { %w[F C G E D H A B] } before do unsorted_names.each { |name| FactoryGirl.create(:firm, registered_name: name) } @@ -551,14 +551,12 @@ def set_marker_field(firm, value) describe '#advice_types' do it 'returns a hash of advice types' do - expect(subject.advice_types).to eq({ - retirement_income_products_flag: subject.retirement_income_products_flag, - pension_transfer_flag: subject.pension_transfer_flag, - long_term_care_flag: subject.long_term_care_flag, - equity_release_flag: subject.equity_release_flag, - inheritance_tax_and_estate_planning_flag: subject.inheritance_tax_and_estate_planning_flag, - wills_and_probate_flag: subject.wills_and_probate_flag - }) + expect(subject.advice_types).to eq(retirement_income_products_flag: subject.retirement_income_products_flag, + pension_transfer_flag: subject.pension_transfer_flag, + long_term_care_flag: subject.long_term_care_flag, + equity_release_flag: subject.equity_release_flag, + inheritance_tax_and_estate_planning_flag: subject.inheritance_tax_and_estate_planning_flag, + wills_and_probate_flag: subject.wills_and_probate_flag) end end diff --git a/spec/models/lookup/firm_spec.rb b/spec/models/lookup/firm_spec.rb index fcc8daa5f..f8c8b0958 100644 --- a/spec/models/lookup/firm_spec.rb +++ b/spec/models/lookup/firm_spec.rb @@ -2,7 +2,7 @@ describe 'validation' do it 'is valid with valid attributes' do expect( - described_class.new(fca_number: 123456, registered_name: 'Ben Lovell Ltd') + described_class.new(fca_number: 123_456, registered_name: 'Ben Lovell Ltd') ).to be_valid end @@ -12,7 +12,7 @@ end it 'accepts only 6 digits' do - expect(described_class.new(fca_number: 1234567)).to_not be_valid + expect(described_class.new(fca_number: 1_234_567)).to_not be_valid end end end diff --git a/spec/models/office_spec.rb b/spec/models/office_spec.rb index c00c50404..9a2802732 100644 --- a/spec/models/office_spec.rb +++ b/spec/models/office_spec.rb @@ -334,18 +334,18 @@ describe '#full_street_address' do subject { office.full_street_address } - it { is_expected.to eql "#{office.address_postcode}, United Kingdom"} + it { is_expected.to eql "#{office.address_postcode}, United Kingdom" } context 'when line two is nil' do before { office.address_line_two = nil } - it { is_expected.to eql "#{office.address_postcode}, United Kingdom"} + it { is_expected.to eql "#{office.address_postcode}, United Kingdom" } end context 'when line two is an empty string' do before { office.address_line_two = '' } - it { is_expected.to eql "#{office.address_postcode}, United Kingdom"} + it { is_expected.to eql "#{office.address_postcode}, United Kingdom" } end end end diff --git a/spec/models/principal_spec.rb b/spec/models/principal_spec.rb index a13aed067..a4eb46544 100644 --- a/spec/models/principal_spec.rb +++ b/spec/models/principal_spec.rb @@ -78,7 +78,7 @@ it 'must be a 6 digit number' do build(:principal).tap do |p| - p.fca_number = 12345 + p.fca_number = 12_345 expect(p).to_not be_valid end end @@ -108,7 +108,7 @@ end it 'must be a reasonably valid format' do - %w(zzz abc@abc a@a.).each do |bad| + %w[zzz abc@abc a@a.].each do |bad| principal.email_address = bad expect(principal).to_not be_valid end @@ -179,14 +179,14 @@ describe 'dough #field_order' do let(:fields) do - [ - :fca_number, - :first_name, - :last_name, - :job_title, - :email_address, - :telephone_number, - :confirmed_disclaimer + %i[ + fca_number + first_name + last_name + job_title + email_address + telephone_number + confirmed_disclaimer ] end @@ -207,8 +207,8 @@ context 'when no firms are publishable' do before do FactoryGirl.build(:invalid_firm, - fca_number: principal.fca_number, - parent: principal.firm).save(validate: false) + fca_number: principal.fca_number, + parent: principal.firm).save(validate: false) expect(principal.main_firm_with_trading_names).to have(2).items expect(principal.main_firm_with_trading_names) diff --git a/spec/models/snapshot_spec.rb b/spec/models/snapshot_spec.rb index 6d0f69674..b5f433563 100644 --- a/spec/models/snapshot_spec.rb +++ b/spec/models/snapshot_spec.rb @@ -128,7 +128,7 @@ end it do - VCR.use_cassette("england_and_scotland_postcode") do + VCR.use_cassette('england_and_scotland_postcode') do firms = subject.query_firms_in_england expect(firms).to match([firm1, firm2]) expect(firms).not_to include(firm3) @@ -148,7 +148,7 @@ end it do - VCR.use_cassette("scotland_and_england_postcode") do + VCR.use_cassette('scotland_and_england_postcode') do firms = subject.query_firms_in_scotland expect(firms).to match([firm1, firm2]) expect(firms).not_to include(firm3) @@ -168,7 +168,7 @@ end it do - VCR.use_cassette("wales_and_england_postcode") do + VCR.use_cassette('wales_and_england_postcode') do firms = subject.query_firms_in_wales expect(firms).to match([firm1, firm2]) expect(firms).not_to include(firm3) @@ -188,7 +188,7 @@ end it do - VCR.use_cassette("northern_ireland_and_england_postcode") do + VCR.use_cassette('northern_ireland_and_england_postcode') do firms = subject.query_firms_in_northern_ireland expect(firms).to match([firm1, firm2]) expect(firms).not_to include(firm3) @@ -336,7 +336,7 @@ let!(:adviser3) { FactoryGirl.create(:adviser, postcode: scotland_postcode) } it do - VCR.use_cassette("england_and_scotland_postcode") do + VCR.use_cassette('england_and_scotland_postcode') do advisers = subject.query_advisers_in_england expect(advisers).to match([adviser1, adviser2]) expect(advisers).not_to include(adviser3) @@ -350,7 +350,7 @@ let!(:adviser3) { FactoryGirl.create(:adviser, postcode: england_postcode) } it do - VCR.use_cassette("scotland_and_england_postcode") do + VCR.use_cassette('scotland_and_england_postcode') do advisers = subject.query_advisers_in_scotland expect(advisers).to match([adviser1, adviser2]) expect(advisers).not_to include(adviser3) @@ -364,7 +364,7 @@ let!(:adviser3) { FactoryGirl.create(:adviser, postcode: england_postcode) } it do - VCR.use_cassette("wales_and_england_postcode") do + VCR.use_cassette('wales_and_england_postcode') do advisers = subject.query_advisers_in_wales expect(advisers).to match([adviser1, adviser2]) expect(advisers).not_to include(adviser3) @@ -378,7 +378,7 @@ let!(:adviser3) { FactoryGirl.create(:adviser, postcode: england_postcode) } it do - VCR.use_cassette("northern_ireland_and_england_postcode") do + VCR.use_cassette('northern_ireland_and_england_postcode') do advisers = subject.query_advisers_in_northern_ireland expect(advisers).to match([adviser1, adviser2]) expect(advisers).not_to include(adviser3) @@ -698,66 +698,66 @@ describe '#metrics_in_order' do it 'has the configured order' do - expect(subject.metrics_in_order).to eq([ - :firms_with_no_minimum_fee, - :firms_with_min_fee_between_1_500, - :firms_with_min_fee_between_501_1000, - :firms_any_pot_size, - :firms_any_pot_size_min_fee_less_than_500, - :registered_firms, - :published_firms, - :firms_offering_face_to_face_advice, - :firms_offering_remote_advice, - :firms_in_england, - :firms_in_scotland, - :firms_in_wales, - :firms_in_northern_ireland, - :firms_providing_retirement_income_products, - :firms_providing_pension_transfer, - :firms_providing_long_term_care, - :firms_providing_equity_release, - :firms_providing_inheritance_tax_and_estate_planning, - :firms_providing_wills_and_probate, - :firms_providing_ethical_investing, - :firms_providing_sharia_investing, - :firms_providing_workplace_financial_advice, - :firms_providing_non_uk_residents, - :firms_offering_languages_other_than_english, - :offices_with_disabled_access, - :registered_advisers, - :advisers_in_england, - :advisers_in_scotland, - :advisers_in_wales, - :advisers_in_northern_ireland, - :advisers_who_travel_5_miles, - :advisers_who_travel_10_miles, - :advisers_who_travel_25_miles, - :advisers_who_travel_50_miles, - :advisers_who_travel_100_miles, - :advisers_who_travel_150_miles, - :advisers_who_travel_200_miles, - :advisers_who_travel_250_miles, - :advisers_who_travel_uk_wide, - :advisers_accredited_in_solla, - :advisers_accredited_in_later_life_academy, - :advisers_accredited_in_iso22222, - :advisers_accredited_in_bs8577, - :advisers_with_qualification_in_level_4, - :advisers_with_qualification_in_level_6, - :advisers_with_qualification_in_chartered_financial_planner, - :advisers_with_qualification_in_certified_financial_planner, - :advisers_with_qualification_in_pension_transfer, - :advisers_with_qualification_in_equity_release, - :advisers_with_qualification_in_long_term_care_planning, - :advisers_with_qualification_in_tep, - :advisers_with_qualification_in_fcii, - :advisers_part_of_personal_finance_society, - :advisers_part_of_institute_financial_planning, - :advisers_part_of_institute_financial_services, - :advisers_part_of_ci_bankers_scotland, - :advisers_part_of_ci_securities_and_investments, - :advisers_part_of_cfa_institute, :advisers_part_of_chartered_accountants - ]) + expect(subject.metrics_in_order).to eq(%i[ + firms_with_no_minimum_fee + firms_with_min_fee_between_1_500 + firms_with_min_fee_between_501_1000 + firms_any_pot_size + firms_any_pot_size_min_fee_less_than_500 + registered_firms + published_firms + firms_offering_face_to_face_advice + firms_offering_remote_advice + firms_in_england + firms_in_scotland + firms_in_wales + firms_in_northern_ireland + firms_providing_retirement_income_products + firms_providing_pension_transfer + firms_providing_long_term_care + firms_providing_equity_release + firms_providing_inheritance_tax_and_estate_planning + firms_providing_wills_and_probate + firms_providing_ethical_investing + firms_providing_sharia_investing + firms_providing_workplace_financial_advice + firms_providing_non_uk_residents + firms_offering_languages_other_than_english + offices_with_disabled_access + registered_advisers + advisers_in_england + advisers_in_scotland + advisers_in_wales + advisers_in_northern_ireland + advisers_who_travel_5_miles + advisers_who_travel_10_miles + advisers_who_travel_25_miles + advisers_who_travel_50_miles + advisers_who_travel_100_miles + advisers_who_travel_150_miles + advisers_who_travel_200_miles + advisers_who_travel_250_miles + advisers_who_travel_uk_wide + advisers_accredited_in_solla + advisers_accredited_in_later_life_academy + advisers_accredited_in_iso22222 + advisers_accredited_in_bs8577 + advisers_with_qualification_in_level_4 + advisers_with_qualification_in_level_6 + advisers_with_qualification_in_chartered_financial_planner + advisers_with_qualification_in_certified_financial_planner + advisers_with_qualification_in_pension_transfer + advisers_with_qualification_in_equity_release + advisers_with_qualification_in_long_term_care_planning + advisers_with_qualification_in_tep + advisers_with_qualification_in_fcii + advisers_part_of_personal_finance_society + advisers_part_of_institute_financial_planning + advisers_part_of_institute_financial_services + advisers_part_of_ci_bankers_scotland + advisers_part_of_ci_securities_and_investments + advisers_part_of_cfa_institute advisers_part_of_chartered_accountants + ]) end end end diff --git a/spec/serializers/firm_serializer_spec.rb b/spec/serializers/firm_serializer_spec.rb index 41ecc169f..fa37b295a 100644 --- a/spec/serializers/firm_serializer_spec.rb +++ b/spec/serializers/firm_serializer_spec.rb @@ -124,10 +124,10 @@ describe 'languages' do context 'when languages have been selected' do - before { firm.languages = ['fra', 'deu'] } + before { firm.languages = %w[fra deu] } it 'serializes them' do - expect(subject[:languages]).to eq(['fra', 'deu']) + expect(subject[:languages]).to eq(%w[fra deu]) end end diff --git a/spec/serializers/serializers/adviser_serializer_spec.rb b/spec/serializers/serializers/adviser_serializer_spec.rb index e91913658..1d7132313 100644 --- a/spec/serializers/serializers/adviser_serializer_spec.rb +++ b/spec/serializers/serializers/adviser_serializer_spec.rb @@ -31,11 +31,13 @@ end context 'qualifications' do - let(:qualifications) { [ - Qualification.create(id: 1, order: 1, name: 'First Qualification'), - Qualification.create(id: 2, order: 2, name: 'Second Qualification') - ] } - let(:adviser) { create(:adviser, qualifications: qualifications ) } + let(:qualifications) do + [ + Qualification.create(id: 1, order: 1, name: 'First Qualification'), + Qualification.create(id: 2, order: 2, name: 'Second Qualification') + ] + end + let(:adviser) { create(:adviser, qualifications: qualifications) } it 'exposes `qualification_ids`' do expect(subject[:qualification_ids]).to eq([qualifications.first.id, qualifications.last.id]) @@ -43,11 +45,13 @@ end context 'accreditations' do - let(:accreditations) { [ - Accreditation.create!(id: 1, name: 'First Accreditation', order: 1), - Accreditation.create!(id: 2, name: 'Second Accreditation', order: 2) - ] } - let(:adviser) { create(:adviser, accreditations: accreditations ) } + let(:accreditations) do + [ + Accreditation.create!(id: 1, name: 'First Accreditation', order: 1), + Accreditation.create!(id: 2, name: 'Second Accreditation', order: 2) + ] + end + let(:adviser) { create(:adviser, accreditations: accreditations) } it 'exposes `qualification_ids`' do expect(subject[:accreditation_ids]).to eq([accreditations.first.id, accreditations.last.id]) diff --git a/spec/serializers/serializers/firm_serializer_spec.rb b/spec/serializers/serializers/firm_serializer_spec.rb index 41ecc169f..fa37b295a 100644 --- a/spec/serializers/serializers/firm_serializer_spec.rb +++ b/spec/serializers/serializers/firm_serializer_spec.rb @@ -124,10 +124,10 @@ describe 'languages' do context 'when languages have been selected' do - before { firm.languages = ['fra', 'deu'] } + before { firm.languages = %w[fra deu] } it 'serializes them' do - expect(subject[:languages]).to eq(['fra', 'deu']) + expect(subject[:languages]).to eq(%w[fra deu]) end end diff --git a/spec/support/self_service/offices_table_row_section.rb b/spec/support/self_service/offices_table_row_section.rb index cdf641a39..b220bebb8 100644 --- a/spec/support/self_service/offices_table_row_section.rb +++ b/spec/support/self_service/offices_table_row_section.rb @@ -11,7 +11,7 @@ class OfficesTableRowSection < SitePrism::Section def the_main_office? main_firm.present? - rescue + rescue StandardError false end end diff --git a/spec/support/shared_examples/geocodable_examples.rb b/spec/support/shared_examples/geocodable_examples.rb index bf0357749..36869f7a5 100644 --- a/spec/support/shared_examples/geocodable_examples.rb +++ b/spec/support/shared_examples/geocodable_examples.rb @@ -56,7 +56,8 @@ def modify_address(subject) describe '#geocoded?' do context 'when the subject has lat/long' do before do - subject.latitude, subject.longitude = [1.0, 1.0] + subject.latitude = 1.0 + subject.longitude = 1.0 end it 'is classed as geocoded' do @@ -66,7 +67,8 @@ def modify_address(subject) context 'when the subject does not have lat/long' do before do - subject.latitude, subject.longitude = [nil, nil] + subject.latitude = nil + subject.longitude = nil end it 'is not classed as geocoded' do diff --git a/spec/support/shared_examples/system_named.rb b/spec/support/shared_examples/system_named.rb index ace406f4e..963f485db 100644 --- a/spec/support/shared_examples/system_named.rb +++ b/spec/support/shared_examples/system_named.rb @@ -3,8 +3,8 @@ let(:method) { create(type, order: 1) } before do - fail unless described_class::SYSTEM_NAMES - stub_const("#{described_class.model_name}::SYSTEM_NAMES", { 1 => :phone }) + raise unless described_class::SYSTEM_NAMES + stub_const("#{described_class.model_name}::SYSTEM_NAMES", 1 => :phone) end subject { described_class.system_name(method.id) }