mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-27 02:05:26 +08:00
Ruby's compact module syntax (`module Migrations::Database::Schema::DSL`) breaks lexical constant lookup — `Module.nesting` only includes the innermost constant, so every cross-module reference must be fully qualified. In practice this means writing `Migrations::Database::Schema::Helpers` even when you're already inside `Migrations::Database::Schema`. Nested module definitions restore the full nesting chain, which brings several practical benefits: - **Less verbose code**: references like `Schema::Helpers`, `Database::IntermediateDB`, or `Converters::Base::ProgressStep` work without repeating the full path from root - **Easier to write new code**: contributors don't need to remember which prefixes are required — if you're inside the namespace, short names just work - **Fewer aliasing workarounds**: removes the need for constants like `MappingType = Migrations::Importer::MappingType` that existed solely to shorten references - **Standard Ruby style**: consistent with how most Ruby projects and gems structure their namespaces The diff is large but mechanical — no logic changes, just module wrapping and shortening references that the nesting now resolves. Generated code (intermediate_db models/enums) keeps fully qualified references like `Migrations::Database.format_*` since it must work regardless of the configured output namespace. - Convert 138 lib files from compact to nested module definitions - Remove now-redundant fully qualified prefixes and aliases - Update model and enum writers to generate nested modules with correct indentation - Regenerate all intermediate_db models and enums
193 lines
5.8 KiB
Ruby
Vendored
193 lines
5.8 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module Migrations
|
|
module Database
|
|
module Schema
|
|
class ModelWriter
|
|
def initialize(model_namespace, enum_namespace, header)
|
|
@model_namespace = model_namespace
|
|
@enum_namespace = enum_namespace
|
|
@header = header.gsub(/^/, "# ")
|
|
@namespace_parts = model_namespace.split("::")
|
|
@base_indent = " " * (@namespace_parts.size - 1)
|
|
end
|
|
|
|
def self.filename_for(table)
|
|
"#{table.name.singularize}.rb"
|
|
end
|
|
|
|
def output_table(table, output_stream, custom_code: nil)
|
|
@out = output_stream
|
|
module_name = Helpers.to_singular_classname(table.name)
|
|
columns = table.sorted_columns
|
|
|
|
emit "# frozen_string_literal: true"
|
|
emit
|
|
emit @header
|
|
emit
|
|
@namespace_parts.each { |part| emit "module #{part}" }
|
|
emit " module #{module_name}"
|
|
emit " SQL = <<~SQL"
|
|
emit " INSERT INTO #{escape_identifier(table.name)} ("
|
|
emit column_names(columns)
|
|
emit " )"
|
|
emit " VALUES ("
|
|
emit " #{value_placeholders(columns)}"
|
|
emit " )"
|
|
emit " SQL"
|
|
emit " private_constant :SQL"
|
|
emit
|
|
|
|
if table.model_mode == :extended
|
|
emit " # -- custom code --"
|
|
emit custom_code if custom_code.present?
|
|
emit " # -- end custom code --"
|
|
emit
|
|
end
|
|
|
|
emit method_documentation(table.name, columns)
|
|
emit " def self.create("
|
|
emit method_parameters(columns)
|
|
emit " )"
|
|
emit " #{@model_namespace}.insert("
|
|
emit " SQL,"
|
|
emit insertion_arguments(columns)
|
|
emit " )"
|
|
emit " end"
|
|
(@namespace_parts.size + 1).times { emit " end" }
|
|
ensure
|
|
@out = nil
|
|
end
|
|
|
|
private
|
|
|
|
def emit(text = nil)
|
|
if text.nil?
|
|
@out.puts
|
|
else
|
|
text.each_line(chomp: true) do |line|
|
|
@out.puts(line.empty? ? "" : "#{@base_indent}#{line}")
|
|
end
|
|
end
|
|
end
|
|
|
|
def column_names(columns)
|
|
columns.map { |c| " #{escape_identifier(c.name)}" }.join(",\n")
|
|
end
|
|
|
|
def value_placeholders(columns)
|
|
(["?"] * columns.size).join(", ")
|
|
end
|
|
|
|
def method_documentation(table_name, columns)
|
|
max_name_length = columns.map { |c| c.name.length }.max
|
|
see_references = []
|
|
|
|
param_lines = columns.map { |c| param_line_for(c, max_name_length, see_references) }
|
|
|
|
lines = [
|
|
" # Creates a new `#{table_name}` record in the #{Helpers.db_label(@model_namespace)}.",
|
|
" #",
|
|
*param_lines,
|
|
" #",
|
|
" # @return [void]",
|
|
]
|
|
|
|
if see_references.any?
|
|
lines << " #"
|
|
lines.concat(see_references.map { |ref| " # @see #{ref}" })
|
|
end
|
|
|
|
lines.join("\n")
|
|
end
|
|
|
|
def param_line_for(column, max_name_length, see_references)
|
|
param_name = column.name.ljust(max_name_length)
|
|
datatypes = datatypes_for_documentation(column)
|
|
line = +" # @param #{param_name} [#{datatypes}]"
|
|
|
|
if (enum = column.enum)
|
|
module_name = Helpers.to_singular_classname(enum.name)
|
|
first_const = Helpers.to_const_name(enum.values.min_by { |_k, v| v }.first)
|
|
|
|
line << "\n # Any constant from #{module_name} (e.g. #{module_name}::#{first_const})"
|
|
see_references << "#{@enum_namespace}::#{module_name}"
|
|
end
|
|
|
|
line
|
|
end
|
|
|
|
def datatypes_for_documentation(column)
|
|
datatypes =
|
|
Array(
|
|
case column.datatype
|
|
when :datetime, :date
|
|
"Time"
|
|
when :boolean
|
|
"Boolean"
|
|
when :inet
|
|
"IPAddr"
|
|
when :blob
|
|
"String"
|
|
when :json
|
|
"Object"
|
|
when :float
|
|
"Float"
|
|
when :integer
|
|
"Integer"
|
|
when :numeric
|
|
%w[Integer String]
|
|
when :text
|
|
"String"
|
|
else
|
|
raise "Unknown datatype: #{column.datatype}"
|
|
end,
|
|
)
|
|
|
|
datatypes << "nil" if column.nullable
|
|
datatypes.join(", ")
|
|
end
|
|
|
|
def method_parameters(columns)
|
|
columns
|
|
.map do |c|
|
|
default_value = !c.is_primary_key && c.nullable ? " nil" : ""
|
|
" #{c.name}:#{default_value}"
|
|
end
|
|
.join(",\n")
|
|
end
|
|
|
|
def insertion_arguments(columns)
|
|
columns
|
|
.map do |c|
|
|
argument =
|
|
case c.datatype
|
|
when :datetime
|
|
"Migrations::Database.format_datetime(#{c.name})"
|
|
when :date
|
|
"Migrations::Database.format_date(#{c.name})"
|
|
when :boolean
|
|
"Migrations::Database.format_boolean(#{c.name})"
|
|
when :inet
|
|
"Migrations::Database.format_ip_address(#{c.name})"
|
|
when :blob
|
|
"Migrations::Database.to_blob(#{c.name})"
|
|
when :json
|
|
"Migrations::Database.to_json(#{c.name})"
|
|
when :float, :integer, :numeric, :text
|
|
c.name
|
|
else
|
|
raise "Unknown datatype: #{c.datatype}"
|
|
end
|
|
" #{argument},"
|
|
end
|
|
.join("\n")
|
|
end
|
|
|
|
def escape_identifier(identifier)
|
|
Helpers.escape_identifier(identifier)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|