Class: RailsAiContext::Tools::MigrationAdvisor

Inherits:
BaseTool
  • Object
show all
Defined in:
lib/rails_ai_context/tools/migration_advisor.rb

Constant Summary collapse

VALID_ACTIONS =
%w[add_column remove_column rename_column add_index add_association change_type create_table].freeze

Constants inherited from BaseTool

BaseTool::SESSION_CONTEXT, BaseTool::SHARED_CACHE

Class Method Summary collapse

Methods inherited from BaseTool

abstract!, abstract?, cache_key, cached_context, config, extract_method_source_from_file, extract_method_source_from_string, find_closest_match, fuzzy_find_key, inherited, not_found_response, paginate, rails_app, registered_tools, reset_all_caches!, reset_cache!, session_queries, session_record, session_reset!, set_call_params, text_response

Class Method Details

.call(action: nil, table: nil, column: nil, type: nil, new_name: nil, options: nil, server_context: nil) ⇒ Object



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
103
104
105
106
107
108
# File 'lib/rails_ai_context/tools/migration_advisor.rb', line 48

def self.call(action: nil, table: nil, column: nil, type: nil, new_name: nil, options: nil, server_context: nil)
  action = action.to_s.strip
  table = table.to_s.strip
  column = column.to_s.strip.presence if column

  # Normalize model names to table names: "Cook" → "cooks", "BrandProfile" → "brand_profiles"
  table = table.underscore.pluralize if table.match?(/\A[A-Z]/)

  return text_response("**Error:** `action` is required. Valid actions: #{VALID_ACTIONS.join(', ')}") if action.empty?
  return text_response("**Error:** `table` is required (e.g., 'users', 'posts').") if table.empty?

  # Validate identifier characters to produce valid migration code
  unless table.match?(/\A[a-z_][a-z0-9_]*\z/)
    return text_response("**Error:** Invalid table name `#{table}`. Use lowercase letters, digits, and underscores only.")
  end
  # create_table uses column param as a comma-separated column:type definition string
  if action != "create_table" && column && !column.empty? && !column.match?(/\A[a-z_][a-z0-9_]*\z/)
    return text_response("**Error:** Invalid column name `#{column}`. Use lowercase letters, digits, and underscores only.")
  end

  unless VALID_ACTIONS.include?(action)
    suggestion = VALID_ACTIONS.find { |a| a.start_with?(action) || a.include?(action) }
    hint = suggestion ? " Did you mean `#{suggestion}`?" : ""
    return text_response("**Error:** Unknown action `#{action}`.#{hint} Valid actions: #{VALID_ACTIONS.join(', ')}")
  end

  schema = cached_context[:schema]
  models = cached_context[:models]

  lines = [ "# Migration Advisor", "" ]

  # Check if table exists
  table_exists = schema.is_a?(Hash) && !schema[:error] && schema[:tables]&.key?(table)

  case action
  when "add_column"
    lines.concat(generate_add_column(table, column, type, options, table_exists))
  when "remove_column"
    lines.concat(generate_remove_column(table, column, type, schema, models))
  when "rename_column"
    rename_to = new_name&.to_s&.strip
    rename_to = type if rename_to.nil? || rename_to.empty?
    lines.concat(generate_rename_column(table, column, rename_to))
  when "add_index"
    lines.concat(generate_add_index(table, column, options))
  when "add_association"
    lines.concat(generate_add_association(table, column, type, options))
  when "change_type"
    lines.concat(generate_change_type(table, column, type, options))
  when "create_table"
    lines.concat(generate_create_table(table, column, options))
  end

  # Show affected models
  lines.concat(show_affected_models(table, models))

  # Strong Migrations warnings (only when the gem is present in the project)
  lines.concat(strong_migrations_warnings(action, table, column, options)) if strong_migrations_gem_present?

  text_response(lines.join("\n"))
end