Validate hierarchies
This commit is contained in:
parent
277fad827d
commit
d4540f8498
13 changed files with 611 additions and 33 deletions
|
@ -9,15 +9,15 @@ class OrgUnit < ApplicationRecord
|
||||||
class_name: 'OrgUnitKind',
|
class_name: 'OrgUnitKind',
|
||||||
inverse_of: :instances
|
inverse_of: :instances
|
||||||
|
|
||||||
belongs_to :parent,
|
belongs_to :parent_unit,
|
||||||
class_name: 'OrgUnit',
|
class_name: 'OrgUnit',
|
||||||
inverse_of: :children,
|
inverse_of: :children_units,
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
has_many :children,
|
has_many :children_units,
|
||||||
class_name: 'OrgUnit',
|
class_name: 'OrgUnit',
|
||||||
inverse_of: :parent,
|
inverse_of: :parent_unit,
|
||||||
foreign_key: :parent_id
|
foreign_key: :parent_unit_id
|
||||||
|
|
||||||
has_many :all_relationships,
|
has_many :all_relationships,
|
||||||
class_name: 'Relationship',
|
class_name: 'Relationship',
|
||||||
|
@ -31,7 +31,7 @@ class OrgUnit < ApplicationRecord
|
||||||
|
|
||||||
validates :name, good_small_text: true, uniqueness: true
|
validates :name, good_small_text: true, uniqueness: true
|
||||||
|
|
||||||
validates :parent,
|
validates :parent_unit,
|
||||||
presence: {
|
presence: {
|
||||||
if: ->(record) { record.kind&.parent_kind },
|
if: ->(record) { record.kind&.parent_kind },
|
||||||
message: :required,
|
message: :required,
|
||||||
|
@ -39,9 +39,19 @@ class OrgUnit < ApplicationRecord
|
||||||
|
|
||||||
validate :parent_matches_kind
|
validate :parent_matches_kind
|
||||||
|
|
||||||
|
#############
|
||||||
|
# Callbacks #
|
||||||
|
#############
|
||||||
|
|
||||||
|
before_validation :set_level
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def parent_matches_kind
|
def parent_matches_kind
|
||||||
errors.add :parent unless parent&.kind == kind&.parent_kind
|
errors.add :parent_unit unless parent_unit&.kind == kind&.parent_kind
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_level
|
||||||
|
self.level = parent_unit.nil? ? 0 : parent_unit.level + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,6 +30,12 @@ class OrgUnitKind < ApplicationRecord
|
||||||
|
|
||||||
validates :name, good_small_text: true, uniqueness: true
|
validates :name, good_small_text: true, uniqueness: true
|
||||||
|
|
||||||
|
#############
|
||||||
|
# Callbacks #
|
||||||
|
#############
|
||||||
|
|
||||||
|
before_validation :set_level
|
||||||
|
|
||||||
###########
|
###########
|
||||||
# Methods #
|
# Methods #
|
||||||
###########
|
###########
|
||||||
|
@ -37,4 +43,10 @@ class OrgUnitKind < ApplicationRecord
|
||||||
def to_param
|
def to_param
|
||||||
codename
|
codename
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_level
|
||||||
|
self.level = parent_kind.nil? ? 0 : parent_kind.level + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,13 +7,35 @@ class Relationship < ApplicationRecord
|
||||||
|
|
||||||
belongs_to :org_unit, inverse_of: :all_relationships
|
belongs_to :org_unit, inverse_of: :all_relationships
|
||||||
|
|
||||||
|
belongs_to :parent_rel,
|
||||||
|
class_name: 'Relationship',
|
||||||
|
inverse_of: :children_rels,
|
||||||
|
optional: true
|
||||||
|
|
||||||
belongs_to :status, class_name: 'RelationStatus'
|
belongs_to :status, class_name: 'RelationStatus'
|
||||||
|
|
||||||
belongs_to :person, inverse_of: :all_relationships
|
belongs_to :person, inverse_of: :all_relationships
|
||||||
|
|
||||||
|
has_many :children_rels,
|
||||||
|
class_name: 'Relationship',
|
||||||
|
inverse_of: :parent_rel,
|
||||||
|
foreign_key: :parent_rel_id
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Validations #
|
# Validations #
|
||||||
###############
|
###############
|
||||||
|
|
||||||
validates :from_date, presence: true, uniqueness: { scope: :person_id }
|
validates :from_date, presence: true, uniqueness: { scope: :person_id }
|
||||||
|
|
||||||
|
#############
|
||||||
|
# Callbacks #
|
||||||
|
#############
|
||||||
|
|
||||||
|
before_validation :set_level
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_level
|
||||||
|
self.level = parent_rel.nil? ? 0 : parent_rel.level + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,15 +24,15 @@
|
||||||
<dt><%= OrgUnit.human_attribute_name :name %></dt>
|
<dt><%= OrgUnit.human_attribute_name :name %></dt>
|
||||||
<dd><%= @org_unit.name %></dd>
|
<dd><%= @org_unit.name %></dd>
|
||||||
|
|
||||||
<dt><%= OrgUnit.human_attribute_name :parent %></dt>
|
<dt><%= OrgUnit.human_attribute_name :parent_unit %></dt>
|
||||||
<dd>
|
<dd>
|
||||||
<% if @org_unit.parent.nil? %>
|
<% if @org_unit.parent_unit.nil? %>
|
||||||
<%= none %>
|
<%= none %>
|
||||||
<% elsif policy([:staff, @org_unit.parent]).show? %>
|
<% elsif policy([:staff, @org_unit.parent_unit]).show? %>
|
||||||
<%= link_to @org_unit.parent.name,
|
<%= link_to @org_unit.parent_unit.name,
|
||||||
[:staff, @org_unit.parent] %>
|
[:staff, @org_unit.parent_unit] %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= @org_unit.parent.name %>
|
<%= @org_unit.parent_unit.name %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
|
@ -77,7 +77,7 @@ en:
|
||||||
kind: Type
|
kind: Type
|
||||||
short_name: Short name
|
short_name: Short name
|
||||||
name: Name
|
name: Name
|
||||||
parent: Parent
|
parent_unit: Parent
|
||||||
org_unit_kind:
|
org_unit_kind:
|
||||||
id: ID
|
id: ID
|
||||||
codename: Codename
|
codename: Codename
|
||||||
|
|
|
@ -77,7 +77,7 @@ ru:
|
||||||
kind: Тип
|
kind: Тип
|
||||||
short_name: Короткое развание
|
short_name: Короткое развание
|
||||||
name: Название
|
name: Название
|
||||||
parent: Родитель
|
parent_unit: Родитель
|
||||||
org_unit_kind:
|
org_unit_kind:
|
||||||
id: ID
|
id: ID
|
||||||
codename: Кодовое имя
|
codename: Кодовое имя
|
||||||
|
|
229
db/migrate/20191002002101_add_level_to_tables.rb
Normal file
229
db/migrate/20191002002101_add_level_to_tables.rb
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddLevelToTables < ActiveRecord::Migration[6.0]
|
||||||
|
include Partynest::Migration
|
||||||
|
|
||||||
|
def change
|
||||||
|
rename_column :org_units, :parent_id, :parent_unit_id
|
||||||
|
|
||||||
|
add_reference :relationships,
|
||||||
|
:parent_rel,
|
||||||
|
null: true,
|
||||||
|
index: true,
|
||||||
|
foreign_key: { to_table: :relationships }
|
||||||
|
|
||||||
|
# rubocop:disable Rails/NotNullColumn
|
||||||
|
add_column :org_unit_kinds, :level, :integer, null: false
|
||||||
|
add_column :org_units, :level, :integer, null: false
|
||||||
|
add_column :relationships, :level, :integer, null: false
|
||||||
|
# rubocop:enable Rails/NotNullColumn
|
||||||
|
|
||||||
|
add_constraint :org_unit_kinds, :level, 'level >= 0'
|
||||||
|
add_constraint :org_units, :level, 'level >= 0'
|
||||||
|
add_constraint :relationships, :level, 'level >= 0'
|
||||||
|
|
||||||
|
add_constraint :org_unit_kinds, :parent_kind, 'parent_kind_id != id'
|
||||||
|
add_constraint :org_units, :parent_unit, 'parent_unit_id != id'
|
||||||
|
add_constraint :relationships, :parent_rel, 'parent_rel_id != id'
|
||||||
|
|
||||||
|
add_func_validate_org_unit_kind_hierarchy
|
||||||
|
add_func_validate_org_unit_hierarchy
|
||||||
|
add_func_validate_relationship_hierarchy
|
||||||
|
|
||||||
|
add_trigger :org_unit_kinds,
|
||||||
|
:validate_hierarchy,
|
||||||
|
'BEFORE INSERT OR UPDATE',
|
||||||
|
'validate_org_unit_kind_hierarchy()'
|
||||||
|
|
||||||
|
add_trigger :org_units,
|
||||||
|
:validate_hierarchy,
|
||||||
|
'BEFORE INSERT OR UPDATE',
|
||||||
|
'validate_org_unit_hierarchy()'
|
||||||
|
|
||||||
|
add_trigger :relationships,
|
||||||
|
:validate_hierarchy,
|
||||||
|
'BEFORE INSERT OR UPDATE',
|
||||||
|
'validate_relationship_hierarchy()'
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_func_validate_org_unit_kind_hierarchy
|
||||||
|
add_func :validate_org_unit_kind_hierarchy, <<~SQL
|
||||||
|
() RETURNS trigger LANGUAGE plpgsql AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
parent_kind record;
|
||||||
|
BEGIN
|
||||||
|
IF NEW.parent_kind_id IS NULL THEN
|
||||||
|
IF NEW.level != 0 THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_unit_kinds
|
||||||
|
INTO parent_kind
|
||||||
|
WHERE id = NEW.parent_kind_id;
|
||||||
|
|
||||||
|
IF parent_kind IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.level != parent_kind.level + 1 THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_func_validate_org_unit_hierarchy
|
||||||
|
add_func :validate_org_unit_hierarchy, <<~SQL
|
||||||
|
() RETURNS trigger LANGUAGE plpgsql AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
kind record;
|
||||||
|
parent_kind record;
|
||||||
|
parent_unit record;
|
||||||
|
BEGIN
|
||||||
|
IF NEW.kind_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'does not have type';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_unit_kinds
|
||||||
|
INTO kind
|
||||||
|
WHERE id = NEW.kind_id;
|
||||||
|
|
||||||
|
IF kind IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find type';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_unit_kinds
|
||||||
|
INTO parent_kind
|
||||||
|
WHERE id = kind.parent_kind_id;
|
||||||
|
|
||||||
|
IF (kind.parent_kind_id IS NULL) != (parent_kind IS NULL) THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent type';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF parent_kind IS NULL THEN
|
||||||
|
IF NEW.parent_unit_id IS NOT NULL THEN
|
||||||
|
RAISE EXCEPTION 'parent is invalid (expected NULL)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.level != 0 THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.parent_unit_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'parent is invalid (expected NOT NULL)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_units
|
||||||
|
INTO parent_unit
|
||||||
|
WHERE id = NEW.parent_unit_id;
|
||||||
|
|
||||||
|
IF parent_unit IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF parent_unit.kind_id != parent_kind.id THEN
|
||||||
|
RAISE EXCEPTION 'parent is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF (
|
||||||
|
NEW.level != kind.level OR
|
||||||
|
NEW.level != parent_kind.level + 1 OR
|
||||||
|
NEW.level != parent_unit.level + 1
|
||||||
|
) THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_func_validate_relationship_hierarchy
|
||||||
|
add_func :validate_relationship_hierarchy, <<~SQL
|
||||||
|
() RETURNS trigger LANGUAGE plpgsql AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
org_unit record;
|
||||||
|
parent_unit record;
|
||||||
|
parent_rel record;
|
||||||
|
BEGIN
|
||||||
|
IF NEW.org_unit_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'does not have org unit';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_units
|
||||||
|
INTO org_unit
|
||||||
|
WHERE id = NEW.org_unit_id;
|
||||||
|
|
||||||
|
IF org_unit IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find org unit';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_units
|
||||||
|
INTO parent_unit
|
||||||
|
WHERE id = org_unit.parent_unit_id;
|
||||||
|
|
||||||
|
IF (org_unit.parent_unit_id IS NULL) != (parent_unit IS NULL) THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent org unit';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF parent_unit IS NULL THEN
|
||||||
|
IF NEW.parent_rel_id IS NOT NULL THEN
|
||||||
|
RAISE EXCEPTION 'parent rel is invalid (expected NULL)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.level != 0 THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid (expected 0)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.parent_rel_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'parent rel is invalid (expected NOT NULL)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM relationships
|
||||||
|
INTO parent_rel
|
||||||
|
WHERE id = NEW.parent_rel_id;
|
||||||
|
|
||||||
|
IF parent_rel IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent rel';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF parent_rel.org_unit_id != parent_unit.id THEN
|
||||||
|
RAISE EXCEPTION 'parent rel is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF (
|
||||||
|
NEW.level != org_unit.level OR
|
||||||
|
NEW.level != parent_unit.level + 1 OR
|
||||||
|
NEW.level != parent_rel.level + 1
|
||||||
|
) THEN
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
end
|
246
db/structure.sql
246
db/structure.sql
|
@ -199,6 +199,193 @@ END;
|
||||||
$_$;
|
$_$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: validate_org_unit_hierarchy(); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.validate_org_unit_hierarchy() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
kind record;
|
||||||
|
parent_kind record;
|
||||||
|
parent_unit record;
|
||||||
|
BEGIN
|
||||||
|
IF NEW.kind_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'does not have type';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_unit_kinds
|
||||||
|
INTO kind
|
||||||
|
WHERE id = NEW.kind_id;
|
||||||
|
|
||||||
|
IF kind IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find type';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_unit_kinds
|
||||||
|
INTO parent_kind
|
||||||
|
WHERE id = kind.parent_kind_id;
|
||||||
|
|
||||||
|
IF (kind.parent_kind_id IS NULL) != (parent_kind IS NULL) THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent type';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF parent_kind IS NULL THEN
|
||||||
|
IF NEW.parent_unit_id IS NOT NULL THEN
|
||||||
|
RAISE EXCEPTION 'parent is invalid (expected NULL)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.level != 0 THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.parent_unit_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'parent is invalid (expected NOT NULL)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_units
|
||||||
|
INTO parent_unit
|
||||||
|
WHERE id = NEW.parent_unit_id;
|
||||||
|
|
||||||
|
IF parent_unit IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF parent_unit.kind_id != parent_kind.id THEN
|
||||||
|
RAISE EXCEPTION 'parent is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF (
|
||||||
|
NEW.level != kind.level OR
|
||||||
|
NEW.level != parent_kind.level + 1 OR
|
||||||
|
NEW.level != parent_unit.level + 1
|
||||||
|
) THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: validate_org_unit_kind_hierarchy(); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.validate_org_unit_kind_hierarchy() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
parent_kind record;
|
||||||
|
BEGIN
|
||||||
|
IF NEW.parent_kind_id IS NULL THEN
|
||||||
|
IF NEW.level != 0 THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_unit_kinds
|
||||||
|
INTO parent_kind
|
||||||
|
WHERE id = NEW.parent_kind_id;
|
||||||
|
|
||||||
|
IF parent_kind IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.level != parent_kind.level + 1 THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: validate_relationship_hierarchy(); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.validate_relationship_hierarchy() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
org_unit record;
|
||||||
|
parent_unit record;
|
||||||
|
parent_rel record;
|
||||||
|
BEGIN
|
||||||
|
IF NEW.org_unit_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'does not have org unit';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_units
|
||||||
|
INTO org_unit
|
||||||
|
WHERE id = NEW.org_unit_id;
|
||||||
|
|
||||||
|
IF org_unit IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find org unit';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM org_units
|
||||||
|
INTO parent_unit
|
||||||
|
WHERE id = org_unit.parent_unit_id;
|
||||||
|
|
||||||
|
IF (org_unit.parent_unit_id IS NULL) != (parent_unit IS NULL) THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent org unit';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF parent_unit IS NULL THEN
|
||||||
|
IF NEW.parent_rel_id IS NOT NULL THEN
|
||||||
|
RAISE EXCEPTION 'parent rel is invalid (expected NULL)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.level != 0 THEN
|
||||||
|
RAISE EXCEPTION 'level is invalid (expected 0)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW.parent_rel_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'parent rel is invalid (expected NOT NULL)';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM relationships
|
||||||
|
INTO parent_rel
|
||||||
|
WHERE id = NEW.parent_rel_id;
|
||||||
|
|
||||||
|
IF parent_rel IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'can not find parent rel';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF parent_rel.org_unit_id != parent_unit.id THEN
|
||||||
|
RAISE EXCEPTION 'parent rel is invalid';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF (
|
||||||
|
NEW.level != org_unit.level OR
|
||||||
|
NEW.level != parent_unit.level + 1 OR
|
||||||
|
NEW.level != parent_rel.level + 1
|
||||||
|
) THEN
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
SET default_tablespace = '';
|
SET default_tablespace = '';
|
||||||
|
|
||||||
SET default_with_oids = false;
|
SET default_with_oids = false;
|
||||||
|
@ -473,8 +660,11 @@ CREATE TABLE public.org_unit_kinds (
|
||||||
short_name character varying NOT NULL,
|
short_name character varying NOT NULL,
|
||||||
name character varying NOT NULL,
|
name character varying NOT NULL,
|
||||||
parent_kind_id bigint,
|
parent_kind_id bigint,
|
||||||
|
level integer NOT NULL,
|
||||||
CONSTRAINT codename CHECK (public.is_codename((codename)::text)),
|
CONSTRAINT codename CHECK (public.is_codename((codename)::text)),
|
||||||
|
CONSTRAINT level CHECK ((level >= 0)),
|
||||||
CONSTRAINT name CHECK (public.is_good_small_text((name)::text)),
|
CONSTRAINT name CHECK (public.is_good_small_text((name)::text)),
|
||||||
|
CONSTRAINT parent_kind CHECK ((parent_kind_id <> id)),
|
||||||
CONSTRAINT short_name CHECK (public.is_good_small_text((short_name)::text))
|
CONSTRAINT short_name CHECK (public.is_good_small_text((short_name)::text))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -509,8 +699,11 @@ CREATE TABLE public.org_units (
|
||||||
short_name character varying NOT NULL,
|
short_name character varying NOT NULL,
|
||||||
name character varying NOT NULL,
|
name character varying NOT NULL,
|
||||||
kind_id bigint NOT NULL,
|
kind_id bigint NOT NULL,
|
||||||
parent_id bigint,
|
parent_unit_id bigint,
|
||||||
|
level integer NOT NULL,
|
||||||
|
CONSTRAINT level CHECK ((level >= 0)),
|
||||||
CONSTRAINT name CHECK (public.is_good_small_text((name)::text)),
|
CONSTRAINT name CHECK (public.is_good_small_text((name)::text)),
|
||||||
|
CONSTRAINT parent_unit CHECK ((parent_unit_id <> id)),
|
||||||
CONSTRAINT short_name CHECK (public.is_good_small_text((short_name)::text))
|
CONSTRAINT short_name CHECK (public.is_good_small_text((short_name)::text))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -759,7 +952,11 @@ CREATE TABLE public.relationships (
|
||||||
person_id bigint NOT NULL,
|
person_id bigint NOT NULL,
|
||||||
from_date date NOT NULL,
|
from_date date NOT NULL,
|
||||||
status_id bigint NOT NULL,
|
status_id bigint NOT NULL,
|
||||||
org_unit_id bigint NOT NULL
|
org_unit_id bigint NOT NULL,
|
||||||
|
parent_rel_id bigint,
|
||||||
|
level integer NOT NULL,
|
||||||
|
CONSTRAINT level CHECK ((level >= 0)),
|
||||||
|
CONSTRAINT parent_rel CHECK ((parent_rel_id <> id))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -1343,10 +1540,10 @@ CREATE UNIQUE INDEX index_org_units_on_name ON public.org_units USING btree (nam
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_org_units_on_parent_id; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_org_units_on_parent_unit_id; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE INDEX index_org_units_on_parent_id ON public.org_units USING btree (parent_id);
|
CREATE INDEX index_org_units_on_parent_unit_id ON public.org_units USING btree (parent_unit_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
@ -1454,6 +1651,13 @@ CREATE INDEX index_relationships_on_from_date ON public.relationships USING btre
|
||||||
CREATE INDEX index_relationships_on_org_unit_id ON public.relationships USING btree (org_unit_id);
|
CREATE INDEX index_relationships_on_org_unit_id ON public.relationships USING btree (org_unit_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_relationships_on_parent_rel_id; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_relationships_on_parent_rel_id ON public.relationships USING btree (parent_rel_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_relationships_on_person_id_and_from_date; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_relationships_on_person_id_and_from_date; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1545,6 +1749,27 @@ CREATE TRIGGER ensure_contact_list_id_remains_unchanged BEFORE UPDATE OF contact
|
||||||
CREATE TRIGGER ensure_superuser_has_related_user BEFORE INSERT OR UPDATE ON public.accounts FOR EACH ROW EXECUTE PROCEDURE public.ensure_superuser_has_related_user();
|
CREATE TRIGGER ensure_superuser_has_related_user BEFORE INSERT OR UPDATE ON public.accounts FOR EACH ROW EXECUTE PROCEDURE public.ensure_superuser_has_related_user();
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: org_unit_kinds validate_hierarchy; Type: TRIGGER; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TRIGGER validate_hierarchy BEFORE INSERT OR UPDATE ON public.org_unit_kinds FOR EACH ROW EXECUTE PROCEDURE public.validate_org_unit_kind_hierarchy();
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: org_units validate_hierarchy; Type: TRIGGER; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TRIGGER validate_hierarchy BEFORE INSERT OR UPDATE ON public.org_units FOR EACH ROW EXECUTE PROCEDURE public.validate_org_unit_hierarchy();
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: relationships validate_hierarchy; Type: TRIGGER; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TRIGGER validate_hierarchy BEFORE INSERT OR UPDATE ON public.relationships FOR EACH ROW EXECUTE PROCEDURE public.validate_relationship_hierarchy();
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: relationships fk_rails_0ea63a126c; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: relationships fk_rails_0ea63a126c; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1566,7 +1791,7 @@ ALTER TABLE ONLY public.people
|
||||||
--
|
--
|
||||||
|
|
||||||
ALTER TABLE ONLY public.org_units
|
ALTER TABLE ONLY public.org_units
|
||||||
ADD CONSTRAINT fk_rails_54c0512b74 FOREIGN KEY (parent_id) REFERENCES public.org_units(id);
|
ADD CONSTRAINT fk_rails_54c0512b74 FOREIGN KEY (parent_unit_id) REFERENCES public.org_units(id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
@ -1657,6 +1882,14 @@ ALTER TABLE ONLY public.relation_transitions
|
||||||
ADD CONSTRAINT fk_rails_b61956945e FOREIGN KEY (from_status_id) REFERENCES public.relation_statuses(id);
|
ADD CONSTRAINT fk_rails_b61956945e FOREIGN KEY (from_status_id) REFERENCES public.relation_statuses(id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: relationships fk_rails_b943fd3c34; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.relationships
|
||||||
|
ADD CONSTRAINT fk_rails_b943fd3c34 FOREIGN KEY (parent_rel_id) REFERENCES public.relationships(id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: org_units fk_rails_ccc56f184e; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: org_units fk_rails_ccc56f184e; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1725,6 +1958,7 @@ INSERT INTO "schema_migrations" (version) VALUES
|
||||||
('20190930210852'),
|
('20190930210852'),
|
||||||
('20190930215223'),
|
('20190930215223'),
|
||||||
('20191001022049'),
|
('20191001022049'),
|
||||||
('20191001211809');
|
('20191001211809'),
|
||||||
|
('20191002002101');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ FactoryBot.define do
|
||||||
trait :with_parent do
|
trait :with_parent do
|
||||||
association :kind, factory: :some_children_org_unit_kind
|
association :kind, factory: :some_children_org_unit_kind
|
||||||
|
|
||||||
parent { create :some_root_org_unit, kind: kind.parent_kind }
|
parent_unit { create :some_root_org_unit, kind: kind.parent_kind }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,29 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :supporter_relationship, class: Relationship do
|
factory :some_root_relationship, class: Relationship do
|
||||||
association :org_unit, factory: :some_children_org_unit
|
association :org_unit, factory: :some_root_org_unit
|
||||||
association :status, factory: :some_relation_status
|
association :status, factory: :some_relation_status
|
||||||
association :person, factory: :initial_person
|
association :person, factory: :initial_person
|
||||||
|
|
||||||
sequence :from_date do |n|
|
sequence :from_date do |n|
|
||||||
Date.new rand((10 * n)...(11 * n)), rand(1..12), rand(1..28)
|
Date.new rand((10 * n)...(11 * n)), rand(1..12), rand(1..28)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :with_parent do
|
||||||
|
association :org_unit, factory: :some_children_org_unit
|
||||||
|
|
||||||
|
parent_rel do
|
||||||
|
create :some_root_relationship, org_unit: org_unit&.parent_unit
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
factory :some_children_relationship,
|
||||||
|
parent: :some_root_relationship,
|
||||||
|
traits: %i[with_parent]
|
||||||
|
|
||||||
|
factory :supporter_relationship, parent: :some_children_relationship
|
||||||
|
|
||||||
factory :excluded_relationship, parent: :supporter_relationship
|
factory :excluded_relationship, parent: :supporter_relationship
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,20 @@ module Partynest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_trigger(table, name, events, call)
|
||||||
|
reversible do |dir|
|
||||||
|
dir.up { trigger_creation(table, name, events, call).call }
|
||||||
|
dir.down { trigger_deletion(table, name).call }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_trigger(table, name, events, call)
|
||||||
|
reversible do |dir|
|
||||||
|
dir.up { trigger_deletion(table, name).call }
|
||||||
|
dir.down { trigger_creation(table, name, events, call).call }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def func_creation(name, sql)
|
def func_creation(name, sql)
|
||||||
|
@ -69,6 +83,18 @@ module Partynest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def trigger_creation(table, name, events, call)
|
||||||
|
lambda do
|
||||||
|
execute <<~SQL
|
||||||
|
CREATE TRIGGER #{name}
|
||||||
|
#{events}
|
||||||
|
ON #{table}
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE #{call};
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def func_deletion(name)
|
def func_deletion(name)
|
||||||
lambda do
|
lambda do
|
||||||
execute "DROP FUNCTION #{name}"
|
execute "DROP FUNCTION #{name}"
|
||||||
|
@ -86,5 +112,11 @@ module Partynest
|
||||||
execute "ALTER TABLE #{table} DROP CONSTRAINT #{name}"
|
execute "ALTER TABLE #{table} DROP CONSTRAINT #{name}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def trigger_deletion(table, name)
|
||||||
|
lambda do
|
||||||
|
execute "DROP TRIGGER #{name} ON #{table}"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,19 +17,21 @@ RSpec.describe OrgUnit do
|
||||||
it { is_expected.to validate_presence_of(:kind).with_message(:required) }
|
it { is_expected.to validate_presence_of(:kind).with_message(:required) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#parent' do
|
describe '#parent_unit' do
|
||||||
it do
|
it do
|
||||||
is_expected.to \
|
is_expected.to \
|
||||||
belong_to(:parent)
|
belong_to(:parent_unit)
|
||||||
.class_name('OrgUnit')
|
.class_name('OrgUnit')
|
||||||
.inverse_of(:children)
|
.inverse_of(:children_units)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when organizational unit type does not require parent' do
|
context 'when organizational unit type does not require parent' do
|
||||||
subject { create :some_root_org_unit }
|
subject { create :some_root_org_unit }
|
||||||
|
|
||||||
it do
|
it do
|
||||||
is_expected.not_to validate_presence_of(:parent).with_message(:required)
|
is_expected.not_to \
|
||||||
|
validate_presence_of(:parent_unit)
|
||||||
|
.with_message(:required)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -37,18 +39,20 @@ RSpec.describe OrgUnit do
|
||||||
subject { create :some_children_org_unit }
|
subject { create :some_children_org_unit }
|
||||||
|
|
||||||
it do
|
it do
|
||||||
is_expected.to validate_presence_of(:parent).with_message(:required)
|
is_expected.to \
|
||||||
|
validate_presence_of(:parent_unit)
|
||||||
|
.with_message(:required)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#children' do
|
describe '#children_units' do
|
||||||
it do
|
it do
|
||||||
is_expected.to \
|
is_expected.to \
|
||||||
have_many(:children)
|
have_many(:children_units)
|
||||||
.class_name('OrgUnit')
|
.class_name('OrgUnit')
|
||||||
.inverse_of(:parent)
|
.inverse_of(:parent_unit)
|
||||||
.with_foreign_key(:parent_id)
|
.with_foreign_key(:parent_unit_id)
|
||||||
.dependent(:restrict_with_exception)
|
.dependent(:restrict_with_exception)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,27 @@ RSpec.describe Relationship do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#parent_rel' do
|
||||||
|
it do
|
||||||
|
is_expected.to \
|
||||||
|
belong_to(:parent_rel)
|
||||||
|
.class_name('Relationship')
|
||||||
|
.inverse_of(:children_rels)
|
||||||
|
.optional
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#children_rels' do
|
||||||
|
it do
|
||||||
|
is_expected.to \
|
||||||
|
have_many(:children_rels)
|
||||||
|
.class_name('Relationship')
|
||||||
|
.inverse_of(:parent_rel)
|
||||||
|
.with_foreign_key(:parent_rel_id)
|
||||||
|
.dependent(:restrict_with_exception)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#status' do
|
describe '#status' do
|
||||||
it do
|
it do
|
||||||
is_expected.to belong_to(:status).class_name('RelationStatus').required
|
is_expected.to belong_to(:status).class_name('RelationStatus').required
|
||||||
|
|
Reference in a new issue