diff --git a/app/models/relation_status.rb b/app/models/relation_status.rb new file mode 100644 index 0000000..7f99988 --- /dev/null +++ b/app/models/relation_status.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class RelationStatus < ApplicationRecord + CODENAME_RE = /\A[a-z][a-z0-9]*(_[a-z0-9]+)*\z/.freeze + FORMAT_RE = /\A[^[:space:]]+(.*[^[:space:]]+)?\z/.freeze + + ############### + # Validations # + ############### + + validates :codename, + presence: true, + length: { in: 3..36 }, + format: CODENAME_RE, + uniqueness: { case_sensitive: false } + + validates :name, + presence: true, + length: { in: 1..255 }, + format: FORMAT_RE, + uniqueness: true + + ########### + # Methods # + ########### + + def to_param + codename + end +end diff --git a/db/migrate/20190921142404_create_relation_statuses.rb b/db/migrate/20190921142404_create_relation_statuses.rb new file mode 100644 index 0000000..2f2dda0 --- /dev/null +++ b/db/migrate/20190921142404_create_relation_statuses.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class CreateRelationStatuses < ActiveRecord::Migration[6.0] + include Partynest::Migration + + def change + create_table :relation_statuses do |t| + t.timestamps null: false + + t.string :codename, null: false, index: { unique: true } + t.string :name, null: false, index: { unique: true } + end + + constraint :relation_statuses, :codename, <<~SQL + is_codename(codename) + SQL + + constraint :relation_statuses, :name, <<~SQL + is_good_small_text(name) + SQL + end +end diff --git a/db/structure.sql b/db/structure.sql index 5b119a5..5a7ae0c 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -680,6 +680,40 @@ CREATE SEQUENCE public.regional_offices_id_seq ALTER SEQUENCE public.regional_offices_id_seq OWNED BY public.regional_offices.id; +-- +-- Name: relation_statuses; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.relation_statuses ( + id bigint NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + codename character varying NOT NULL, + name character varying NOT NULL, + CONSTRAINT codename CHECK (public.is_codename((codename)::text)), + CONSTRAINT name CHECK (public.is_good_small_text((name)::text)) +); + + +-- +-- Name: relation_statuses_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.relation_statuses_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: relation_statuses_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.relation_statuses_id_seq OWNED BY public.relation_statuses.id; + + -- -- Name: relationship_statuses; Type: TABLE; Schema: public; Owner: - -- @@ -958,6 +992,13 @@ ALTER TABLE ONLY public.person_comments ALTER COLUMN id SET DEFAULT nextval('pub ALTER TABLE ONLY public.regional_offices ALTER COLUMN id SET DEFAULT nextval('public.regional_offices_id_seq'::regclass); +-- +-- Name: relation_statuses id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.relation_statuses ALTER COLUMN id SET DEFAULT nextval('public.relation_statuses_id_seq'::regclass); + + -- -- Name: relationship_statuses id; Type: DEFAULT; Schema: public; Owner: - -- @@ -1089,6 +1130,14 @@ ALTER TABLE ONLY public.regional_offices ADD CONSTRAINT regional_offices_pkey PRIMARY KEY (id); +-- +-- Name: relation_statuses relation_statuses_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.relation_statuses + ADD CONSTRAINT relation_statuses_pkey PRIMARY KEY (id); + + -- -- Name: relationship_statuses relationship_statuses_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1291,6 +1340,20 @@ CREATE UNIQUE INDEX index_regional_offices_on_federal_subject_id ON public.regio CREATE UNIQUE INDEX index_regional_offices_on_name ON public.regional_offices USING btree (name); +-- +-- Name: index_relation_statuses_on_codename; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_relation_statuses_on_codename ON public.relation_statuses USING btree (codename); + + +-- +-- Name: index_relation_statuses_on_name; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_relation_statuses_on_name ON public.relation_statuses USING btree (name); + + -- -- Name: index_relationship_statuses_on_codename; Type: INDEX; Schema: public; Owner: - -- @@ -1586,6 +1649,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20190911081459'), ('20190914050858'), ('20190915085803'), -('20190915131325'); +('20190915131325'), +('20190921142404'); diff --git a/factories/relation_statuses.rb b/factories/relation_statuses.rb new file mode 100644 index 0000000..5b162ba --- /dev/null +++ b/factories/relation_statuses.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :some_relation_status, class: RelationStatus do + codename { Faker::Internet.unique.username 3..36, %w[_] } + name { Faker::Company.unique.name } + end +end diff --git a/spec/models/relation_status_spec.rb b/spec/models/relation_status_spec.rb new file mode 100644 index 0000000..ecd7051 --- /dev/null +++ b/spec/models/relation_status_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe RelationStatus do + subject { create :some_relation_status } + + describe '#to_param' do + specify do + expect(subject.to_param).to eq subject.codename + end + end + + describe '#codename' do + def allow_value(*) + super.for :codename + end + + it { is_expected.to validate_presence_of :codename } + it { is_expected.to validate_uniqueness_of(:codename).case_insensitive } + + it do + is_expected.to validate_length_of(:codename).is_at_least(3).is_at_most(36) + end + + it { is_expected.not_to allow_value nil } + it { is_expected.not_to allow_value '' } + it { is_expected.not_to allow_value ' ' * 3 } + + it { is_expected.to allow_value Faker::Internet.username(3..36, %w[_]) } + it { is_expected.to allow_value 'foo_bar' } + it { is_expected.to allow_value 'foo123' } + + it do + is_expected.not_to \ + allow_value Faker::Internet.username(3..36, %w[_]).upcase + end + + it { is_expected.not_to allow_value Faker::Internet.email } + it { is_expected.not_to allow_value '_foo' } + it { is_expected.not_to allow_value 'bar_' } + it { is_expected.not_to allow_value '1foo' } + end + + describe '#name' do + def allow_value(*) + super.for :name + end + + it { is_expected.to validate_presence_of :name } + it { is_expected.to validate_uniqueness_of :name } + + it do + is_expected.to validate_length_of(:name).is_at_least(1).is_at_most(255) + end + + it { is_expected.not_to allow_value nil } + it { is_expected.not_to allow_value '' } + it { is_expected.not_to allow_value ' ' } + + it { is_expected.to allow_value Faker::Name.name } + it { is_expected.to allow_value Faker::Name.first_name } + it { is_expected.to allow_value 'Foo Bar' } + + it { is_expected.not_to allow_value ' Foo' } + it { is_expected.not_to allow_value 'Foo ' } + it { is_expected.not_to allow_value "\tFoo" } + it { is_expected.not_to allow_value "Foo\t" } + it { is_expected.not_to allow_value "\nFoo" } + it { is_expected.not_to allow_value "Foo\n" } + end +end