rspec-parameterized の誤用でテスト用DBにデータが残った話

背景

rspec-parameterized を使って spec を作成している際に、テスト用 DB にデータが残ってしまうという事象が発生した。このはまり方は初めてだったので記録として残しておく。 原因まではおいきれていない点は了承いただきたい。 (こんな書き方するなよという話な気はしている...)

本題

前提

rspec-parameterized の where で let の値を参照する時には、以下のように ref を用いる必要がある。 (READMEにも記載がある)

require 'rails_helper'

RSpec.describe Post, type: :model do
  describe '#test' do
    let(:post) { create(:post) }

    where(:expected) do
      [
        [ref(:post)]
      ]
    end

    with_them do
      it do
        expect(Post.first).to eq expected
      end
    end
  end
end

(↑は再現性を優先するために作成したテストなのでその点はご容赦ください。)

refをつけるのを忘れて

    where(:expected) do
      [
        [post]
      ]
    end

こんな感じに書くと、通常は以下のようにエラーが発生する。

An error occurred while loading ./spec/models/post_spec.rb.
Failure/Error: [post]

NoMethodError:
  super: no superclass method `post' for #<RSpec::ExampleGroups::Post::Test:0x0000000124eb3a78>

発生した事象

require 'rails_helper'

RSpec.describe Post, type: :model do
  describe '#test' do
    let(:post) { create(:post) }

    context 'xxx' do ⭐️
      where(:expected) do
        [
          [post]
        ]
      end
  
      with_them do
        it do
          expect(Post.first.title).to eq expected.title
        end
      end
    end ⭐️
  end
end

上記のように where 以降を context で囲むと(⭐️の箇所)、test がパスするようになるが、test用のデータベースにデータが残った状態になってしまう。

$ bundle exec rspec spec/models/post_spec.rb
.

Finished in 0.02243 seconds (files took 5.12 seconds to load)
1 example, 0 failures

$ RAILS_ENV=test bundle exec rails db
sqlite> select * from posts;
11|MyString|MyText|2025-01-19 04:38:14.849007|2025-01-19 04:38:14.849007

let(:post) { create(:post) }を context のなかに入れるなりそもそも不必要にcontext 使わないなりすれば回避可能。

所感

  • 要因は不明。折を見て別途調査したいが、ひとまず記録のために事象だけ残しておく。
  • おそらく詳しい人が見たら「そもそもなんでこんなことするの?」と言われてしまうような内容な気はする
    • 流れとしてはパラメーターテストと並行して、1回だけ確認したいテストケースを書こうとして、それぞれとりあえずcontextで囲っておいたら遭遇した。
  • ただなぜそれをすべきでないのか?は正直理解できていない。多分 rspec そのものに対する理解が不足してるんだな...

Rails View のロジックって結局どうする

背景

Rails 8.0 のお試しとして Rails の簡単なアプリを作っている最中、ふと Rails の view のロジックってどうするのが良いんだ?という疑問が生まれた。

経験上は大体3種類。

  • view helper: 最初から入ってるやつ。グローバルだから嫌われる。初期から入ってるから最初はまぁいいかで使える気はするが、規模が大きくなったらメソッド名の衝突やら依存関係やらで泣くのが目に見えている。
  • presenter object: POROでも行けそうだが、大体 drapper とか active decorator の gem を使う印象。割とよくいるイメージ。 ActiveRecord のモデルに気がつくと勝手に view 用のメソッドが生えてるのが正直気持ち悪い。別名つけたい。user モデルだったら user_presetner 的な。でもすごく冗長に感じると思われる。Rails っぽくもない。
  • view component: viewとclassをセットで扱えるやつ。最近はよく見かける。Reactとかやってると特に惹かれる?(私は最初そんな感じだった)。便利だと思うけど個人的にはあんまり好きじゃない。ピッタリハマるケースもたくさんあるけど、ここまでやらなくて良いのでは?というケースも同じくらいあるイメージ。ボイラーテンプレート的になってしまいがちというか...もちろん使う人,使い方次第だとは思うが...。でも使うとうーんやっぱりこれか...となりそう。

仕事だと、1から作るのでない限り、そこにあるやつ使いますねって感じになる。今なら多分 view component なのか?

自分なりの意見を持ちたい...でも持てない。 だから DHH/37signals のやり方みてみるか! という感じの背景。

本題

調査

1. Do Basecamp/Hey use ViewComponents?

discuss.rubyonrails.org

ViewComponents を出発点として、Rails の view のロジックあり方について、かなり熱い議論が交わされている。 全部追ってられないので DHH のコメントを中心に抜粋する。

We do not use ViewComponents at 37signals, and have no plans to do so. Glad to see that they’re working for some people, but in my opinion, you’re usually better off with regular partial templates the majority of the time.

If some views are particularly complicated, we use presenter objects that serve to encapsulate domain object manipulation required by the views. A heuristic there is that if you have helper methods that call other helper methods, you may benefit from a presenter object. But these presenter objects do not call #render directly.

https://discuss.rubyonrails.org/t/do-basecamp-hey-use-viewcomponents/83270/9

整理すると

  • 37signals では ViewComponent は使っていない。また今後使う予定もない。多くの場合は標準の partial templates で良いと考えている。
  • view が複雑になる場合は、presenter object を用いて、ドメインに関する操作をカプセル化している
  • helper method が他の helper メソッドを呼ぶようになったら、presenter objectを使った方が良い

といった感じ。 ここでいうところの presenter object は、一般的には Drapper/Acitve Decorator などの Gem が該当しそうにも思うが、DHH/37signals の普段の言動を鑑みるに PORO で自分で作って別に Gem を推奨したりはしてないだろう(当然か)

2. Do Basecamp/Hey use ViewComponents?

zenn.dev

なんでも 37Signals が Writebook というプロダクトのコードを公開してくれているらしい。

once.com

ここからダウンロードできるので早速 view の観点で見てみた。

これは...基本的に全て view helper でやってるね。 そもそもアプリケーションの規模が小さいというのはあるけど(table数14)、このくらいなら普通に成り立つのか。 あと関係ないけど自己申告通り 使ってる Gem ほんと少ないな。テストもやっぱり minitest で書いてんな... あとむしろ JavaScript のコードが多いな。こっちの方が読むの大変そうな気もする、1つ1つは大したことないような気もする 自作のアプリならこのくらいで良いんだろうな。勉強になります。

Docerfile とかもあるしローカルでも普通に立ち上がるのだろうか? どこかでやってみたいところではある。

まぁ結論としては、「困らない限りは view helper でいける」ということを体現していた感じかな。

参考

所感

  • helperで行けるならhelperで行けばいいじゃない。という考えはまぁまぁすき。これで行こうかな。
  • 自分の作るアプリなら DHH 流で良いのかもしれない。DBはSQLiteで、テストはminitestになるが...😅
  • presenter object 作ってるコードもみたい。

本題とは関係ないメモ

Rails の I18n は、ロケールファイルを用いた訳文の参照(I18n.t)とフォーマット変換(I18n.l)をしているだけとようやく理解した話

背景

RailsI18n について、長年よくわからないままなんとなく使っていたので、いい加減重い腰を上げて理解しに行く。

本題

Rubyだけで I18n を使ってみる

これをするのが一番早く I18n を理解できた。

そもそも

GitHub - ruby-i18n/i18n: Internationalization (i18n) library for Ruby

という Gem が存在しており、Rails もこれを組み込み幾分か拡張しているだけのようだ。

以下のような簡単なサンプルコードで、Rails から独立した I18n の動作を確認できる。

require 'i18n'
require 'date'

I18n.default_locale = :ja

I18n.load_path = Dir["./*.yml"]

puts I18n.t('hello')
puts I18n.t('activerecord.models.post')
puts I18n.t('activerecord.attributes.post.title')
puts I18n.l(Date.today)
puts I18n.l(Date.today, format: :short)
ja:
  hello: こんにちは
  activerecord:
    models:
      post: ポスト
    attributes:
      post:
        title: タイトル
        content: コンテンツ
  date:
    formats:
      default: '%Y/%m/%d'
      short: '%m/%d'

動作結果は以下の感じ。

% ls
Gemfile      Gemfile.lock ja.yml       try_i18n.rb

% ruby try_i18n.rb
こんにちは
ポスト
タイトル
2025/01/05
01/05

Rails上で I18n.t, I18n.l を用いるのと同じ感覚で、I18n を利用できることがわかる。

サンプルコードからみる I18n の基本ユースケース

# (前略)

# 1. I18n モジュールの設定
I18n.default_locale = :ja

# 2. ロケールファイルの読み込み
I18n.load_path = Dir["./*.yml"]

# 3. I18n APIを利用した訳文の参照 or フォーマット変換
puts I18n.t('hello')
# ...
puts I18n.l(Date.today)

# (後略)

上述の通り、サンプルコードは3つの段階に分けれている。

1つ目は I18n モジュール(*)の設定。 ここでは default_locale を ja にしているが、これは Rails でも config/application.rb あたりでよくやる設定だ。

2つ目はロケールファイルの読み込み。Rails では config/locales 下が自動で読み込まれているが、おそらく内部的に同様のことが行われているのだろう。

3つ目は I18n API の利用。これは Rails では view ファイルでよくやるやつで一番馴染みがある。I18n.t はローケルファイルに定義された訳文を参照しており、I18n.lはロケールファイルの定義に従ったDateオブジェクト等のフォーマット変換を行う。

一言で言えば I18n がやっていることは、
ロケールファイル(ja.yml等)をベースにした、訳文の参照(I18n.t)とフォーマット変換(I18n.l)」
だけということになる。 こうして整理してみるとやらんとしていることは本当にシンプルだな...

Rails拡張

ただ Railsで使う際には以下のような拡張がなされより使いやすくなっている。

  • Model.model_name.humanメソッドとModel.human_attribute_name(attribute)
  • エラーメッセージの自動翻訳
  • フォームラベルやエラー表示で自動的にロケールを参照

まぁこの辺は本題から逸れるので割愛する。大体 こちら に書いてある。

ついでによく一緒に使われてそうな rails-i18n(gem) は、ざっくり言うと便利なデフォルトロケールファイルを提供してくれているという理解で良さそう。I18n の機能(例えば I18n.t や I18n.l 自体)を拡張するものでは無い

参考

所感

  • これまで5年近く Rails で仕事してきたが、I18n に関しては「よくわからんがこれで動く」で済ませてきた。そんな自分が恐ろしい。
  • やってることが単純だからコードもわかりやすいかなと思って読みに行ったがそれほどではなかった。いずれリーディングも調整したい。(しない)

Rails での日付のフォーマットは、strftime による都度指定ではなく I18n で管理する

背景

Rails の veiw で日時のフォーマットを整えようとして手癖で strftime を使おうとした際に、 「そういえば strftime はあんまり良くなかったな...」 と思い出したので軽く整理。

本題

Rails での日付のフォーマットは、strftime による都度指定ではなく I18n で管理する

以下のようなイメージ

# ja.yml
ja:
  date:
    formats:
      default: '%Y-%m-%d'
  time:
    formats:
      default: '%Y-%m-%d %H:%M:%S'
      date_only: '%Y-%m-%d'
# viewファイル(erb)
<td><%= l(issue.created_at, format: :date_only) %></td>

strftime ではなく i18n で管理した方が良い理由

strftimeを利用した際は以下のような形になる。

post.created_at.strftime('%Y-%m-%d')

1箇所だけなら特に問題ないが、アプリケーション上共通してフォーマットを用いたい場合に、繰り返し '%Y-%m-%d' を指定するのは非効率で、手間や記述ミスの原因になりやすい。

locale にアプリケーションで必要なパターンを用意して、それを指定する方がスマートだよねという話。

# ja.yml(再掲)
ja:
  date:
    formats:
      default: '%Y-%m-%d'
  time:
    formats:
      default: '%Y-%m-%d %H:%M:%S'
      date_only: '%Y-%m-%d'

参考

「まだRailsでstrftime使ってるやついる?」「いねえよなぁ!!?」【日時のフォーマットをja.ymlで管理する】 #Ruby - Qiita

Rails 国際化(I18n)API - Railsガイド

所感

一瞬アレ...っとなったので記録した。こういうのすぐ忘れる... 2025年はこのレベルの内容でも気楽にブログに書き起こしていきたい気持ち。

そもそも RailsI18n よく理解してないので次はそこに触れるかな。

[読書感想] 休養学 あなたを疲れから救う

30代も後半に差し掛かり、自分のコンディション維持に意識を向けることが多くなってきた。そんな中で妻から紹介されたこの本がとても良かったので感想を書く。 疲労や休養というものに対して今まであまり向き合ってこなかった人にとてもお勧めできる本だ。

タイトル

休養学 あなたを疲れから救う

著者

片野秀樹

概要

本書では、これまで栄養や運動に比べて軽視されてきた「疲労」と「休息」について科学的な解説を加え、
・人はなぜ疲れるのか
・疲れても無理をして休まずにいると、人間の体はどうなるのか
・どんな休み方をすれば最も効果的に疲れがとれるのか
……といった疑問に答えていきます。

さらに、休養を7種類に分類し、それらを組み合わせて、自分がもっともリフレッシュできる休み方を見つける方法も伝授します。

「日本人の約8割が疲れている」というデータもあります。ただ、世界各国と比べて平均労働時間がとくに多いわけではありません。日本人は「休み下手」なのです。

本書を読んで、単に寝る、休息するといった「守りの休養」から、「攻めの休養」へ今すぐシフトしましょう!

https://www.amazon.co.jp/dp/4492047484 より

目次

  1. 日本人の8割が疲れている
  2. 科学でわかった!疲労の正体
  3. 最高の「休養」をとる7つの戦略
  4. 眠るだけでは休養にならない
  5. 新しい「休み方」を始めよう

所感

疲労とは何か?どのように引き起こされるのか?どうすれば効率よく回復できるのか?を程よく科学的に解説してくれる本。

個人的には「2. 科学でわかった!疲労の正体」が特に学びが多かった。
その章で取り上げられた内容(自律神経やサーカディアンリズム活性酸素、栄養バランスの良い食事の重要性など)については概ね知っていたが、「疲労とその回復」という一連のストーリーの中で説明されたことで、それらをひとまとまりの知識として理解させてくれた。「ああそういうことか」「そう繋がるのか」と感じる機会も多く、全体的にとても楽しく読み進められる内容だった。

自分なりにその辺りをまとめてみると以下のようになる。

  • 疲労とは「肉体的、精神的な活動の結果、本来の活動能力が下がった状態」のこと。
  • 様々な外的な刺激が疲労をもたらす。これを総称してストレスと呼ぶ。
  • ストレスを受けると、それに対処するために身体のシステムが様々な形で反応を起こす。その中でも自律神経の働きは疲労回復に対して特に重要。
  • 自律神経とは、24時間サイクルで私たちの体を最適な形に調整してくれる神経のこと。大きく分けて交感神経と副交感神経がある。
  • 疲労回復のためには夜に副交感神経が優位になることが必要だが、ストレスが要因となって過緊張の状態が続くと、副交感神経優位の状態にならず、疲労回復が適切に行われなくなる。

この本では続く「3. 最高の「休養」をとる7つの戦略」で具体的な休養方法が多く紹介されているが、その大半も「いかに自律神経を整え、疲労回復が適切に行われる状態を作るか」という点に目が向けられているように感じた。

逆に少し気になる点としては、科学的と言いつつやや裏付けに乏しく見える内容もあったり、2章の「科学的な説明」と、3章の「具体的な休養戦略」のつながりがあまり感じられなかったことが挙げられる。特に後者に関しては、2章の「科学的な説明」が刺さった私としては、3章の「具体的な休養戦略」だってそれをベースに説明してくれよ...(「活力」って2章の内容でいうと何に当たるの?)とモヤモヤするところはあった。

ただ、疲労やそのメカニズムに関してあまり知識のない人が全体像を掴むにはとても良い本ではないかなと感じる。基本的には色々な人にお勧めできる良い本だった。

「学びを結果に変えるアウトプット大全」感想

動機

  • アウトプットが全然できていないという悩み
  • ハードルを低くして習慣化したい。そのためのきっかけとなるような情報が欲しい

自分にとっての要点

  • アウトプット前提のインプットを行う
    • インプット/アウトプットの比率は 3:7 が目標
    • アウトプットをインプットの2倍にする
  • フィードバックも重要
    • フィードバック = アウトプットを評価して、次のインプットを調整し、より良いものにする
    • フィードバックを受けると言う目的でも「公開」した方が良い
    • 「誰にも読まれず、誰からも批判されない文章を書いても、文章が上達することはありません。「読まれる」という緊張感が、集中力を高め、より良い文章を書こうとする最高の刺激になります」
  • アウトプットのやり方
    • 全般
      • 期限を決めて集中して行う
        • 5分とか15分とか
        • 15分の隙間時間でアウトプット
      • 構造を決めてから書く -手段
      • 本や映画の感想をTwitter等で呟いて要約力を高める
      • 本の感想
        • before + 気づき + Todo のフォーマットが書きやすい
          • 自分だと、 動機 + 自分にとっての要点 + 気づき + Todo がしっくりくるかも
        • ブログに書くか。この記事みたいな感じで。
      • 日記
        • ポジティブなこと(今日あった嬉しいこと、楽しいことなど)を書く
        • 3行5分とかで良い
  • その他
    • ちょっと頑張ればできそうなことを繰り返す
      • これはアウトプットそのものの習慣化に関しても同様
    • イデア出しのための100均(に売ってる)カード

気づき

  • 「アウトプットを習慣化する」こと自体を目標にしてみる
    • どうすれば楽しく続けられるか?が重要
    • 今までうまくいっていなかった理由は多分…
      • 無理をしていた。良いものを出そうとしていた。
      • ジャンルを技術に絞っていた(他にもアウトプットできることはある)
      • 「アウトプットを前提とする」インプットをしていなかった
  • 極力「公開」しよう
    • このハードルをいかに下げるか?が自分にとっては重要
    • これはTwitterでハードル低く始める、というのが良いかな

Todo

  • 新しくTwitterのアカウントを作る
  • アウトプットを習慣化するために以下のうち、どれか1つを達成する。
    • Tweet1日1回
      • 公開アウトプットのハードルを下げることが目的
    • 3行日記を週3回書く
      • ポジティブ&健康のやつ
    • ブログ1ヶ月に1回更新
      • 読んだ本、Youtube、参加したイベント、とかの感想
      • コーヒーとか登山とかでも良い気がする

Rails7 + tailwindcss-railsの環境で、privateなgithubリポジトリ上の自作gemをGemfile上で参照した場合、bin/devからのアプリケーション起動で'xxx is not yet checked out. Please run `bundle install`' のエラー

かなり謎な内容なのだが...

タイトルの通り Rails7 + tailwindcss-railsの環境で、
privateなgithubリポジトリ上の自作gemをGemfile上で参照した場合に、
bin/dev(foreman)からのアプリケーション起動が 'xxx is not yet checked out. Please run bundle install' というエラーが出て失敗する。

一応回避できたが全く理由は分からない。一度回避した後は再現もしなくなってしまった。 記録のためにやったことを記載しておく。

前提

Rails7 + tailwindcss-rails

Rails7でcssをtailwindを使用する場合 tailwindcss-rails gemの利用が推奨されている。

インストールすると rails serverとbin/rails tailwindcss:watch(tailwind cssの変更監視&再コンパイル用コマンド)を別プロセスで起動するために以下のような bin/dev が作成される。

開発環境においてはbin/devを用いてアプリケーションを立ち上げることになる。
(手動で2つ立ち上げても同じことではある)

# bin/dev
exec foreman start -f Procfile.dev "$@"

# Procfile.dev
web: bundle install; bin/rails server -p 3000
css: bundle install; bin/rails tailwindcss:watch

privateなgithubリポジトリの自作gemを利用

privateなgithubリポジトリのgemを利用する方法はいくつかあるが、ここでは簡易的にsshの利用することにする。 Gemfileに対して以下のように記載する。

gem 'your_gem_name', git: 'git@github.com:your_username/your_repo.git', branch: 'your_branch'
# 事前に自分のSSHキーをGitHubに登録しておく必要がある

参考 obel.hatenablog.jp qiita.com

現象

上記2つの前提を満たした上で

つまり

の状況において、

bin/dev を用いて Railsアプリケーション(& tailwind cssの変更監視&再コンパイル)を起動しようとすると以下のようなエラーが発生した。

https://xxxxxx@github.com/XXX/my_gem.git is not yet checked out. Please run `bundle install`

この事象は以下のような前提条件のもと起きている。

  • bundle installは実施済み
  • bin/rails s と tailwindcss:watch をterminal上で個別に実行すると問題なく起動する。

自分から見ると bundle install はちゃんと出来ているはずだが...? という感じ。

回避方法

少し調べてみたが...内容が特殊なせいか今ひとつ情報が出てこない。 ChatGPTに聞いても残念ながら的を射た回答は得られなかった(質問の仕方が悪いのかもしれないが...)

ただ エラーメッセージ的には foremanの環境とターミナルの環境が別になっているように感じられる。

そこで以下のようにProcfileを修正した上でbin/devを実行することで、現象を回避することができた。

web: bundle install; bin/rails server -p 3000
css: bundle install; bin/rails tailwindcss:watch

それぞれのコマンドの前にbundle installをつけることで、foremanの環境(?)においてもbundle installの実行を保証している。

なお、一度回避した後は、Procfileから bundle installを抜いても問題なく起動するようになった。 やはり何かしら環境は存在しているように見える。foremanについて調べればこの辺わかるのかな...