Module: Dagnabit::Migration

Defined in:
lib/dagnabit/migration.rb

Overview

Contains shorthand for setting up database triggers and constraints for maintaining dagnabit’s invariants. See the README for more information.

This module is intended to be extended by subclasses of ActiveRecord::Migration.

Instance Method Summary collapse

Instance Method Details

#create_cycle_check_function(edge_table) ⇒ void

This method returns an undefined value.

Builds a PL/pgSQL function for performing cycle checks.

If the function already exists, then calling this method will overwrite it.

A ‘CREATE TRUSTED LANGUAGE plpgsql` declaration must have been made in the database prior to invocation of #create_cycle_check_function.

Parameters:

  • edge_table (Symbol, String)

    the table to check



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/dagnabit/migration.rb', line 50

def create_cycle_check_function(edge_table)
  execute %Q{
    CREATE OR REPLACE FUNCTION #{function_name}_#{edge_table}() RETURNS trigger AS $#{function_name}$
      DECLARE
        cyclic bool;
      BEGIN
        WITH RECURSIVE cycles(id, path, cycle) AS (
          SELECT e.child_id, ARRAY[]::integer[], false
            FROM #{edge_table} e WHERE e.parent_id = NEW.parent_id
          UNION ALL
          SELECT e.child_id, c.path || c.id, c.id = ANY(c.path)
            FROM cycles c INNER JOIN #{edge_table} e ON e.parent_id = c.id AND NOT cycle
        )
        SELECT true FROM cycles WHERE cycle = true INTO cyclic;

        IF cyclic = true THEN
          RAISE EXCEPTION 'Edge (%, %) introduces a cycle', NEW.child_id, NEW.parent_id;
        END IF;

        RETURN NULL;
      END;
    $#{function_name}$ LANGUAGE plpgsql;
  }.strip
end

#create_cycle_check_trigger(edge_table) ⇒ void

This method returns an undefined value.

Instantiates a cycle check trigger on ‘edge_table`. The trigger is executed on a per row basis for every insert or update.

Parameters:

  • edge_table (Symbol, String)

    the table for the trigger



17
18
19
20
21
22
23
24
# File 'lib/dagnabit/migration.rb', line 17

def create_cycle_check_trigger(edge_table)
  create_cycle_check_function(edge_table)

  execute %Q{
    CREATE TRIGGER #{trigger_name} AFTER INSERT OR UPDATE ON #{edge_table}
      FOR EACH ROW EXECUTE PROCEDURE #{function_name}_#{edge_table}();
  }.strip
end

#drop_cycle_check_function(edge_table) ⇒ void

This method returns an undefined value.

Drops the function created by #create_cycle_check_function.

It is safe to call this method if the function was not previously created.



82
83
84
85
86
# File 'lib/dagnabit/migration.rb', line 82

def drop_cycle_check_function(edge_table)
  execute %Q{
    DROP FUNCTION IF EXISTS #{function_name}_#{edge_table}();
  }.strip
end

#drop_cycle_check_trigger(edge_table) ⇒ void

This method returns an undefined value.

Drops a trigger created by #create_cycle_check_trigger.

Parameters:

  • edge_table (Symbol, String)

    the table owning the trigger



31
32
33
34
35
36
37
# File 'lib/dagnabit/migration.rb', line 31

def drop_cycle_check_trigger(edge_table)
  execute %Q{
    DROP TRIGGER #{trigger_name} ON #{edge_table};
  }.strip

  drop_cycle_check_function(edge_table)
end