長野県上田市に引っ越してもう半年も経っていた

去年の秋に東京都練馬区から長野県上田市に引っ越して、もう半年が経っていた。

なぜ東京を離れたのか

  • 新型コロナで在宅勤務が平常になった
  • 新型コロナ感染リスクのストレスが嫌になった
  • 子供が小さいうちは東京のミニシアターや美術館、博物館などの文化資本にもそんなに触れられない
    • たまにいくくらいなら地方からでもいける
    • 自然史系の博物館は地方も充実している
  • 東京にしかないレストランも子供が小さいうちは行く機会が少ない
  • プログラミングの勉強会とかも子供が小さいうちは基本的にいかないことにしている
  • 関東圏の夏は暑くて湿気が強いのがすごく嫌い

というのもあり、東京離れるのを会社に相談したら快諾してくれたので、もはや東京にいる意味がなくなり、引っ越すことにした。

なぜ上田なのか

雨が少ないところがいい

長野は日照時間が日本でトップクラスのところが多いので、長野県が候補になった。なので、逆に栃木県那須塩原市とかは雨が多いのがネックで候補から外れた。

夏の暑さがマイルドなところがいい

群馬県高崎市茨城県筑波市とかはこれで候補から外れた。そこそこ標高があるところがいい。

東京駅まで行きやすいところがいい

松本市は東京へのアクセスのしにくさで候補から外れた。後日で、松本へ遊びに行ったけど、道路事情の辛さもあって松本選ばなくてよかったとなった。

雪は少ないところがいい

長野市まで行くと日本海の気候の影響が強くなるので降雪量が多いけど、上田市は比較的降雪量が少ない。

コンパクトな街がいい

佐久市もいいんだけど、上田の方が市街地がほどよくコンパクト。

という感じで上田市になった。

引っ越してどうか

今のところ、住みやすくて大満足。 郊外にあるようなチェーン店は大概あるし、美味しいレストランやパン屋、お菓子屋も探せば結構あって嬉しい。パニエレストランとか、東京いた時にもなかなかいけないような店でそれだけでも移住してよかったと思えている。 あと、そばがやはり普通にすごく美味しい。草笛が盛りがケチケチしてないのでお気に入りです。近所だし。

すごく驚いたのは、車が歩行者優先で一時停止すること。 「横断歩道で車が止まってくれる率」長野県はなぜダントツなのか 県警に聞いてみた | 乗りものニュース

神奈川や東京で、横断歩道を無視してビュンビュン走る車に怯えながら暮らしてた身からすると、それだけでかなり好感度高い。 けど、車を運転する身としては、ウィンカーを出すタイミングが遅すぎるのでそれはどうにかしてほしい。

野菜や果実が豊富で、直売所行くと、美味しいりんごがたくさん買えるし、地物の大豆や落花生も安価で買えてすごく助かる。

松本の方が栄えているんだけど、幼児を育てる身としては盛り場はなくていいので、今のニーズには松本は合わない。子供が中学生、高校生になったら松本に行きたがりそう。その時はバスで行ってもらおう。

人生を思い返すと、わりと引っ越しが多い。引っ越しは荷物減らす圧になるので、そういうのも気に入っている。これからもまた引っ越しするのかな。どうだろ、子供の学校のことがあるのでしばらくは定住すると思う。

GitHub Actionsでバージョンをバンプしつつタグを打ち、リリースノートにPRベースのchangelogを記載したい

tl;dr

https://github.com/shrkw/sandbox/blob/master/.github/workflows/create_release_tag.yml

このワークフローを実行すると、こんなリリースタグが生成されて便利。

f:id:shrkw:20210310122951p:plain

Release Release v0.4.0 · shrkw/sandbox · GitHub

なぜやりたいのか

  • 手動でリリースタグを作成しているとバージョンナンバーをたまに間違えたりして面倒
  • ローカルでタグを打ってプッシュするやり方もできるけど、ローカルにリモートの最新を取ってくるのも面倒なので、GitHubのWeb UI上で完結させたい
  • 今はActionsの手動実行でやるフローにしているけど、mainやreleaseへのコミットで起動させるようにしてもいいと思う
  • リリースノートにはコミットではなくプルリクエストを列挙したかった

どうやるか

やるべき要素は以下。

できたワークフローは以下。

sandbox/create_release_tag.yml

on:
  workflow_dispatch:
    inputs:
      bumping_part:
        description: 'major, minor or patch'
        default: 'minor'
        required: true

workflow_dispatchがあると Run workflow のメニューが出てくる。

f:id:shrkw:20210310122932p:plain

semverのどのパートを更新するかを選べるようにしていて、github-tag-actiondefault_bump でパートを指定できるので渡している。

yarn run の結果を全部出してるから余計な箇所もあるので、きれいにしたい場合はsedとかでがんばって。

リリースノート記載内容をどう集めるか

リリースノート、チェンジログを自動で生成したいのでlerna-changelogを使った - Bouldering & Com. に書いた、 @shrkw/lerna-changelog を使う。

      - uses: actions/checkout@v2
        with:
          fetch-depth: 0 # 履歴を全て見たい
      - name: Install dependencies
        run: yarn install
      - name: run lerna-changelog
        id: run_changelog
        run: |
          yarn run lerna-changelog | tee /tmp/changelog.txt
          changelog=$(cat /tmp/changelog.txt)
          echo "::set-output name=changelog::${changelog//$'\n'/'%0A'}";

これだと毎回インストールが実行されるのでお好みでキャッシュを利用すると良いです。

リリースノート、チェンジログを自動で生成したいのでlerna-changelogを使った

デプロイする前にリリースタグを打つ運用をしているんですが、リリースノートに書く内容を自分で集めるのも面倒なので、みんな自動化してるやろと思って調べてみました。

conventional-changelog/conventional-changelog がポピュラーだけど、今回はコミットの列挙じゃなくてGitHubのプルリクエストを列挙したかったので要件に合わず。

tl;dr

lerna-changelogの方が動作がキビキビしてていいけど、PRにタグを打つのも面倒。

なので、下記のPRのforkをビルドしてnpmで公開しました。

www.npmjs.com

github-changelog-generator

github.com

  • Ruby
  • PRからCHANGELOG.mdを自動で生成してくれる
  • ラベルがなければ全部のPRを載せてくれる
  • ラベルが打ってあると、enhancement, bug fixなどでカテゴリを分けてくれる
  • 起動すると全部のPRを取得するので遅い
    • キャッシュはするけど、それでも遅い

lerna-changelog

github.com

上記のforkでmasterのHEADを取り込んでやるのが良さそう。やってみる。

github.com

やってみた。GitHub Packagesで公開してみましたが、利用する各位が.npmrcで利用するnpmリポジトリの設定とパーソナルアクセストークンの発行が必要なんですね。それだと使いにくいので結局npmでスコープつけて公開にしました。

www.npmjs.com

PRを閲覧する関係で、GitHubのPersonal Access Tokenが必要(必要権限はrepo)なので、 $ export GITHUB_AUTH=... で登録し、package.jsonにこんな設定を入れると、

  "changelog": {
    "labels": {
      "unlabeled": "PRs",
      "dependencies": "Dependabot"
    },
    "wildcardLabel": "unlabeled"
  }

こんな感じで標準出力に出してくれるので、 PRsとDependabotの所だけリリースタグを打つときのリリースノートに入れるのが便利かなと。

yarn run v1.22.5
$ /home/runner/work/sandbox/sandbox/node_modules/.bin/lerna-changelog

## Unreleased (2021-03-09)

#### PRs
* [#6](https://github.com/shrkw/sandbox/pull/6) escape line feed character ([@shrkw](https://github.com/shrkw))

#### Dependabot
* [#100](https://github.com/shrkw/sandbox/pull/100) Bump @types/react from 17.0.2 to 17.0.3 ([@dependabot-preview[bot]](https://github.com/apps/dependabot-preview))

#### Committers: 1
- Hiroyuki Shirakawa ([@shrkw](https://github.com/shrkw))
- [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
Done in 0.83s.

タグを打ったあとに、 $ yarn run lerna-changelog --from=v1 --to=v2 に実行するのでも良い。

将棋の子 を読んだ

将棋の子 (講談社文庫)

将棋の子 (講談社文庫)

これはノンフィクションなんだろうか。取材はしてるんだろうけど想像で描く場面ばかりだから、要出典とか独自の解釈とかのタグがいっぱい付くべき文章ばかりだった。登場している人たちは勝手に解釈されて文句はないのかな。

全体通して、センチメンタルが強すぎるというか、書いている対象ではなく書いている自分自身への陶酔が強すぎのように感じた。昭和の著者のノンフィクションという感じ。

Railsのコントローラでの例外は直接ハンドリングしなくてもいい / 例外処理の方針

以下は静的なレスポンスを返す場合での方針です。動的なレスポンスならこの限りではありません。

基本方針

  1. 400番台以降のレスポンス(以下、例外レスポンス)を返したい場合でもコントローラであっても直接renderせず、例外をraiseする
  2. application.rb の config.action_dispatch.rescue_responses に例外とステータスコードマッピングを書いていく

こんな感じ。

  config.action_dispatch.rescue_responses = {
    "ActiveRecord::RecordNotFound" => :not_found
  }

デフォルトではこんな設定。 rails/exception_wrapper.rb at master · rails/rails · GitHub

rescue_fromは基本使わなくていい。

このやり方だとenvironmentごとに振る舞いがかわる。これはRails 6.0.3でのデフォルト設定での場合。

  • development
    • 例外がハンドリングされ、そのままRailsの詳細なエラーの画面として表示される
  • test
    • 詳細なデバッグ情報がHTTPレスポンスに出力されつつ、そのまま例外がスローされる(画面やレスポンスが描画されない)
  • production

このままだとrequest spec等のテストのときに、期待するようなレスポンスコードの変換がされないので具合が悪い。それについては下記で補足する。

また、デフォルトでのexceptions_appの仕組みに則って描画されるので、ActionDispatch::PublicExceptions によって描画され、JSONの場合は rack/utils.rb に定義されているメッセージが返却される。ここは変更できないので、変更したい場合はexceptions_appを差し替えるか別の仕組みを利用することになる。

例外の定義

上記のやり方だとカスタム例外を定義していかないといけないが、処理を振り分けするだけのためにStandardErrorにラベル付けする程度の例外を定義しなければならず、中身が1行だけのファイルが増えて煩雑。 なので、こういう定義でラクをするのはどうだろうか。

app/models/exceptions.rb

module Exceptions
  class NotFound < StandardError; end
  class Unauth < StandardError; end
end

なぜそうするか

開発中は、例外レスポンスは想定外な状況で起きることもあるので、発生した場所が分かった方がいい場合もある。

なぜそうなるのか

config.consider_all_requests_local, config.action_dispatch.show_exceptionsの二つの設定が影響し、ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions の二つのRack Middlewareが操作されている。

ざっくりいうと、 config.consider_all_requests_local がtrueならActionDispatch::DebugExceptionsがデバッグ画面を表示し、config.action_dispatch.show_exceptions がtrueなら、ActionDispatch::ShowExceptionsが例外をハンドリングしてユーザー向けの描画をしている。

config.consider_all_requests_local: このフラグがtrueの場合、どのような種類のエラーが発生した場合にも詳細なデバッグ情報がHTTPレスポンスに出力され、アプリケーションの実行時コンテキストがRails::Infoコントローラによって/rails/info/propertiesに出力されます。このフラグはdevelopmentモードとtestモードではtrue、productionモードではfalseに設定されます。もっと細かく制御したい場合は、このフラグをfalseに設定してから、コントローラでlocal_request?メソッドを実装し、エラー時にデバッグ情報を出力したいリクエストをそこで指定してください。

ActionDispatch::ShowExceptions: アプリケーションから返されるすべての例外をrescueし、リクエストがローカルであるかconfig.consider_all_requests_localがtrueに設定されている場合に適切な例外ページを出力します。config.action_dispatch.show_exceptionsがfalseに設定されていると、常に例外が出力されます。

ref. Rails アプリケーションを設定する - Railsガイド

上記の環境ごとの設定と見比べると分かってくる。

  • development
    • 例外がハンドリングされず、そのままRailsの詳細なエラーの画面として表示される
    • config.consider_all_requests_local: true
    • config.action_dispatch.show_exceptions: true
  • test
    • 詳細なデバッグ情報がHTTPレスポンスに出力されつつ、そのまま例外がスローされる(画面やレスポンスが描画されない)
    • config.consider_all_requests_local: true
    • config.action_dispatch.show_exceptions: false
  • production

refs.

テストどうするか

Rails の exceptions_app によるエラーページの表示をテストする - Qiita に書いているとおり、要は、request specなどでは本番と同じ設定になっていればいい。ということなので、rspecではこんな感じで、実行時に設定を変更する。

shared_context 'Show Exceptions', show_exceptions: true do
  around(:each) do |example|
    show_detailed_exceptions = Rails.application.env_config['action_dispatch.show_detailed_exceptions']
    show_exceptions          = Rails.application.env_config['action_dispatch.show_exceptions']

    Rails.application.env_config['action_dispatch.show_detailed_exceptions'] = false
    Rails.application.env_config['action_dispatch.show_exceptions']          = true

    example.run

    Rails.application.env_config['action_dispatch.show_detailed_exceptions'] = show_detailed_exceptions
    Rails.application.env_config['action_dispatch.show_exceptions']          = show_exceptions
  end
end

# `show_exceptions`メタを付けて実行する 
describe 'Get /hoge', show_exceptions: true do
  ...
end

2020年のRuby/RailsのJSONシリアライザは何を使うべきか問題

よくわからなかったので整理してみる。

候補

tl;dr

基本はjbuilderでいいと思う。 パフォーマンスが気になるならActiveModelSerializersか、Jb。ただ、どちらにせよメンテは心配。

Jbuilder

  • https://github.com/rails/jbuilder
  • HEYのGemfile見る限り、DHHは使っているようだ
  • テンプレート評価が遅いとか、パーシャル使うとpartの評価でN+1問題とか、パフォーマンス関連の課題があったみたいだけどどうなんだろう。コード見る限り変わってないような。
  • 開発は継続されている

サンプルコード

json.extract! @post, :id, :title, :content, :published_at
json.author do
  if @post.anonymous?
    json.null! # or json.nil!
  else
    json.first_name @post.author_first_name
    json.last_name @post.author_last_name
  end
end

ActiveModelSerializers

  • https://github.com/rails-api/active_model_serializers
  • Star: 5000
  • masterは2018年から開発が止まっているけど、 0-10-stable ブランチは年初にRuby2.7のサポートが入っている
  • Starも多し定番だった。メンテの頻度下がってるけど今も使ってる人は多いと思う

サンプルコード

class PostSerializer < ActiveModel::Serializer
  attributes :title, :body

  has_many :comments
  has_one :author
end

リアライザは自動で適用されるのでコントローラは変更不要。

class PostsController < ApplicationController

  def show
    @post = Post.find(params[:id])
    render json: @post
  end
end

jsonapi-rb

サンプルコード

class SerializablePost < JSONAPI::Serializable::Resource
  type 'posts'

  attributes :title, :body

  attribute :date do
    @object.created_at
  end

  belongs_to :author

  has_many :comments do
    data do
      @object.published_comments
    end
  end
end

あんまり使いやすそうな気がしない。

JSON:API Serializer

サンプルコード

class MovieSerializer
  include JSONAPI::Serializer

  set_type :movie  # optional
  set_id :owner_id # optional
  attributes :name, :year
  has_many :actors
  belongs_to :owner, record_type: :user
  belongs_to :movie_type
end
json_string = MovieSerializer.new(movie).serializable_hash.to_json
{
  "data": {
    "id": "3",
    "type": "movie",
    "attributes": {
      "name": "test movie",
      "year": null
    },
    "relationships": {
      "actors": {
        "data": [
          {
            "id": "1",
            "type": "actor"
          },
          {
            "id": "2",
            "type": "actor"
          }
        ]
      },
      "owner": {
        "data": {
          "id": "3",
          "type": "user"
        }
      }
    }
  }
}

Blueprinter

サンプルコード

# app/blueprints/todo_blueprint.rb
class TodoBlueprint < Blueprinter::Base
  identifier :id
    
  view :normal do
    fields :name, :due_at, :completed_at
  end

  view :extended do
    include_view :normal
    fields :description, :created_at, :updated_at
  end
end
# app/controllers/api/todos_controller.rb
module Api
  class TodosController < ApplicationController
    def index
      todos = TodoBlueprint.render Todo.all, view: :normal
      render json: todos
    end

    def show
      todo = TodoBlueprint.render Todo.find(params[:id]), view: :extended
      render json: todo
    end
  end
end
[
  {
    "id":1,
    "completed_at":null,
    "due_at":"2018-03-01 23:09:53 UTC",
    "name":"todo0"
  },
  {
    "id":2,
    "completed_at":null,
    "due_at":"2018-03-02 23:09:53 UTC",
    "name":"todo1"
  },
  ...
]

Jb

  • https://github.com/amatsuda/jb
  • Star: 948
  • わりあい安定してメンテされているが、amatsudaが一人でメンテしている感じ。個人のネームスペースだし
  • jbuilderみたいにビューファイルを書くタイプ
    • なので乗り換えは割とやりやすい

サンプルコード

# app/views/messages/show.json.jb

json = {
  content: format_content(@message.content),
  created_at: @message.created_at,
  updated_at: @message.updated_at,
  author: {
    name: @message.creator.name.familiar,
    email_address: @message.creator.email_address_with_name,
    url: url_for(@message.creator, format: :json)
  }
}

if current_user.admin?
  json[:visitors] = calculate_visitors(@message)
end

json[:comments] = @message.comments.map do |comment|
  {
    content: comment.content,
    created_at: comment.created_at
  }
end

json[:attachments] = @message.attachments.map do |attachment|
  {
    filename: attachment.filename,
    url: url_for(attachment)
  }
end

json

JSON APIどうなのか

https://jsonapi.org/

ページングとかの表現とか、みんなバラバラになるから大統一フォーマットを作ろうというので制定されている。 気持ちはわからないでもないけど、この汎用的な、冗長な表現が天下をとるとも思えないんだよなあ。 見れば見るほど気持ちはわかるんだけど、もっと気軽にやりたいんだという。実は僕が知らないだけで、みんなこれ使ってたりする?

こういうレスポンスになる。

{
  "data": {
    "id": "3",
    "type": "movie",
    "attributes": {
      "name": "test movie",
      "year": null
    },
    "relationships": {
      "actors": {
        "data": [
          {
            "id": "1",
            "type": "actor"
          },
          {
            "id": "2",
            "type": "actor"
          }
        ]
      },
      "owner": {
        "data": {
          "id": "3",
          "type": "user"
        }
      }
    }
  }
}

2020.06のReactでReduxは必要なのか

基本的にはstateはコンポネントに閉じ込められて使うべきで外に影響が染み出していくのはよくない。なので、useStateでシンプルに使うだけなのが一番良い。hooksがあるのでいろんなことがとてもシンプルにできるようになった。 とはいえ、遠いコンポネントと連携させたいケースや、認証状態のようなグローバルで共有したいというものがあるケースも理解できる。あとはキャッシュとか。

新しく入った開発プロジェクトでReactを使っているので、そういう時にどういう選択肢があるのかを整理してみた。

React使うのは3年ぶりくらい。前に使ったときはflux-utils, flow, immutable.jsとかで満足していた。今回のも、なるべく長期で育てていきたいプロジェクト。

今回の結論

頻繁に更新される情報やキャッシュしたいものはRedux ToolKitで書いたReduxで管理して、システムグローバルな参照しかしない情報はContextの利用という合わせ技がいいかなと思っている。

候補

  • グローバルな状態を使わない
    • 多くの階層を経由してpropsを渡すのを避けたいだけならコンポネントコンポジションを使えばいい [ コンテクスト – React]
    • websocketとかで更新情報を配布するようにしておいてメッセージを受けたらAPIからデータ取得するようにすれば、コンポネントを跨いだ更新、参照は不要になる
    • Pros.
      • すでにsocket使っているなら一方向の循環があると言えるので要素を増やさないで済む
    • Cons.
      • 認証状態の共有とかではそうもいかないので別途の方法が必要
      • キャッシュとかは別途考えが必要
      • socketサーバがいないと動かないので準備が増える
  • built-inのContextを使う
    • Pros.
      • Reactにbuilt-inな機能なので追加モジュール不要、メンテナンスも安心
      • シンプルなので挙動が想像しやすい
    • Cons.
      • reducer的な高レベルのファンクションを提供できない
        • useReducerで取得できるstate, dispatchを丸ごとContextに突っ込むというやり方もできるけど
      • カジュアルに更新するものをContextに入れるのは設計、パフォーマンスの両面でしんどい
  • Contextの薄いラッパーでFlux風
    • mizchiのちょっとしたスニペット [React Context を用いた簡易 Flux - mizdev]
    • Pros.
      • contextをちょっと使いやすくする薄いラッパーなので見通しがいいし自前でメンテナンスもできる
      • 参照、更新がやりやすい
    • Cons.
      • 普通にContext使うだけでもいいかもしれない。好みの問題
  • Redux Toolkit(RTK)
    • [Redux Toolkit | Redux Toolkit]
    • Reduxの使いやすくしたツールキット。vanillaなReduxはほとんどのケースで触らなくていいんじゃないか
    • createSlice, createAsyncThunkでかなり幸せになれる
    • Pros.
      • Reduxの実績はたくさん
      • UIと状態を切り離せるので、キャッシュとして利用することもできる
      • 型付けがこなれているのでdispatchするにしても dispatch(actions.fetch()) のように文字列を使わずに実行できる
    • Cons.
      • ReduxはFluxアーキテクチャの具現化を目的にしているのでグローバルな状態管理という目線で見ると、多少やりすぎ感は残る。RTKとRedux hooksで昔よりかなりマシになっているけど
  • reactn
  • recoil
    • [Recoil]
    • Pros.
      • facebookプロダクトなので筋は良さそう
    • Cons.
      • experimentalなので使うにはちょっと早い