Class: DynamicMigrations::Postgres::Generator::Migration

Inherits:
Object
  • Object
show all
Defined in:
lib/dynamic_migrations/postgres/generator/migration.rb

Defined Under Namespace

Classes: DuplicateStructureTemplateError, MissingRequiredSchemaName, MissingRequiredTableName, NoFragmentsError, SectionNotFoundError, UnexpectedMigrationMethodNameError, UnexpectedSchemaError, UnexpectedTableError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(schema_name = nil, table_name = nil) ⇒ Migration

schema_name and table_name can be nil



34
35
36
37
38
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 34

def initialize schema_name = nil, table_name = nil
  @schema_name = schema_name
  @table_name = table_name
  @fragments = []
end

Instance Attribute Details

#fragmentsObject (readonly)

Returns the value of attribute fragments.



31
32
33
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 31

def fragments
  @fragments
end

#schema_nameObject (readonly)

Returns the value of attribute schema_name.



30
31
32
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 30

def schema_name
  @schema_name
end

#table_nameObject (readonly)

Returns the value of attribute table_name.



29
30
31
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 29

def table_name
  @table_name
end

Class Method Details

.add_structure_template(method_names, header) ⇒ Object

Defines a new section in the migration file, this is used to group migration fragments of the provided method names together under the provided header



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 43

def self.add_structure_template method_names, header
  @structure_templates ||= []

  if (@structure_templates.map { |s| s[:methods] }.flatten & method_names).any?
    raise DuplicateStructureTemplateError, "Duplicate structure template for methods `#{method_names}`"
  end

  @structure_templates << {
    methods: method_names,
    header_comment: <<~COMMENT.strip
      #
      # #{header.strip}
      #
    COMMENT
  }
end

.clear_structure_templatesObject

return the list of structure templates for use in this migration



66
67
68
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 66

def self.clear_structure_templates
  @structure_templates = []
end

.structure_templatesObject

return the list of structure templates for use in this migration



61
62
63
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 61

def self.structure_templates
  @structure_templates || []
end

Instance Method Details

#add_fragment(fragment) ⇒ Object

Add a migration fragment to this migration, if the migration is not configured (via a structure template) to handle the method_name of the fragment, then am error is raised. An error will also be raised if the migration belongs to a different schema than the provided fragment.



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 107

def add_fragment fragment
  unless supported_migration_method? fragment.migration_method
    raise UnexpectedMigrationMethodNameError, "Expected method to be a valid migrator method, got `#{fragment.migration_method}`"
  end

  # confirm the fragment is for this schema (even if both
  # these values are nil/there is no schema)
  unless @schema_name == fragment.schema_name
    raise UnexpectedSchemaError, "Fragment is for schema `#{fragment.schema_name || "nil"}` but migration is for schema `#{@schema_name || "nil"}`"
  end

  # confirm this fragment is for this table, this works for database and schame
  # migrations to, as all values should be nil
  unless @table_name == fragment.table_name
    raise UnexpectedTableError, "Fragment is for table `#{fragment.table_name || "nil"}` but migration is for table `#{@table_name || "nil"}`"
  end

  @fragments << fragment

  fragment
end

#contentObject

Combine the fragments, and build a string representation of the migration using the structure templates defined in this class. Will raise an error if no fragments have been provided.

Raises:



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 172

def content
  raise NoFragmentsError if fragments.empty?
  sections = []
  self.class.structure_templates.each do |section|
    # add the header comment if we have a migration which matches one of the
    # methods in this section
    if (section[:methods] & @fragments.map(&:migration_method)).any?
      sections << section[:header_comment].strip
    end

    # iterate through this sections methods in order and look
    # for any that match the migrations we have
    section[:methods].each do |migration_method|
      # if we have any migration fragments for this method then add them
      @fragments.filter { |f| f.migration_method == migration_method }.each do |fragment|
        sections << fragment.to_s
        sections << ""
      end
    end
  end
  sections.join("\n").strip
end

#enum_dependenciesObject

Return an array of enum dependencies for this migration, this array comes from combining any enum dependencies from each fragment. Will raise an error if no fragments are available.

Raises:



148
149
150
151
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 148

def enum_dependencies
  raise NoFragmentsError if fragments.empty?
  @fragments.map(&:enum_dependency).compact.uniq
end

#extract_fragments_with_table_dependency(schema_name, table_name) ⇒ Object

removes and returns any fragments which have a dependency on the table with the provided schema_name and table_name, this is used for extracting fragments which cause circular dependencies so they can be placed into their own migrations



161
162
163
164
165
166
167
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 161

def extract_fragments_with_table_dependency schema_name, table_name
  results = @fragments.filter { |f| f.is_dependent_on_table? schema_name, table_name }
  # remove any of these from the internal array of fragments
  @fragments.filter! { |f| !f.is_dependent_on_table?(schema_name, table_name) }
  # return the results
  results
end

#fragments_with_table_dependency_count(schema_name, table_name) ⇒ Object

returns the number of fragment within this migration which have the provided dependency



154
155
156
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 154

def fragments_with_table_dependency_count schema_name, table_name
  @fragments.count { |f| f.is_dependent_on_table? schema_name, table_name }
end

#function_dependenciesObject

Return an array of function dependencies for this migration, this array comes from combining any function dependencies from each fragment. Will raise an error if no fragments are available.

Raises:



140
141
142
143
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 140

def function_dependencies
  raise NoFragmentsError if fragments.empty?
  @fragments.map(&:function_dependency).compact.uniq
end

#nameObject

Using the migration fragments, generate a friendly/descriptive name for the migration. Will raise an error if no fragments have been provided.

Raises:



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 197

def name
  raise NoFragmentsError if fragments.empty?

  if fragments_for_method? :create_schema
    :"create_#{fragments.first&.object_name}_schema"

  elsif fragments_for_method? :drop_schema
    :"drop_#{fragments.first&.object_name}_schema"

  elsif fragments_for_method? :create_table
    :"create_#{first_fragment_using_migration_method(:create_table).table_name}"

  elsif fragments_for_method? :drop_table
    :"drop_#{first_fragment_using_migration_method(:drop_table).table_name}"

  elsif all_fragments_for_method? [:create_function]
    :"create_function_#{@fragments.find { |s| s.migration_method == :create_function }&.object_name}"

  elsif all_fragments_for_method? [:create_function, :update_function, :drop_function, :set_function_comment, :remove_function_comment]
    :schema_functions

  elsif all_fragments_for_method? [:create_enum, :add_enum_values, :drop_enum, :set_enum_comment, :remove_enum_comment]
    :enums

  elsif all_fragments_for_method? [:enable_extension]
    (@fragments.count > 1) ? :enable_extensions : :"enable_#{fragments.first&.object_name}_extension"

  elsif all_fragments_for_method? [:disable_extension]
    (@fragments.count > 1) ? :disable_extensions : :"disable_#{fragments.first&.object_name}_extension"

  elsif @fragments.first&.table_name
    :"changes_for_#{@fragments.first&.table_name}"

  else
    :changes
  end
end

#table_dependenciesObject

Return an array of table dependencies for this migration, this array comes from combining any table dependencies from each fragment. Will raise an error if no fragments are available.

Raises:



132
133
134
135
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 132

def table_dependencies
  raise NoFragmentsError if fragments.empty?
  @fragments.map(&:table_dependency).compact.uniq
end

#to_sObject



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
# File 'lib/dynamic_migrations/postgres/generator/migration.rb', line 70

def to_s
  # because calling these methods will rise an error if there are no
  # fragments, and this method is primarily used for debugging
  if @fragments.any?
    tds = table_dependencies
    eds = enum_dependencies
    fds = function_dependencies
  else
    tds = []
    eds = []
    fds = []
  end
  <<~PREVIEW.strip
    # Migration content preview
    # -------------------------
    # Schema:#{@schema_name ? " #{@schema_name}" : ""}
    # Table:#{@table_name ? " #{@table_name}" : ""}

    # Table Dependencies (count: #{tds.count}):
    #{(tds.any? ? "#   " : "") + tds.map { |d| "Schema: `#{d[:schema_name]}` Table: `#{d[:table_name]}`" }.join("\n#   ")}

    # Enum Dependencies (count: #{eds.count}):
    #{(eds.any? ? "#   " : "") + eds.map { |d| "Schema: `#{d[:schema_name]}` Enum: `#{d[:enum_name]}`" }.join("\n#   ")}

    # Function Dependencies (count: #{fds.count}):
    #{(fds.any? ? "#   " : "") + fds.map { |d| "Schema: `#{d[:schema_name]}` Function: `#{d[:function_name]}`" }.join("\n#   ")}

    # Fragments (count: #{@fragments.count}):

    #{@fragments.map(&:to_s).join("\n\n")}
  PREVIEW
end