Module: DbSchema::Changes

Defined in:
lib/db_schema/changes.rb

Defined Under Namespace

Classes: AddValueToEnum, AllowNull, AlterColumnDefault, AlterColumnType, AlterTable, ColumnOperation, CreateCheckConstraint, CreateColumn, CreateEnum, CreateExtension, CreateForeignKey, CreateIndex, CreatePrimaryKey, CreateTable, DisallowNull, DropCheckConstraint, DropColumn, DropEnum, DropExtension, DropForeignKey, DropIndex, DropPrimaryKey, DropTable, RenameColumn

Class Method Summary collapse

Class Method Details

.between(desired_schema, actual_schema) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/db_schema/changes.rb', line 7

def between(desired_schema, actual_schema)
  desired_tables = extract_tables(desired_schema)
  actual_tables  = extract_tables(actual_schema)

  table_names = [desired_tables, actual_tables].flatten.map(&:name).uniq

  table_changes = table_names.each.with_object([]) do |table_name, changes|
    desired = desired_tables.find { |table| table.name == table_name }
    actual  = actual_tables.find  { |table| table.name == table_name }

    if desired && !actual
      changes << CreateTable.new(
        table_name,
        fields:  desired.fields,
        indices: desired.indices,
        checks:  desired.checks
      )

      fkey_operations = desired.foreign_keys.map do |fkey|
        CreateForeignKey.new(table_name, fkey)
      end
      changes.concat(fkey_operations)
    elsif actual && !desired
      changes << DropTable.new(table_name)

      actual.foreign_keys.each do |fkey|
        changes << DropForeignKey.new(table_name, fkey.name)
      end
    elsif actual != desired
      field_operations = field_changes(desired.fields, actual.fields)
      index_operations = index_changes(desired.indices, actual.indices)
      check_operations = check_changes(desired.checks, actual.checks)
      fkey_operations  = foreign_key_changes(table_name, desired.foreign_keys, actual.foreign_keys)

      if field_operations.any? || index_operations.any? || check_operations.any?
        changes << AlterTable.new(
          table_name,
          fields:  field_operations,
          indices: index_operations,
          checks:  check_operations
        )
      end

      changes.concat(fkey_operations)
    end
  end

  desired_enums = extract_enums(desired_schema)
  actual_enums  = extract_enums(actual_schema)

  enum_names = [desired_enums, actual_enums].flatten.map(&:name).uniq

  enum_changes = enum_names.each_with_object([]) do |enum_name, changes|
    desired = desired_enums.find { |enum| enum.name == enum_name }
    actual  = actual_enums.find  { |enum| enum.name == enum_name }

    if desired && !actual
      changes << CreateEnum.new(enum_name, desired.values)
    elsif actual && !desired
      changes << DropEnum.new(enum_name)
    elsif actual != desired
      new_values     = desired.values - actual.values
      dropped_values = actual.values - desired.values

      if dropped_values.any?
        raise UnsupportedOperation, "Enum #{enum_name.inspect} doesn't describe values #{dropped_values.inspect} that are present in the database; dropping values from enums is not supported."
      end

      if desired.values - new_values != actual.values
        raise UnsupportedOperation, "Enum #{enum_name.inspect} describes values #{(desired.values - new_values).inspect} that are present in the database in a different order (#{actual.values.inspect}); reordering values in enums is not supported."
      end

      new_values.reverse.each do |value|
        value_index = desired.values.index(value)

        if value_index == desired.values.count - 1
          changes << AddValueToEnum.new(enum_name, value)
        else
          next_value = desired.values[value_index + 1]
          changes << AddValueToEnum.new(enum_name, value, before: next_value)
        end
      end
    end
  end

  desired_extensions = extract_extensions(desired_schema)
  actual_extensions  = extract_extensions(actual_schema)

  extension_changes = (desired_extensions - actual_extensions).map do |extension|
    CreateExtension.new(extension.name)
  end + (actual_extensions - desired_extensions).map do |extension|
    DropExtension.new(extension.name)
  end

  table_changes + enum_changes + extension_changes
end