🐶
scaffdogを使ってrails generate scaffold相当を手軽に実現する
scaffdogというscaffoldingツールがとても良かったので紹介します。
scaffdogの使い方
- 以下のようなmarkdownファイルを用意して
yarn run scaffdog generate crud
を実行 -
モデル名を入力してください
と聞かれるので、そこでUser
と答える -
src/models/User.ts
src/controllers/UserController.ts
が作成される
---
name: 'crud'
root: '.'
output: '.'
ignore: []
questions:
name: 'モデル名を入力してください。'
---
# `src/models/{{ inputs.name }}.ts`
```ts
export class {{ inputs.name }} {
// TODO
}
``
# `src/controllers/{{ inputs.name }}Controller.ts`
```ts
export class {{ inputs.name }}Controller {
// TODO
}
``
markdownの中ではmustache記法({{ }}
)を使って入力された値を埋め込みできます。
説明するほど難しいところはないので、詳しい使い方は公式をどうぞ。
便利な点
- 設定ファイルの書き方がシンプルで分かりやすい。学習コストなしで良い。
- 設定ファイルがmarkdownなので、syntax highlightつきで書ける・読める。
-
pascal
camel
snake
など、組み込みのヘルパー関数が便利。 - ヘルパー関数をカスタマイズできるため、柔軟に出力できる。
なぜrails generate scaffold相当をしたいか
現在prisma,GraphQL(Apollo Server, Apollo Client),nextを使ってWebサービス開発をしており、あるモデル(仮に User
とする)に対するCRUDを作るために、例えば以下のファイル群を作る・編集する必要があります。
- サーバーサイド
prisma.schema
-
User.ts
,UserInput.ts
(nexusのobjectType
とinputObjectType
),UsersQuery.ts
UpsertUserMutation.ts
-
query.ts
mutation.ts
types.ts
inputs.ts
- フロントエンド
-
fetchUsers.graphql
,upsertUser.graphql
,UserFields.graphql
-
pages/users/index.tsx
pages/users/[id]/edit.tsx
- user関連のコンポーネント群
-
14ファイルに渡って毎回同じような記述をするのは非人間的なので、効率化したい。可能ならscaffoldでモデル名とカラム名・型を指定したら、それに沿って全てのファイルが出力されて欲しい。
ということで、rails generate scaffold相当の挙動になるようにしてみる。
テクニック1. 既存ファイルへの追記
scaffdogは新規作成だけでなく、追記も可能です。
追記のためには、 read
という組み込みのヘルパー関数を使ってファイルを開いて上書きすればOK。
以下、prismaファイルの末尾にモデルを追加する例。
# `src/infra/schema.prisma`
```prisma
{{ '/foo/bar/src/infra/schema.prisma' | read }}
model {{ inputs.name | pascal }} {
id String @id @default(uuid())
// ...
}
``
テクニック2. カラムの作成
bin/rails generate scaffold User name:string age:integer
的なことをしたいところですが、これもヘルパー関数を自作すれば楽勝です。
ヘルパー関数は .scaffdog/config.js
で拡張することができます。
ここでは name:string age:integer
のような引数を受け取って、prismaの属性を出力する prismaAttributes
ヘルパー関数を定義してみます。
module.exports = {
files: ['./*'],
helpers: [
(registry) => {
registry.set('prismaAttributes', (_context, value, _size, _str) => {
const attributes = value.split(',').map((line) => {
const [name, nexusType] = line.split(':')
return `${name} ${prismaType(nexusType)}`
})
return attributes.join(`\n`)
})
},
],
}
function prismaType(nexusType) {
return (
{
string: 'String',
int: 'Int',
date: 'DateTime',
datetime: 'DateTime',
boolean: 'Boolean',
json: 'Json',
}[nexusType] ?? nexusType
)
}
これを以下のようなmarkdownで記入すればOKです。
---
name: 'crud'
root: '.'
output: '.'
ignore: []
questions:
name: 'モデル名を入力してください。'
attributes: '属性を入力してください。'
---
# `src/infra/prisma/schema.prisma`
```prisma
{{ '/foo/bar/src/infra/prisma/schema.prisma' | read }}
model {{ inputs.name | pascal }} {
id String @id @default(uuid())
{{ inputs.attributes | prismaAttributes }}
}
``
Discussion