Rails + OpenAPI + ViewComponent + Hotwire
Rails + OpenAPI + ViewComponent + Hotwire(TODOの表示切り替え/CRUD/通知の実装など)の構成でアプリを作る練習。まずはTODOリストを作成する。DBやモデル周りの設計は別にスコープ外なので外部APIとしてJSONPlaceholder - Free Fake REST APIを使う。
JSON Placeholder APIのOpenAPI化
JSONPlaceholder - Free Fake REST APIをOpenAPIファイルに落とし込む。そんなに量はないしゼロから手書きも可能だが面倒なのでCursorで下記プロンプトを指定して一気に生成してもらった。
code:txt
@Web @https://jsonplaceholder.typicode.com/guide/ このAPIドキュメントを元にOpenAPIのymlファイルを作成してください。
OpenAPIのYAMLファイルはconfig/api_docs配下に配置している。
なおVSCodeで編集するにはOpenAPI (Swagger) Editor - Visual Studio Marketplaceを使うと少し楽になるので入れてみた。
OpenAPIファイルからRuby用のクライアントライブラリの生成
OpenAPITools/openapi-generator: OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)を使ってYAMLファイルから自動生成する。
インストールはbrewで行った。
code:sh
brew install openapi-generator
下記のようなコマンドを実行すると指定したdir配下にGemが生成される。
code:sh
openapi-generator generate -i ./config/api_docs/openapi.yml -g ruby -o ./tmp/out
あとはGemfileに追記してローカルinstallする。
code:Gemfile
gem 'openapi_client', path: './tmp/out'
実行に関しては./tmp/out/README.mdに使い方が書いてあるのでそれを参照する。
code:rb
require 'openapi_client'
api_instance = OpenapiClient::DefaultApi.new
opts = {
user_id: 1
}
begin
result = api_instance.posts_get(opts)
p result
rescue OpenapiClient::ApiError => e
puts "Exception when calling DefaultApi->posts_get: #{e}"
end
Postの表示系を実装
routes.rb/controller/viewsに適切にファイルを生成していくだけ。controllerでは先ほど生成したOpenapiClientライブラリを使っている。
code:rb
class PostsController < ApplicationController
def index
client = OpenapiClient::DefaultApi.new
@posts = client.posts_get
end
def show
client = OpenapiClient::DefaultApi.new
@post = client.posts_id_get(params:id)
end
end
ViewComponentの導入
Overview | ViewComponent
code:Gemfile
gem 'view_component'
code:sh
bundle exec rails g component Post post
@postsの中のpostの表示をViewComponentにしていく
Postの更新系を実装
特筆することはない。唯一turboでdeleteするときにlinkでdeleteができないところは注意が必要。turboが効いてる場合は以下のようにdata-turbo-methodとdata-turbo-confirmを指定すると良い。
code:html
<a href="/posts/<%= @post.id %>" data-turbo-method="delete" data-turbo-confirm="Are you sure?">Delete</a>
Tailwindのコンポーネント
Flowbiteというサイトがshadcn/uiみたいな感じでコピペで使えるので感じで良い。
notyfを導入する
Stimulusとimport-mapを使う練習のためにcaroso1222/notyf: 👻 A minimalistic, responsive, vanilla JavaScript library to show toast notifications.を使ってnotification barを実装する。
importmapでnpmパッケージを導入するにはまずpinする。これでnotyfがconfig/importmaprbに追加される。
code:sh
bundle exec bin/importmap pin notyf
notyf用のCSSを追加する。views/layouts/application.html.erbに<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.css">を追加。CSSに関してはimportmapでいい感じにやってくれないので本家ライブラリのCSSをDLしてきてassets配下に置くかcdnから読み込むしかない。だるい。
What is the recommended way to import css bundled in js libraries · Issue #80 · rails/importmap-rails
notyf用のViewComponentを作成する
code:sh
bundle exec rails g component Notyf
app/components/notyf_component.rb
code:rb
class NotyfComponent < ViewComponent::Base
def initialize(flash = nil)
@flash = flash
super
end
end
app/components/notyf_component.html.erb
code:erb
<% if flash.present? %>
<div data-controller="notyf"
data-notyf-flash-value='<%= flash.as_json %>'>
</div>
<% end %>
app/javascript/controllers/notyf_controller.js
code:js
import { Controller } from '@hotwired/stimulus'
import * as Notyf from 'notyf'
export default class extends Controller {
static values = {
flash: Array
}
connect() {
const notyf = new Notyf.Notyf()
this.flashValue.forEach((flash) => {
switch (flash0) {
case 'notice':
notyf.success(flash1)
break
case 'alert':
notyf.error(flash1)
break
}
})
}
}
viewの方に<%= render NotyfComponent.new(flash) %>を埋め込んで完了。
rails OpenAPI ViewComponent Hotwire