Improving Typescript experience in Rails by generating a Typescript schema from Rails models (with enums and associations)
Tags: rails, typescript
Published: 2021-09-22 18:29:35

---
title: Improving Typescript experience in Rails by generating a Typescript schema from Rails models (with enums and associations)
published: true
date: 2021-09-15 14:01:17 UTC
tags: rails,typescript
canonical_url: https://www.stefanwienert.de/blog/2021/09/15/generate-typescript-schema-from-rails-models-with-enums-and-associations/
---

For a side project (which I won’t reveal at the moment), I develop most of the frontend in Vue/TSX+Typescript. At the moment, I don’t have a specific “API”, but just use Inertia.js with a couple of main models that are simply serialized by `model.as_json`. Having then to type out a deep model schema with dozens of connected models (using delegated types in my case) by hand is a PITA. Fortunately, I came across [schema2type](https://github.com/kawamataryo/schema2type), which is a Rails plugin that generates a Typescript schema for all of your models by only using `schema.rb`, **BUT** because of this constraint, it cannot know about Rails enums and associations.

This is why I quickly cobbled together a script that improves on that idea:

[**Gist**](https://gist.github.com/zealot128/419949f1c426330493c84bb8eadc4533)

<script src="https://gist.github.com/zealot128/419949f1c426330493c84bb8eadc4533.js"></script>

To “install” and run:

```bash
wget https://gist.github.com/zealot128/419949f1c426330493c84bb8eadc4533/raw/1c4629c05506630d3fe8543320cd6d2026405d8e/rails-models-to-typescript-schema.rb

rails runner rails-models-to-typescript-schema.rb > app/javascript/types/schema.d.ts

```

Afterwards you can easily use `schema.MyModel.<TAB>` to autocomplete all the things! In my usecase, I then built upon that schema to define more specific types that I serialize and pass to my frontend (via Inertia.js). It also works for the built-in ActiveStorage models and spit out this:

```typescript
declare namespace schema {

  interface ActiveStorageBlob {
    id: number;
    key: string;
    filename: string;
    content_type: string | null;
    metadata: string | null;
    service_name: string;
    byte_size: number;
    checksum: string;
    created_at: string;
    variant_records?: ActiveStorageVariantRecord[];
    attachments?: ActiveStorageAttachment[];
    preview_image_attachment?: ActiveStorageAttachment;
    preview_image_blob?: ActiveStorageBlob;
  }

  interface ActiveStorageVariantRecord {
    id: number;
    blob_id: number;
    variation_digest: string;
    image_attachment?: ActiveStorageAttachment;
    image_blob?: ActiveStorageBlob;
    blob?: ActiveStorageBlob;
  }

  interface ActiveStorageAttachment {
    id: number;
    name: string;
    record_type: string;
    record_id: number;
    blob_id: number;
    created_at: string;
    blob?: ActiveStorageBlob;
  }
}

```

The 80 line script already supports:

- belongs\_to, has\_one and has\_many associations (un-polymorphic)
- enums
- null and not null
- type mapping, borrowed from the original schema2type script

Security implications: The script generates a type with all attributes, that includes password-digests, tokens etc. You probably shouldn’t send those informations to frontend clients in general. For the typings that should be fine though, because, typings are only used in development and discarded in a production build.

To further improve your Typescript experience, have a look at `https://github.com/bitjourney/ts_routes-rails` - IMO here you really should make sure to not leak Routes to clients that you don’t want them to access. Depending on your production build unused route helpers may or may not be removed due to treeshaking.