Class: Rodauth::SequelGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/rodauth/sequel_generator.rb

Overview

SequelGenerator generates Sequel migration code for missing Rodauth tables.

It uses table structure information from TableInspector to generate appropriate CREATE TABLE statements with proper columns, foreign keys, and indexes.

Examples:

Generate migration for missing tables

missing = rodauth.missing_tables
generator = Rodauth::SequelGenerator.new(missing, rodauth)
puts generator.generate_migration

Generate only CREATE statements

puts generator.generate_create_statements

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(missing_tables, rodauth_instance, missing_columns = []) ⇒ SequelGenerator

Initialize the Sequel generator

Parameters:

  • missing_tables (Array<Hash>)

    Array of missing table info from table_guard

  • rodauth_instance (Rodauth::Auth)

    Rodauth instance for context

  • missing_columns (Array<Hash>) (defaults to: [])

    Array of missing column info from table_guard



30
31
32
33
34
35
# File 'lib/rodauth/sequel_generator.rb', line 30

def initialize(missing_tables, rodauth_instance, missing_columns = [])
  @missing_tables = missing_tables
  @missing_columns = missing_columns
  @rodauth_instance = rodauth_instance
  @db = rodauth_instance.respond_to?(:db) ? rodauth_instance.db : nil
end

Instance Attribute Details

#dbObject (readonly)

Returns the value of attribute db.



23
24
25
# File 'lib/rodauth/sequel_generator.rb', line 23

def db
  @db
end

#missing_columnsObject (readonly)

Returns the value of attribute missing_columns.



23
24
25
# File 'lib/rodauth/sequel_generator.rb', line 23

def missing_columns
  @missing_columns
end

#missing_tablesObject (readonly)

Returns the value of attribute missing_tables.



23
24
25
# File 'lib/rodauth/sequel_generator.rb', line 23

def missing_tables
  @missing_tables
end

#rodauth_instanceObject (readonly)

Returns the value of attribute rodauth_instance.



23
24
25
# File 'lib/rodauth/sequel_generator.rb', line 23

def rodauth_instance
  @rodauth_instance
end

Instance Method Details

#execute_alter_tables(db) ⇒ Object

Execute ALTER TABLE operations directly against the database

Parameters:

  • db (Sequel::Database)

    Database connection



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/rodauth/sequel_generator.rb', line 230

def execute_alter_tables(db)
  return if missing_columns.empty?

  # Group columns by table
  columns_by_table = missing_columns.group_by { |col| col[:table] }

  columns_by_table.each do |table_name, columns|
    # Capture self reference before entering alter_table block
    generator = self

    db.alter_table(table_name.to_sym) do
      columns.each do |col|
        # Map Ruby type symbol or class to Sequel column type
        column_type = generator.send(:map_column_type_for_execution, col[:type])

        # Build column options
        column_opts = {}
        column_opts[:null] = col[:null] unless col[:null].nil?
        column_opts[:default] = col[:default] if col[:default]
        column_opts[:unique] = true if col[:unique]
        column_opts[:size] = col[:size] if col[:size]

        add_column col[:column].to_sym, column_type, column_opts

        # Add index if requested
        if col[:index]
          index_opts = col[:unique] ? { unique: true } : {}
          add_index col[:column].to_sym, index_opts
        end
      end
    end
  end
end

#execute_creates(db) ⇒ Object

Execute CREATE TABLE operations directly against the database

Parameters:

  • db (Sequel::Database)

    Database connection



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/rodauth/sequel_generator.rb', line 207

def execute_creates(db)
  # Create tables if there are any missing
  if missing_tables.any?
    # Extract unique features from missing tables
    features_needed = extract_features_from_missing_tables

    # Use Migration class to execute ERB templates directly
    migration = create_migration_generator(features_needed)

    begin
      migration.execute_create_tables(db)
    rescue StandardError => e
      raise "Failed to execute table creation: #{e.class} - #{e.message}\n  #{e.backtrace.first(5).join("\n  ")}"
    end
  end

  # Execute ALTER TABLE statements for missing columns
  execute_alter_tables(db)
end

#execute_drops(db, features: nil) ⇒ Object

Execute DROP TABLE operations directly against the database

Uses TemplateInspector to extract ALL tables from ERB templates, including "hidden" tables (account_statuses, account_password_hashes) that have no corresponding *_table method.

By default the feature set is derived from missing_tables (the :sync path). Callers that need to drop the full schema regardless of what is currently missing — :recreate and :drop, where nothing may be "missing" — pass an explicit features list so the template enumeration still covers every enabled feature (and therefore the hidden tables).

Parameters:

  • db (Sequel::Database)

    Database connection

  • features (Array<Symbol>, nil) (defaults to: nil)

    Explicit feature list to enumerate; when nil, features are inferred from missing_tables



279
280
281
282
283
284
285
286
287
288
289
# File 'lib/rodauth/sequel_generator.rb', line 279

def execute_drops(db, features: nil)
  # Extract all tables from ERB templates
  all_tables = extract_all_tables_from_templates(features: features)

  # Drop in reverse order to handle foreign key dependencies
  ordered_tables = order_tables_for_drop(all_tables).reverse

  ordered_tables.each do |table_name|
    db.drop_table?(table_name.to_sym)
  end
end

#generate_alter_table_statementsString

Generate ALTER TABLE statements for missing columns

Groups columns by table and generates alter_table blocks for each table with missing columns.

Returns:

  • (String)

    Sequel ALTER TABLE code



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/rodauth/sequel_generator.rb', line 141

def generate_alter_table_statements
  return '' if missing_columns.empty?

  # Group columns by table
  columns_by_table = missing_columns.group_by { |col| col[:table] }

  statements = columns_by_table.map do |table_name, columns|
    # Generate alter_table block for this table
    alter_block = ["alter_table(:#{table_name}) do"]

    columns.each do |col|
      # Map Ruby type symbol or class to Sequel column type
      column_type = map_column_type(col[:type])

      # Build options array
      options = []
      options << "null: #{col[:null]}" unless col[:null].nil?
      options << "default: #{format_default_value(col[:default])}" if col[:default]
      options << 'unique: true' if col[:unique]
      options << "size: #{col[:size]}" if col[:size]

      options_str = options.empty? ? '' : ", #{options.join(", ")}"

      alter_block << "  add_column :#{col[:column]}, #{column_type}#{options_str}"

      # Add index if requested
      if col[:index]
        index_opts = col[:unique] ? ', unique: true' : ''
        alter_block << "  add_index :#{col[:column]}#{index_opts}"
      end
    end

    alter_block << 'end'
    alter_block.join("\n")
  end

  statements.join("\n\n")
end

#generate_create_statements(idempotent: true) ⇒ String

Generate only the CREATE TABLE statements

Parameters:

  • idempotent (Boolean) (defaults to: true)

    Use create_table? for idempotency (default: true)

Returns:

  • (String)

    Sequel CREATE TABLE code



100
101
102
103
104
105
106
107
108
109
# File 'lib/rodauth/sequel_generator.rb', line 100

def generate_create_statements(idempotent: true)
  # Extract unique features from missing tables
  features_needed = extract_features_from_missing_tables

  # Use Migration class to generate from ERB templates
  migration = create_migration_generator(features_needed)

  # Generate the migration content
  migration.generate
end

#generate_drop_column_statementsString

Generate DROP COLUMN statements for rolling back column additions

Returns:

  • (String)

    Sequel DROP COLUMN code



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/rodauth/sequel_generator.rb', line 183

def generate_drop_column_statements
  return '' if missing_columns.empty?

  # Group columns by table
  columns_by_table = missing_columns.group_by { |col| col[:table] }

  statements = columns_by_table.map do |table_name, columns|
    # Generate alter_table block for this table
    alter_block = ["alter_table(:#{table_name}) do"]

    columns.each do |col|
      alter_block << "  drop_column :#{col[:column]}"
    end

    alter_block << 'end'
    alter_block.join("\n")
  end

  statements.join("\n\n")
end

#generate_drop_statementsString

Generate DROP TABLE statements

Uses TemplateInspector to extract ALL tables from ERB templates, including "hidden" tables that don't have corresponding *_table methods (like account_statuses and account_password_hashes from base.erb).

Returns:

  • (String)

    Sequel DROP TABLE code



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/rodauth/sequel_generator.rb', line 118

def generate_drop_statements
  return '' if missing_tables.empty?

  # Extract all tables from ERB templates (not just discovered methods)
  all_tables = extract_all_tables_from_templates

  # Drop in reverse order to handle foreign key dependencies
  # (child tables first, then parent tables)
  ordered_tables = order_tables_for_drop(all_tables).reverse

  statements = ordered_tables.map do |table_name|
    "drop_table?(:#{table_name})"
  end

  statements.join("\n")
end

#generate_migration(idempotent: true) ⇒ String

Generate a complete Sequel migration with up and down blocks

Parameters:

  • idempotent (Boolean) (defaults to: true)

    Use create_table? for idempotency (default: true)

Returns:

  • (String)

    Complete Sequel migration code



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
# File 'lib/rodauth/sequel_generator.rb', line 41

def generate_migration(idempotent: true)
  # Generate CREATE TABLE statements for missing tables
  migration_content = if missing_tables.any?
                        # Extract unique features from missing tables
                        features_needed = extract_features_from_missing_tables

                        # Use Migration class to generate from ERB templates
                        migration = create_migration_generator(features_needed)

                        # Generate the migration content
                        migration.generate
                      else
                        ''
                      end

  # Generate ALTER TABLE statements for missing columns
  alter_statements = generate_alter_table_statements

  # Combine CREATE and ALTER statements
  up_content = if migration_content.strip.empty?
                 alter_statements
               elsif alter_statements.strip.empty?
                 migration_content
               else
                 migration_content.strip + "\n\n" + alter_statements
               end

  # Generate down statements
  drop_tables = generate_drop_statements
  drop_columns = generate_drop_column_statements

  down_content = if drop_tables.strip.empty?
                   drop_columns
                 elsif drop_columns.strip.empty?
                   drop_tables
                 else
                   drop_tables.strip + "\n\n" + drop_columns
                 end

  # Wrap in Sequel.migration block with up/down
  <<~RUBY
    # frozen_string_literal: true

    Sequel.migration do
      up do
    #{indent(up_content, 4)}
      end

      down do
    #{indent(down_content, 4)}
      end
    end
  RUBY
end