🐶
scaffdogを使ってrails generate scaffold相当を手軽に実現する
scaffdogというscaffoldingツールがとても良かったので紹介します。
scaffdogの使い方
- 以下のようなmarkdownファイルを用意して
yarn run scaffdog generate crudを実行 -
モデル名を入力してくださいと聞かれるので、そこでUserと答える -
src/models/User.tssrc/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つきで書ける・読める。
-
pascalcamelsnakeなど、組み込みのヘルパー関数が便利。 - ヘルパー関数をカスタマイズできるため、柔軟に出力できる。
なぜrails generate scaffold相当をしたいか
現在prisma,GraphQL(Apollo Server, Apollo Client),nextを使ってWebサービス開発をしており、あるモデル(仮に User とする)に対するCRUDを作るために、例えば以下のファイル群を作る・編集する必要があります。
- サーバーサイド
prisma.schema-
User.ts,UserInput.ts(nexusのobjectTypeとinputObjectType),UsersQuery.tsUpsertUserMutation.ts -
query.tsmutation.tstypes.tsinputs.ts
- フロントエンド
-
fetchUsers.graphql,upsertUser.graphql,UserFields.graphql -
pages/users/index.tsxpages/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