From abdaf0a31c23bebfadeea8c139151d43998f9121 Mon Sep 17 00:00:00 2001 From: ntxtthomas Date: Tue, 10 Mar 2026 18:45:42 -0500 Subject: [PATCH] validate company entries --- app/models/company.rb | 6 ++++ ...0000_add_unique_index_on_companies_name.rb | 28 +++++++++++++++++++ spec/models/company_spec.rb | 28 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 db/migrate/20260310120000_add_unique_index_on_companies_name.rb create mode 100644 spec/models/company_spec.rb diff --git a/app/models/company.rb b/app/models/company.rb index 0f3bf5f..f74b80d 100644 --- a/app/models/company.rb +++ b/app/models/company.rb @@ -3,10 +3,12 @@ class Company < ApplicationRecord has_many :opportunities, dependent: :destroy has_many :resource_sheets, dependent: :nullify + before_validation :normalize_name before_save :shorten_urls, :sanitize_size # Validations validates :name, presence: true + validates :name, uniqueness: { case_sensitive: false } validates :company_type, inclusion: { in: %w[Product Consultancy Staffing], message: "%{value} is not a valid company type" }, allow_nil: true validates :size, format: { with: /\A[\d,\-\s]*\z/, message: "should be a range like '501-1,000'" }, allow_nil: true, allow_blank: true @@ -64,6 +66,10 @@ def tech_stack_with_sources private + def normalize_name + self.name = name.to_s.squish.presence + end + def shorten_urls if website_changed? && website.present? && !website.include?("is.gd") shortened = UrlShortenerService.shorten(website) diff --git a/db/migrate/20260310120000_add_unique_index_on_companies_name.rb b/db/migrate/20260310120000_add_unique_index_on_companies_name.rb new file mode 100644 index 0000000..75d7e51 --- /dev/null +++ b/db/migrate/20260310120000_add_unique_index_on_companies_name.rb @@ -0,0 +1,28 @@ +class AddUniqueIndexOnCompaniesName < ActiveRecord::Migration[8.0] + def up + execute <<~SQL + UPDATE companies + SET name = NULLIF(TRIM(REGEXP_REPLACE(name, '\\s+', ' ', 'g')), '') + WHERE name IS NOT NULL; + SQL + + duplicate_names = select_values(<<~SQL) + SELECT LOWER(name) + FROM companies + WHERE name IS NOT NULL + GROUP BY LOWER(name) + HAVING COUNT(*) > 1 + SQL + + if duplicate_names.any? + raise ActiveRecord::IrreversibleMigration, + "Cannot add unique index on companies.name. Resolve duplicates first: #{duplicate_names.join(', ')}" + end + + add_index :companies, "LOWER(name)", unique: true, name: "index_companies_on_lower_name" + end + + def down + remove_index :companies, name: "index_companies_on_lower_name" + end +end diff --git a/spec/models/company_spec.rb b/spec/models/company_spec.rb new file mode 100644 index 0000000..074fe4d --- /dev/null +++ b/spec/models/company_spec.rb @@ -0,0 +1,28 @@ +require "rails_helper" + +RSpec.describe Company, type: :model do + describe "validations" do + it "validates name presence" do + company = Company.new(name: nil, company_type: "Product") + + expect(company).not_to be_valid + expect(company.errors[:name]).to include("can't be blank") + end + + it "validates name uniqueness case-insensitively" do + Company.create!(name: "Acme Corp", company_type: "Product") + duplicate = Company.new(name: "acme corp", company_type: "Product") + + expect(duplicate).not_to be_valid + expect(duplicate.errors[:name]).to include("has already been taken") + end + + it "normalizes whitespace in name before validation" do + company = Company.new(name: " New Co ", company_type: "Product") + + company.validate + + expect(company.name).to eq("New Co") + end + end +end