Skip to content

Commit

Permalink
feat: infer type from enums (#20)
Browse files Browse the repository at this point in the history
* Extract union type from enums

This adds support for enums in Rails. Defined enums will be converted
to a union type with it's allowed values.

* fix: ensure aliased attributes also infer types from enums

---------

Co-authored-by: Maximo Mussini <[email protected]>
  • Loading branch information
tijmenb and ElMassimo authored Aug 23, 2024
1 parent cd63653 commit 49dc61d
Show file tree
Hide file tree
Showing 10 changed files with 35 additions and 14 deletions.
2 changes: 1 addition & 1 deletion playground/vanilla/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ../..
specs:
types_from_serializers (2.0.2)
types_from_serializers (2.1.0)
listen (~> 3.2)
oj_serializers (~> 2.0, >= 2.0.2)
railties (>= 5.1)
Expand Down
3 changes: 3 additions & 0 deletions playground/vanilla/app/models/song.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
class Song < ApplicationRecord
belongs_to :composer
has_many :video_clips

enum genre: { disco: "disco", rock: "rock", classical: "classical" }
enum tempo: %w[slow medium fast]
end
2 changes: 2 additions & 0 deletions playground/vanilla/app/serializers/song_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ class SongSerializer < BaseSerializer
attributes(
:id,
:title,
:genre,
:tempo,
)

has_one :composer, serializer: ComposerSerializer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddEnumsToComposers < ActiveRecord::Migration[6.0]
def change
add_column :composers, :genre, :string, null: false
add_column :composers, :tempo, :integer, null: true
end
end
5 changes: 4 additions & 1 deletion playground/vanilla/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2022_07_09_151259) do
ActiveRecord::Schema.define(version: 2024_02_27_112250) do

create_table "composers", force: :cascade do |t|
t.text "first_name"
t.text "last_name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "genre", null: false
t.integer "tempo"
end

create_table "songs", force: :cascade do |t|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// TypesFromSerializers CacheKey c3af64a41d21e71dfae56644517994e1
// TypesFromSerializers CacheKey 460b4a83a2e81c9d693ac5490f9e0b76
//
// DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
import type Composer from './Composer'

export default interface Song {
id: number
composer: Composer
genre: "disco" | "rock" | "classical"
tempo: "slow" | "medium" | "fast"
title?: string
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// TypesFromSerializers CacheKey 6774f7cbf07614cf9b4136fbd0c8b441
// TypesFromSerializers CacheKey 3aa811bd4673913bbd09f2967979b304
//
// DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
import type Composer from './Composer'
Expand All @@ -7,6 +7,8 @@ import type Video from './Video'
export default interface SongWithVideos {
id: number
composer: Composer
genre: "disco" | "rock" | "classical"
tempo: "slow" | "medium" | "fast"
title?: string
videos: Video[]
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// TypesFromSerializers CacheKey c3af64a41d21e71dfae56644517994e1
// TypesFromSerializers CacheKey 460b4a83a2e81c9d693ac5490f9e0b76
//
// DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
import type Composer from './Composer'
Expand All @@ -8,6 +8,8 @@ declare global {
interface Song {
id: number
composer: Composer
genre: "disco" | "rock" | "classical"
tempo: "slow" | "medium" | "fast"
title?: string
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// TypesFromSerializers CacheKey 6774f7cbf07614cf9b4136fbd0c8b441
// TypesFromSerializers CacheKey 3aa811bd4673913bbd09f2967979b304
//
// DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
import type Composer from './Composer'
Expand All @@ -9,6 +9,8 @@ declare global {
interface SongWithVideos {
id: number
composer: Composer
genre: "disco" | "rock" | "classical"
tempo: "slow" | "medium" | "fast"
title?: string
videos: Video[]
}
Expand Down
15 changes: 7 additions & 8 deletions types_from_serializers/lib/types_from_serializers/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,12 @@ def ts_filename
TypesFromSerializers.config.name_from_serializer.call(name).gsub("::", "/")
end

# Internal: The columns corresponding to the serializer model, if it's a
# record.
def model_columns
@model_columns ||= _serializer_model_name&.to_model.try(:columns_hash) || {}
end

# Internal: The TypeScript properties of the serialzeir interface.
def ts_properties
@ts_properties ||= begin
model_class = _serializer_model_name&.to_model
model_columns = model_class.try(:columns_hash) || {}
model_enums = model_class.try(:defined_enums) || {}
types_from = try(:_serializer_types_from)

prepare_attributes(
Expand All @@ -64,7 +61,7 @@ def ts_properties
multi: options[:association] == :many,
column_name: options.fetch(:value_from),
).tap do |property|
property.infer_type_from(model_columns, types_from)
property.infer_type_from(model_columns, model_enums, types_from)
end
end
}
Expand Down Expand Up @@ -190,9 +187,11 @@ def inspect

# Internal: Infers the property's type by checking a corresponding SQL
# column, or falling back to a TypeScript interface if provided.
def infer_type_from(columns_hash, ts_interface)
def infer_type_from(columns_hash, defined_enums, ts_interface)
if type
type
elsif (enum = defined_enums[column_name.to_s])
self.type = enum.keys.map(&:inspect).join(" | ")
elsif (column = columns_hash[column_name.to_s])
self.multi = true if column.try(:array)
self.optional = true if column.null && !column.default
Expand Down

0 comments on commit 49dc61d

Please sign in to comment.