From e87407bbd3f7b704f52322d3859e6e393c8be4db Mon Sep 17 00:00:00 2001 From: Oleksii Kriuchykhin Date: Thu, 29 Oct 2020 15:44:21 +0100 Subject: [PATCH 1/2] Create database structure for project folders [SCI-5126] --- app/models/project.rb | 1 + app/models/project_folder.rb | 26 +++++ app/models/team.rb | 1 + .../20201028103608_create_project_folders.rb | 26 +++++ db/structure.sql | 106 +++++++++++++++++- 5 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 app/models/project_folder.rb create mode 100644 db/migrate/20201028103608_create_project_folders.rb diff --git a/app/models/project.rb b/app/models/project.rb index f0f01fc6a..589af04de 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -30,6 +30,7 @@ class Project < ApplicationRecord class_name: 'User', optional: true belongs_to :team, inverse_of: :projects, touch: true + belongs_to :project_folder, inverse_of: :projects, optional: true has_many :user_projects, inverse_of: :project has_many :users, through: :user_projects has_many :experiments, inverse_of: :project diff --git a/app/models/project_folder.rb b/app/models/project_folder.rb new file mode 100644 index 000000000..2099585a0 --- /dev/null +++ b/app/models/project_folder.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class ProjectFolder < ApplicationRecord + include SearchableModel + include SearchableByNameModel + + validates :name, + length: { minimum: Constants::NAME_MIN_LENGTH, + maximum: Constants::NAME_MAX_LENGTH }, + uniqueness: { scope: :team_id, case_sensitive: false } + + before_validation :inherit_team_from_parent_folder, if: -> { parent_folder.present? } + + belongs_to :team, inverse_of: :project_folders, touch: true + belongs_to :parent_folder, class_name: 'ProjectFolder', optional: true + has_many :projects, inverse_of: :project_folder, dependent: :nullify + has_many :project_folders, foreign_key: 'parent_folder_id', inverse_of: :parent_folder, dependent: :destroy + + scope :top_level, -> { where(parent_folder: nil) } + + private + + def inherit_team_from_parent_folder + self.team = parent_folder.team + end +end diff --git a/app/models/team.rb b/app/models/team.rb index 925fd0bbe..e4cd39a74 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -31,6 +31,7 @@ class Team < ApplicationRecord has_many :user_teams, inverse_of: :team, dependent: :destroy has_many :users, through: :user_teams has_many :projects, inverse_of: :team + has_many :project_folders, inverse_of: :team, dependent: :destroy has_many :protocols, inverse_of: :team, dependent: :destroy has_many :protocol_keywords, inverse_of: :team, dependent: :destroy has_many :tiny_mce_assets, inverse_of: :team, dependent: :destroy diff --git a/db/migrate/20201028103608_create_project_folders.rb b/db/migrate/20201028103608_create_project_folders.rb new file mode 100644 index 000000000..ec90d90bd --- /dev/null +++ b/db/migrate/20201028103608_create_project_folders.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class CreateProjectFolders < ActiveRecord::Migration[6.0] + include DatabaseHelper + + def up + create_table :project_folders do |t| + t.string :name, null: false + t.references :team, index: true, null: false, foreign_key: true + t.references :parent_folder, index: true, foreign_key: { to_table: :project_folders }, null: true + t.timestamps + end + + add_reference :projects, :project_folder, index: true, foreign_key: true + + add_gin_index_without_tags :project_folders, :name + end + + def down + remove_index :project_folders, name: :index_project_folders_on_name + + remove_reference :projects, :project_folder, index: true, foreign_key: true + + drop_table :project_folders + end +end diff --git a/db/structure.sql b/db/structure.sql index 7076a31e1..0bab42098 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -985,6 +985,39 @@ CREATE SEQUENCE public.oauth_applications_id_seq ALTER SEQUENCE public.oauth_applications_id_seq OWNED BY public.oauth_applications.id; +-- +-- Name: project_folders; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.project_folders ( + id bigint NOT NULL, + name character varying NOT NULL, + team_id bigint NOT NULL, + parent_folder_id bigint, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: project_folders_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.project_folders_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_folders_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.project_folders_id_seq OWNED BY public.project_folders.id; + + -- -- Name: projects; Type: TABLE; Schema: public; Owner: - -- @@ -1006,7 +1039,8 @@ CREATE TABLE public.projects ( restored_on timestamp without time zone, experiments_order character varying, template boolean, - demo boolean DEFAULT false NOT NULL + demo boolean DEFAULT false NOT NULL, + project_folder_id bigint ); @@ -2947,6 +2981,13 @@ ALTER TABLE ONLY public.oauth_access_tokens ALTER COLUMN id SET DEFAULT nextval( ALTER TABLE ONLY public.oauth_applications ALTER COLUMN id SET DEFAULT nextval('public.oauth_applications_id_seq'::regclass); +-- +-- Name: project_folders id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_folders ALTER COLUMN id SET DEFAULT nextval('public.project_folders_id_seq'::regclass); + + -- -- Name: projects id; Type: DEFAULT; Schema: public; Owner: - -- @@ -3504,6 +3545,14 @@ ALTER TABLE ONLY public.oauth_applications ADD CONSTRAINT oauth_applications_pkey PRIMARY KEY (id); +-- +-- Name: project_folders project_folders_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_folders + ADD CONSTRAINT project_folders_pkey PRIMARY KEY (id); + + -- -- Name: projects projects_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -4452,6 +4501,27 @@ CREATE INDEX index_on_repository_checklist_item_id ON public.repository_checklis CREATE INDEX index_on_repository_checklist_value_id ON public.repository_checklist_items_values USING btree (repository_checklist_value_id); +-- +-- Name: index_project_folders_on_name; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_project_folders_on_name ON public.project_folders USING gin (public.trim_html_tags((name)::text) public.gin_trgm_ops); + + +-- +-- Name: index_project_folders_on_parent_folder_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_project_folders_on_parent_folder_id ON public.project_folders USING btree (parent_folder_id); + + +-- +-- Name: index_project_folders_on_team_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_project_folders_on_team_id ON public.project_folders USING btree (team_id); + + -- -- Name: index_projects_on_archived_by_id; Type: INDEX; Schema: public; Owner: - -- @@ -4480,6 +4550,13 @@ CREATE INDEX index_projects_on_last_modified_by_id ON public.projects USING btre CREATE INDEX index_projects_on_name ON public.projects USING gin (public.trim_html_tags((name)::text) public.gin_trgm_ops); +-- +-- Name: index_projects_on_project_folder_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_projects_on_project_folder_id ON public.projects USING btree (project_folder_id); + + -- -- Name: index_projects_on_restored_by_id; Type: INDEX; Schema: public; Owner: - -- @@ -5617,6 +5694,14 @@ ALTER TABLE ONLY public.report_elements ADD CONSTRAINT fk_rails_0510000a52 FOREIGN KEY (table_id) REFERENCES public.tables(id); +-- +-- Name: project_folders fk_rails_05fe6e31fe; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_folders + ADD CONSTRAINT fk_rails_05fe6e31fe FOREIGN KEY (parent_folder_id) REFERENCES public.project_folders(id); + + -- -- Name: assets fk_rails_0916329f9e; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -6025,6 +6110,14 @@ ALTER TABLE ONLY public.repository_status_items ADD CONSTRAINT fk_rails_74e5e4cacc FOREIGN KEY (repository_column_id) REFERENCES public.repository_columns(id); +-- +-- Name: project_folders fk_rails_795296de66; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_folders + ADD CONSTRAINT fk_rails_795296de66 FOREIGN KEY (team_id) REFERENCES public.teams(id); + + -- -- Name: results fk_rails_79fcaa8e37; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -6753,6 +6846,14 @@ ALTER TABLE ONLY public.team_repositories ADD CONSTRAINT fk_rails_f99472b670 FOREIGN KEY (team_id) REFERENCES public.teams(id); +-- +-- Name: projects fk_rails_fbf93d1a3d; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects + ADD CONSTRAINT fk_rails_fbf93d1a3d FOREIGN KEY (project_folder_id) REFERENCES public.project_folders(id); + + -- -- Name: my_modules fk_rails_fd094f363d; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -6960,6 +7061,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20200713142353'), ('20200714082503'), ('20200826143431'), -('20200909121441'); +('20200909121441'), +('20201028103608'); From bb1d9e883f28182dd6cf68501f8cc7cc3d20e8ed Mon Sep 17 00:00:00 2001 From: Oleksii Kriuchykhin Date: Sun, 1 Nov 2020 23:07:57 +0100 Subject: [PATCH 2/2] Add ProjectFolder factory and specs [SCI-5126] --- spec/factories/project_folders.rb | 8 ++++++ spec/models/project_folder_spec.rb | 44 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 spec/factories/project_folders.rb create mode 100644 spec/models/project_folder_spec.rb diff --git a/spec/factories/project_folders.rb b/spec/factories/project_folders.rb new file mode 100644 index 000000000..8a7308290 --- /dev/null +++ b/spec/factories/project_folders.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :project_folder do + sequence(:name) { |n| "My projects folder (#{n})" } + team { create :team } + end +end diff --git a/spec/models/project_folder_spec.rb b/spec/models/project_folder_spec.rb new file mode 100644 index 000000000..131827e84 --- /dev/null +++ b/spec/models/project_folder_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ProjectFolder, type: :model do + let(:project_folder) { build :project_folder } + + it 'is valid' do + expect(project_folder).to be_valid + end + + it 'should be of class ProjectFolder' do + expect(subject.class).to eq ProjectFolder + end + + describe 'Database table' do + it { should have_db_column :id } + it { should have_db_column :name } + it { should have_db_column :team_id } + it { should have_db_column :parent_folder_id } + it { should have_db_column :created_at } + it { should have_db_column :updated_at } + end + + describe 'Relations' do + it { should belong_to(:team) } + it { should belong_to(:parent_folder).class_name('ProjectFolder').optional } + it { should have_many :projects } + it { should have_many :project_folders } + end + + describe 'Validations' do + describe '#name' do + it do + is_expected.to(validate_length_of(:name) + .is_at_least(Constants::NAME_MIN_LENGTH) + .is_at_most(Constants::NAME_MAX_LENGTH)) + end + it do + expect(project_folder).to validate_uniqueness_of(:name).scoped_to(:team_id).case_insensitive + end + end + end +end