Turbolinks5でPOSTするときはajax経由のほうが良いのかも

Turbolinks5になってから「フォームでバリデーションエラーになったあとその画面をリロードしたときの挙動がTurbolinksを利用していないときと異なる」という問題がある。

2年半くらい前からIssueが立っている

Turbolinks doesn't recognize form re-submission [POST] and navigates instead [GET]. · Issue #251 · turbolinks/turbolinks

POSTしたあとの画面をリロードしようとすると、通常は「フォームの内容を再送信しますか?」みたいな確認ダイアログが出る。しかしTurbolinks経由だと単にいまのURLをGETで取得しようとする*1。これは、Turbolinksがhistory apiを使って履歴を操作しているのが原因。history apiに履歴を足すときはGETとしてのURLしか足せないので、現状のTurbolinksの仕様では対応するのは無理ぽい(放置されている原因の一つはこれだと思う)

そもそもTurbolinks(というかBasecamp)的には、formは全部js経由にしてしまう方針なので上記の問題で困っていない、というのもありそう。Railsでform_withを使ったとき、デフォルトではremote: trueになる。

普通はajaxリクエストでリダイレクトはできないのだけど、turbolinks(turbolinks-rails)ではajaxでpostを受け付けたときのハックが書かれていて、redirect_toされたときはTurbolinks.visitを使うようにしてリダイレクトを実現している。なので正常系だけならremote: trueで問題ない。

バリデーションエラーになったときの対応が面倒で、これは独自に定義をしないといけない。DHHはSJR(Server generated Javascript Response)でやればいいと書いてる。ようするにcreate.js.erbみたいなやつ。

Provide form_with as a new alternative to form_for/form_tag · Issue #25197 · rails/rails

わかるんだけどバリデーションエラーを表示するためだけにそれをやるのってめんどいのですよね。画一的に処理できるgemをつくればいいのか。すでに誰か作っていそうだからそれでも良さそう。

*1:railsであればindexアクションのURLになるはず

同一モデルへの関連に対するdependentオプション

いつ書いたのか忘れたけど、下書きに書いてあったので公開

次のように、同じモデルに対してdependent: :destroyを設定してしまうと、もとのモデルを削除したときに二重にDELETEが発行されてしまう

class User < ApplicationRecord
  has_one :latest_post, -> { order(created_at: :desc) }, class_name: 'Post', dependent: :destroy
  has_many :posts, dependent: :destroy
end
 user.destroy
   (0.1ms)  begin transaction
  Post Load (0.3ms)  SELECT  "posts".* FROM "posts" WHERE "posts"."user_id" = ? ORDER BY "posts"."created_at" DESC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
  Post Destroy (0.4ms)  DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 2]]
  Post Destroy (0.1ms)  DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 1]]
  Post Destroy (0.1ms)  DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 2]]
  User Destroy (0.1ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 1]]
   (0.8ms)  commit transaction

dependent: :destroyは片方だけにしておいたほうがよさそう

capybaraでunicornを使うときの注意事項

  • capybaraで利用するテストサーバは変更可能
  • デフォルトだとwebrickだったはず?
  • Railsのsystem testだとpuma
  • unicornをproductionで使っているのであれば、unicornにしたほうがいいのでは?と思うタイミングがありやってみた

やり方

問題点

  • feature spec(system spec)でモックを使うことができないので使いづらい
  • puma(やwebrick)はテストのプロセスと同じプロセス内のスレッドで利用するので、モックの定義がテストサーバにも反映される
  • unicornは、最低でも2プロセスになる(マスタプロセスとワーカプロセス)
  • ワーカプロセスはテストのプロセスとは独立しているので、テストのプロセス内でモックを定義してもテストサーバには反映されない

まとめ

capybaraでunicornを使うには覚悟が必要

るりま編集方法のメモ

プログラミング言語 Ruby リファレンスマニュアルでリンク切れが見つかったので、PRしようと思って調べたことのメモ。

  • 内容はGitHubで管理されているrurema/doctree: Repository of Japanese Ruby reference manual
    • 普通にPRだせばよいみたい
  • wikiに古い記事がたくさんあって、何をどうしたら修正内容を確認できるのかよくわからない…
    • Rakefileがあったのでrakeタスクでhtmlファイルを確認できるのだろうと推測
    • bundle exec rake statichtml:2.5.0 のようにすると、ローカルの/private/tmp/html 配下にhtmlファイルが出力された
      • html出力用のdatabaseが/tmp/db-2.5.0のような場所にできる
      • 編集前にdatabaseを作ると、編集しても内容が反映されないっぽい

ということで編集した内容をローカルで確認できるところまでやった。がリンクの仕様がよくわからずPRまだ出せていない><

追記

PRだしました Fix broken link by willnet · Pull Request #1193 · rurema/doctree

Font Awesome5をRailsアプリケーションに導入した

Font Awesomeのバージョンが5になって、有料版と無料版の二パターンになった。

  • 4.7.0だと675アイコン
  • 5.0.6(無料版)だと929アイコン
  • 5.0.6(有料版)だと2316アイコン(無料版のアイコン含む)

Font Awesomeお世話になっているし、アイコン多いほうが嬉しいので課金した。リリース前だったので少し安くて$40だったはず(今は$60)。

Font Awesome5の導入の仕方はいくつかある。webサービスで使う場合は大きく次の2つ

  • jsでsvgを使う方法
  • cssとfontを使う方法

公式的にはjsの方がオススメとのこと。

js版のインストール方法と気をつける点

無料版はnpmパッケージとしても提供されているけど、有料版はパッケージがないみたいなので有料版を使う場合は公式サイトからファイルをダウンロードする必要がある。sprocketsもしくはwebpackerでも、どちらでもダウンロードしたものを読み込めば動いた。

js版、ファイル読み込み時にフォント用のクラスをsvgに置き換えるということをしているので、turbolinksを使っているとうまく動かない。次のようにturbolinks:loadイベントをフックしてsvgへの置き換え用のメソッドを実行してやるとうまく動く。

$(document).on('turbolinks:load', () => {
  window.FontAwesome.dom.i2svg()
})

css版のインストール

css版も一応検討したけど、sprockets経由で利用するにはcssからfontへのパスの解決が面倒(ファイル名にdigestが含まれてしまう)なのでjs版にした。4.7.0であればそのへん解決したfont-awesome-sassというgemがあるので、もしこれが5対応になったらこれをGemfileに入れるだけで良いのかもしれない。

(追記)5に対応したバージョンリリースされたみたい。

prmdを利用して空のレスポンスを表現する

prmdで生成されるドキュメントは、Response Exampleが自動的に生成されて便利。

f:id:willnet:20171229175710p:plain

しかし、何かを作成するときや何かを削除するときなど、レスポンスボディがなくても問題ないことを表すのが面倒。

prmd/link.md.erb at 928d9cb1c0fa1163bab5f3cb43c81eed4a147aa2 · interagent/prmd を読んで、どのようにResponse Exampleが生成されるかを調べて、空のレスポンスを表現する方法を調べてみた。

方法1: relをemptyにする

これが一番手っ取り早いけど、status codeが202に決め打ちになっている。

方法2: response_example を使う

次のようにresponse_exampleを指定するとResponse Examnpleをカスタマイズできる。若干面倒だけど現状ではこれしか方法がないように見える。

links:
- description: ユーザフォロー API
  href: "/api/user/{(%2Fschemata%2Fuser%23%2Fdefinitions%2Fid)}/follow"
  method: POST
  rel: create
  title: ユーザフォロー API
  schema:
    properties:
      access_token:
        "$ref": "/schemata/session#/definitions/access_token"
    required:
      - access_token
    type: object
  response_example:
    head: HTTP/1.1 201 Created
    body: ''

続、Railsのきれいなコードについて議論する勉強会

Railsのきれいなコードについて議論する勉強会の話の続き。

とりあえずお題を集めてみようと思い、GitHub上にclean-rails-jaというorganizationを作りました*1。下記のIssueで議論のお題になりそうな案を募集しています。

「こういうとき、いつもどうしたら良いか悩むんですよねー」というものがある方は↓にどしどしその内容をご記入ください。

Railsのきれいなコードのお題案 · Issue #1 · clean-rails-ja/conversation

お題が集まってきたらそれを整理して、勉強会に臨めるとよいなと思います。

*1:ginzarbでもよかったけど別件な気もするのでとりあえず分けてみた