Skip to content

Instantly share code, notes, and snippets.

@zealot128
Last active February 18, 2024 12:11
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save zealot128/419949f1c426330493c84bb8eadc4533 to your computer and use it in GitHub Desktop.
Save zealot128/419949f1c426330493c84bb8eadc4533 to your computer and use it in GitHub Desktop.
Simple ruby script to generate Active Record Typescript information with enums + associations
# USAGE:
# rails runner rails-models-to-typescript-schema.rb > app/javascript/types/schema.d.ts
Rails.application.eager_load!
models = ActiveRecord::Base.descendants.reject { |i| i.abstract_class? }
belongs_to = true
has_many = true
conversions = {
"string" => "string",
"inet" => "string",
"text" => "string",
"json" => "Record<string, any>",
"jsonb" => "Record<string, any>",
"binary" => "string",
"integer" => "number",
"bigint" => "number",
"float" => "number",
"decimal" => "number",
"boolean" => "boolean",
"date" => "string",
"datetime" => "string",
"timestamp" => "string",
"datetime_with_timezone" => "string",
}
type_template = ""
models.each { |model|
name = model.model_name.singular.camelcase
columns = model.columns.map { |i|
type = conversions[i.type.to_s]
if (enum = model.defined_enums[i.name])
type = enum.keys.map { |k| "'#{k}'" }.join(" | ")
end
{
name: i.name,
ts_type: i.null ? "#{type} | null" : type
}
}
model.reflect_on_all_associations.select(&:collection?).each { |collection|
target = collection.compute_class(collection.class_name).model_name.singular.camelcase
columns << {
name: "#{collection.name}?",
ts_type: "#{target}[]"
}
} if has_many
model.reflect_on_all_associations.select(&:has_one?).each { |collection|
target = collection.compute_class(collection.class_name).model_name.singular.camelcase
columns << {
name: "#{collection.name}?",
ts_type: target
}
} if has_many
model.reflect_on_all_associations.select(&:belongs_to?).reject(&:polymorphic?).each { |collection|
target = collection.compute_class(collection.class_name).model_name.singular.camelcase
columns << {
name: "#{collection.name}?",
ts_type: target } } if belongs_to
type_template += <<~TYPESCRIPT
interface #{name} {
#{columns.map { |column| " #{column[:name]}: #{column[:ts_type]}; " }.join("\n")}
}
TYPESCRIPT
}
template = <<~TPL
declare namespace schema {
#{type_template.indent(2)}
}
TPL
puts template
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment