Class: DatabaseModel::Generator::Base

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

Direct Known Subclasses

SqlServer::Model::Generator

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection) ⇒ Base

Returns a new instance of Base.

Raises:

  • (ArgumentError)


59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/database_model_generator.rb', line 59

def initialize(connection)
  raise ArgumentError, "Connection cannot be nil" if connection.nil?
  validate_connection(connection)

  @connection   = connection
  @constraints  = []
  @primary_keys = []
  @foreign_keys = []
  @dependencies = []
  @belongs_to   = []
  @polymorphic_associations = []
  @enum_columns = []
  @column_info  = []
  @table        = nil
  @model        = nil
end

Instance Attribute Details

#belongs_toObject (readonly)

Returns the value of attribute belongs_to.



55
56
57
# File 'lib/database_model_generator.rb', line 55

def belongs_to
  @belongs_to
end

#column_infoObject (readonly)

Returns the value of attribute column_info.



56
57
58
# File 'lib/database_model_generator.rb', line 56

def column_info
  @column_info
end

#connectionObject (readonly)

Returns the value of attribute connection.



55
56
57
# File 'lib/database_model_generator.rb', line 55

def connection
  @connection
end

#constraintsObject (readonly)

Returns the value of attribute constraints.



55
56
57
# File 'lib/database_model_generator.rb', line 55

def constraints
  @constraints
end

#dependenciesObject (readonly)

Returns the value of attribute dependencies.



56
57
58
# File 'lib/database_model_generator.rb', line 56

def dependencies
  @dependencies
end

#enum_columnsObject (readonly)

Returns the value of attribute enum_columns.



57
58
59
# File 'lib/database_model_generator.rb', line 57

def enum_columns
  @enum_columns
end

#foreign_keysObject (readonly)

Returns the value of attribute foreign_keys.



55
56
57
# File 'lib/database_model_generator.rb', line 55

def foreign_keys
  @foreign_keys
end

#modelObject (readonly)

Returns the value of attribute model.



56
57
58
# File 'lib/database_model_generator.rb', line 56

def model
  @model
end

#polymorphic_associationsObject (readonly)

Returns the value of attribute polymorphic_associations.



57
58
59
# File 'lib/database_model_generator.rb', line 57

def polymorphic_associations
  @polymorphic_associations
end

#primary_keysObject (readonly)

Returns the value of attribute primary_keys.



56
57
58
# File 'lib/database_model_generator.rb', line 56

def primary_keys
  @primary_keys
end

#tableObject (readonly)

Returns the value of attribute table.



56
57
58
# File 'lib/database_model_generator.rb', line 56

def table
  @table
end

#viewObject (readonly)

Returns the value of attribute view.



56
57
58
# File 'lib/database_model_generator.rb', line 56

def view
  @view
end

Instance Method Details

#build_composite_recommendations(recommendations) ⇒ Object



583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# File 'lib/database_model_generator.rb', line 583

def build_composite_recommendations(recommendations)
  foreign_key_columns = recommendations[:foreign_keys].map { |fk| fk[:column] }
  date_column_names = recommendations[:date_queries].map { |d| d[:column] }
  status_column_names = recommendations[:status_enum].map { |s| s[:column] }

  if foreign_key_columns.any? && date_column_names.any?
    fk_col = foreign_key_columns.first
    date_col = date_column_names.find { |col| col =~ /created/ } || date_column_names.first
    recommendations[:composite] << {
      columns: [fk_col, date_col],
      sql: "add_index :#{@table.downcase}, [:#{fk_col}, :#{date_col}]",
      reason: "Composite index for filtering by #{fk_col} and #{date_col}"
    }
  end

  if status_column_names.any? && date_column_names.any?
    status_col = status_column_names.first
    date_col = date_column_names.find { |col| col =~ /created/ } || date_column_names.first
    recommendations[:composite] << {
      columns: [status_col, date_col],
      sql: "add_index :#{@table.downcase}, [:#{status_col}, :#{date_col}]",
      reason: "Composite index for filtering by #{status_col} and #{date_col}"
    }
  end
end

#build_date_recommendations(recommendations) ⇒ Object



553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/database_model_generator.rb', line 553

def build_date_recommendations(recommendations)
  date_columns = @column_info.select do |col|
    is_date_type?(col) ||
    col.name.downcase =~ /(created_at|updated_at|modified_date|start_date|end_date|due_date)/
  end

  date_columns.each do |col|
    recommendations[:date_queries] << {
      column: col.name.downcase,
      sql: "add_index :#{@table.downcase}, :#{col.name.downcase}",
      reason: "Date queries for #{col.name}"
    }
  end
end

#build_foreign_key_recommendations(recommendations) ⇒ Object



524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/database_model_generator.rb', line 524

def build_foreign_key_recommendations(recommendations)
  @belongs_to.each do |table_ref|
    fk_column = "#{table_ref.downcase.gsub(/s$/, '')}_id"
    col = @column_info.find { |c| c.name.downcase == fk_column }
    if col
      recommendations[:foreign_keys] << {
        column: col.name.downcase,
        sql: "add_index :#{@table.downcase}, :#{col.name.downcase}",
        reason: "Foreign key index for #{col.name}"
      }
    end
  end
end

#build_full_text_index_sql(column) ⇒ Object

Raises:

  • (NotImplementedError)


643
644
645
# File 'lib/database_model_generator.rb', line 643

def build_full_text_index_sql(column)
  raise NotImplementedError, "Subclasses must implement build_full_text_index_sql"
end

#build_full_text_recommendations(recommendations) ⇒ Object



609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
# File 'lib/database_model_generator.rb', line 609

def build_full_text_recommendations(recommendations)
  text_search_columns = @column_info.select do |col|
    is_text_type?(col) &&
    col.name.downcase =~ /(name|title|description|content|text|search)/ &&
    (get_column_size(col).nil? || get_column_size(col) > 50)
  end

  text_search_columns.each do |col|
    recommendations[:full_text] << {
      column: col.name.downcase,
      sql: build_full_text_index_sql(col),
      reason: "Full-text search for #{col.name}",
      type: get_full_text_index_type
    }
  end
end

#build_status_recommendations(recommendations) ⇒ Object



568
569
570
571
572
573
574
575
576
577
578
579
580
581
# File 'lib/database_model_generator.rb', line 568

def build_status_recommendations(recommendations)
  status_columns = @column_info.select do |col|
    col.name.downcase =~ /(status|state|type|role|priority|level|category)$/ &&
    is_string_type?(col)
  end

  status_columns.each do |col|
    recommendations[:status_enum] << {
      column: col.name.downcase,
      sql: "add_index :#{@table.downcase}, :#{col.name.downcase}",
      reason: "Status/enum queries for #{col.name}"
    }
  end
end

#build_unique_constraint_recommendations(recommendations) ⇒ Object



538
539
540
541
542
543
544
545
546
547
548
549
550
551
# File 'lib/database_model_generator.rb', line 538

def build_unique_constraint_recommendations(recommendations)
  unique_columns = @column_info.select do |col|
    col.name.downcase =~ /(email|username|code|slug|uuid|token)/ ||
    (!col.nullable? && col.name.downcase =~ /(name|title)$/ && is_string_type?(col))
  end

  unique_columns.each do |col|
    recommendations[:unique_constraints] << {
      column: col.name.downcase,
      sql: "add_index :#{@table.downcase}, :#{col.name.downcase}, unique: true",
      reason: "Unique constraint for #{col.name}"
    }
  end
end

#column_namesObject



100
101
102
103
# File 'lib/database_model_generator.rb', line 100

def column_names
  return [] unless generated?
  @column_info.map(&:name)
end

#constraint_summaryObject



115
116
117
118
119
120
121
122
123
124
125
# File 'lib/database_model_generator.rb', line 115

def constraint_summary
  return {} unless generated?

  summary = Hash.new { |h, k| h[k] = [] }
  @constraints.each do |constraint|
    type = format_constraint_type(constraint)
    column_name = get_constraint_column_name(constraint)
    summary[column_name.downcase] << type
  end
  summary
end

#detect_polymorphic_associationsObject



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/database_model_generator.rb', line 399

def detect_polymorphic_associations
  polymorphic_assocs = []
  column_names = @column_info.map { |col| col.name.downcase }

  # Look for patterns like: commentable_type + commentable_id
  # or imageable_type + imageable_id, etc.
  type_columns = column_names.select { |name| name.end_with?('_type') }

  type_columns.each do |type_col|
    base_name = type_col.gsub(/_type$/, '')
    id_col = "#{base_name}_id"

    if column_names.include?(id_col)
      # Check if this isn't already a regular foreign key
      unless @foreign_keys.map(&:downcase).include?(id_col)
        polymorphic_assocs << {
          name: base_name,
          foreign_key: id_col,
          foreign_type: type_col,
          association_name: base_name
        }
      end
    end
  end

  polymorphic_assocs
end

#disconnectObject



110
111
112
113
# File 'lib/database_model_generator.rb', line 110

def disconnect
  # Default implementation - subclasses should override
  @connection = nil
end

#enum_column_namesObject



438
439
440
# File 'lib/database_model_generator.rb', line 438

def enum_column_names
  @enum_columns.map { |enum_col| enum_col[:name] }
end

#enum_definitionsObject



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/database_model_generator.rb', line 442

def enum_definitions
  # Generate Rails enum definitions
  definitions = []
  @enum_columns.each do |enum_col|
    if enum_col[:type] == :integer
      # Integer enum: { draft: 0, published: 1, archived: 2 }
      values = enum_col[:values].each_with_index.map { |val, idx| "#{val}: #{idx}" }.join(', ')
      definitions << "enum #{enum_col[:column_name]}: { #{values} }"
    else
      # String enum: { low: 'low', medium: 'medium', high: 'high' }
      values = enum_col[:values].map { |val| "#{val}: '#{val}'" }.join(', ')
      definitions << "enum #{enum_col[:column_name]}: { #{values} }"
    end
  end
  definitions
end

#enum_validation_suggestionsObject



459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/database_model_generator.rb', line 459

def enum_validation_suggestions
  # Suggest validations for enum columns
  suggestions = []
  @enum_columns.each do |enum_col|
    suggestions << {
      column: enum_col[:column_name],
      validation: "validates :#{enum_col[:column_name]}, inclusion: { in: #{enum_col[:column_name].pluralize}.keys }",
      description: "Validates #{enum_col[:column_name]} is a valid enum value"
    }
  end
  suggestions
end

#find_fk_table(fk) ⇒ Object



491
492
493
494
# File 'lib/database_model_generator.rb', line 491

def find_fk_table(fk)
  # Default implementation - may be overridden by subclasses
  fk.gsub(/_id$/i, '').pluralize rescue "#{fk.gsub(/_id$/i, '')}s"
end

#generate(table, view = false) ⇒ Object

Raises:

  • (ArgumentError)


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/database_model_generator.rb', line 76

def generate(table, view = false)
  raise ArgumentError, "Table name cannot be nil or empty" if table.nil? || table.strip.empty?

  @table = normalize_table_name(table)
  @model = generate_model_name(table)
  @view  = view

  reset_state
  get_column_info
  get_primary_keys
  get_foreign_keys unless view
  get_belongs_to
  get_constraints unless view
  get_polymorphic_associations unless view
  get_enum_columns unless view
  get_dependencies unless view

  self
end

#generated?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/database_model_generator.rb', line 96

def generated?
  !@table.nil? && !@column_info.empty?
end

#get_column_size(column) ⇒ Object

Raises:

  • (NotImplementedError)


639
640
641
# File 'lib/database_model_generator.rb', line 639

def get_column_size(column)
  raise NotImplementedError, "Subclasses must implement get_column_size"
end

#get_full_text_index_typeObject

Raises:

  • (NotImplementedError)


647
648
649
# File 'lib/database_model_generator.rb', line 647

def get_full_text_index_type
  raise NotImplementedError, "Subclasses must implement get_full_text_index_type"
end

#has_enum_columns?Boolean

Returns:

  • (Boolean)


434
435
436
# File 'lib/database_model_generator.rb', line 434

def has_enum_columns?
  !@enum_columns.empty?
end

#has_polymorphic_associations?Boolean

Returns:

  • (Boolean)


430
431
432
# File 'lib/database_model_generator.rb', line 430

def has_polymorphic_associations?
  !@polymorphic_associations.empty?
end

#index_recommendationsObject



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/database_model_generator.rb', line 127

def index_recommendations
  return {} unless generated?

  recommendations = {
    foreign_keys: [],
    unique_constraints: [],
    date_queries: [],
    status_enum: [],
    composite: [],
    full_text: []
  }

  build_foreign_key_recommendations(recommendations)
  build_unique_constraint_recommendations(recommendations)
  build_date_recommendations(recommendations)
  build_status_recommendations(recommendations)
  build_composite_recommendations(recommendations)
  build_full_text_recommendations(recommendations)

  recommendations
end

#is_date_type?(column) ⇒ Boolean

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


631
632
633
# File 'lib/database_model_generator.rb', line 631

def is_date_type?(column)
  raise NotImplementedError, "Subclasses must implement is_date_type?"
end

#is_string_type?(column) ⇒ Boolean

Abstract helper methods for database-specific type checking

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


627
628
629
# File 'lib/database_model_generator.rb', line 627

def is_string_type?(column)
  raise NotImplementedError, "Subclasses must implement is_string_type?"
end

#is_text_type?(column) ⇒ Boolean

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


635
636
637
# File 'lib/database_model_generator.rb', line 635

def is_text_type?(column)
  raise NotImplementedError, "Subclasses must implement is_text_type?"
end

#polymorphic_association_namesObject



472
473
474
# File 'lib/database_model_generator.rb', line 472

def polymorphic_association_names
  @polymorphic_associations.map { |assoc| assoc[:name] }
end

#polymorphic_has_many_suggestionsObject



476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/database_model_generator.rb', line 476

def polymorphic_has_many_suggestions
  # Suggest has_many associations for models that could be polymorphic parents
  suggestions = []
  @polymorphic_associations.each do |assoc|
    # For a 'commentable' polymorphic association, suggest:
    # has_many :comments, as: :commentable, dependent: :destroy
    child_model = pluralize_for_has_many(assoc[:name])
    suggestions << {
      association: "has_many :#{child_model}, as: :#{assoc[:name]}, dependent: :destroy",
      description: "For models that can have #{child_model} (polymorphic)"
    }
  end
  suggestions
end

#table_exists?Boolean

Returns:

  • (Boolean)


105
106
107
108
# File 'lib/database_model_generator.rb', line 105

def table_exists?
  return false unless @table
  check_table_exists(@table)
end