Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ: Rails 7.0.2の改修内容、receipts gemでレシートを作成ほか(20220214前編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙏

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Rails: 先週の改修(Rails公式ニュースより)

今回は少し趣向を変えて、7.0.2に入った改修のうち直近かつ取り上げていなかったものを中心に見繕ってみました。

Rails 7.0.2がリリースされました

🔗 Active StorageのDirectUploadsControllerservice_nameを渡す機能がいったん外された


つっつきボイス:「そういえば7.0.2で削除された機能がありましたね」「Direct Uploadのストレージエンジンを切り替えられるservice_nameオプション機能が削除されたようですね」「この機能があると6.xからのアップグレードが難しくなってしまったので外したみたい」「完全になくなったわけではなくて、再実装が終わるまでいったん外すことにしたようです」「あ、そういうことですか」

「複数のオブジェクトストレージを切り替えながら混ぜて使うことは普通あまりないと思うので、この機能を使っている人はそんなにいないんじゃないかな」「AWSとGCPのストレージを切り替えて使うとか、たしかにあまりやらなさそうですね」

🔗 schema.rbに現在のRailsバージョンも含めるようになった

#42297以降のRailsはMySQLのdatetimeカラムのprecision(精度)を6で生成するようになった。つまり、6.1から7.0にアップグレードすると、データベーススキーマを読み込んだときに新しいprecision値が使われるようになり、productionのスキーマと合わなくなってしまう。
これを回避するため、ActiveRecord::Schemaクラスを6.1の実装でフリーズし、ActiveRecord::Migrationと同様の仕組みで他のバージョンにアクセスできるようにする。
スキーマダンパーは今後以下のようにRailsのバージョンを含む新しいフォーマットを生成するようになる。

ActiveRecord::Schema[7.0].define

関連: #43934および#43909
同PRより


つっつきボイス:「こちらはDBのスキーマファイルにRailsのバージョンを含めるようにした修正」「マイグレーションファイルの[7.0]みたいな感じでschema.rbにもバージョンを含めるんですね」

「Rails 7.0では一部のカラムのデフォルト値が少し変わったんですよ」「そういえばMySQLでprecisionが6に変わったとか書かれていますね」

./bin/rails db:setupするとschema.rbからスキーマを読み込むので、そのままだとRails 6.1からアップグレードするときにスキーマのデフォルト値が変わってしまうことがある」「あ〜そういうことですか」「./bin/rails db:migrateするのであれば、マイグレーションファイルに含まれているRailsのバージョン情報を使って適切に移行できるんですが、./bin/rails db:setupだとマイグレーションファイルを参照せずにschema.rbから直接スキーマを読み込むので、schema.rbにもRailsのバージョンを付加して適切なデフォルト値でスキーマを読み込むようにしたということですね」「なるほど」

🔗 関連付けにリフレクションがない場合のエラー表示が改善


つっつきボイス:「これはエラー表示の改善ですね: 元はテーブル名しか表示されなかったのがモデル名や関連付け名を表示するようになった↓」「NoMethodErrorArgumentErrorエラーに変わったんですね」「情報が足りないと何が起こっているのかわからないことがよくある」「そうそう」「エラーメッセージが親切なのは大事👍」

# 改修前
Post.where.associated(:cars).to_a # Post does not have association named `cars`

=> NoMethodError: undefined method `table_name' for nil:NilClass
# 改修後
Post.where.associated(:cars).to_a
=> ArgumentError: An association named `:cars` does not exist on the model `Post`.

🔗 recordという名前の関連付けを保存するとエラーになるのを修正

recordという名前に関連付けられているリレーションを持つモデル(ポリモーフィックなものなど)のレコードを保存すると、record_changed?belongs_to :recordで上書きされるためActive Recordでエラーが発生する。
同PRより


つっつきボイス:「recordという名前が使われているとエラーになっていたそうです」「なるほど、Active Recordが動的に生やしたrecord_changed?が誤って動いてしまうので、_record_changed?のようにアンダースコアを付けて修正したんですね↓」

# activerecord/lib/active_record/autosave_association.rb#L464
-     def record_changed?(reflection, record, key)
+     def _record_changed?(reflection, record, key)
        record.new_record? ||
          association_foreign_key_changed?(reflection, record, key) ||
          record.will_save_change_to_attribute?(reflection.foreign_key)
      end

recordという名前はいかにもダメな名前っぽいけど、現実に使われる可能性はあるかも」「たしかに」「自分もつい最近、さんざん悩んだ末に仕方なくdateというカラム名を付けました」「何とか_atにしづらい日付なんですね」

🔗Rails

🔗 GitHub CodeQLにRubyコードのサポートが追加(Ruby Weeklyより)

github/codeql - GitHub


つっつきボイス:「CodeQLはGitHubのセキュリティチェック機能だそうです」「お〜、図を見た感じでは、QL Compilerで中間言語的なものを生成して、コードから抽出したデータベースクエリをそれで評価するみたい↓」


同記事より

「Rubyコードのパーサーにはtree-sitterというものを使っている↓」「パーサーで取り出したコードをRDBに保存しているのか、これは面白い」

参考: Tree-sitter|Introduction -- Ruby以外にもいろんな言語向けのラッパーがあります

tree-sitter/ruby-tree-sitter - GitHub


「記事の話と直接関係ないんですが、CodeQLを眺めているうちにノイマン型アーキテクチャvsハーバードアーキテクチャという図式をちょっと連想しました」「ハーバードアーキテクチャはデータとコードが別腹になるヤツでしたっけ」「CodeQLがやっているように、やろうと思えばノイマンアーキテクチャ的にデータストレージにコードを置いたっていいんだよなと改めて思った次第です」「すごい発想」

参考: ノイマン型 - Wikipedia
参考: ハーバード・アーキテクチャ - Wikipedia

🔗 Liquidタグで動的コンテンツ表示(Ruby Weeklyより)


つっつきボイス:「Liquidって何だろうと思ったら、何かの静的サイトジェネレータでLiquidが使われてたのを少し触った覚えがあります」「Liquidはいわゆるテンプレートエンジンで↓、Shopifyが作ったものですね」「Shopifyはこういうのも作ってるんですね」

参考: Liquid template language

Shopify/liquid - GitHub

「ちょっと懐かしい感じのテンプレートエンジン記法↓」

# 元記事より
liquid("Hi {{ missing_value }}", context: {})
#=> "Hi \{\{ missing_value }}"

liquid("Hi {{ foo", context: {})
#=> "Hi \{\{ foo"

「元記事のテンプレートを見ていてSmartyをちょっと思い出しました」「それ、今まったく同じこと思いました😆」「Smartyとは?」「相当昔からあるPHPのテンプレートエンジンですね」「PHPやってたのでSmarty懐かしいです、今も現役なのか」

参考: Smarty マニュアル | Smarty

🔗 receipts: Railsで領収書やインボイスのPDFを手軽に作成(Ruby Weeklyより)

excid3/receipts - GitHub


つっつきボイス:「領収書やインボイスを作成するからreceiptsというそのまんまの名前」「Railsエンジンではなく単なるgemなんですね」

「PDF生成にはprawnを使っているらしい↓」

prawnpdf/prawn - GitHub

「書式のカスタマイズやi18nにも対応しているようですし↓、この書式ベースでいいなら使ってみてもいいんじゃないかな」「領収書やインボイスの書式はそんなに大きく変わらなさそうですよね」「ただ国によって税制なども異なるし、特に日本の業務案件だとgemの書式そのままでOKということはまずありませんけどね」「そこなんですよね...」「PDF生成みたいな機能は同じ言語と商慣習を共有している国で作られたものが安心感ある」

# 同リポジトリより
line_items: [
  [I18n.t("receipts.date"),           created_at.to_s],
  [I18n.t("receipts.product"), "GoRails"],
  [I18n.t("receipts.transaction"), uuid]
]

🔗 paralines: スレッドをわかりやすく表示 (Ruby Weeklyより)

Inversion-des/paralines - GitHub


つっつきボイス:「パララインズ?」「お〜READMEの動画↓を見たら一発で理解できた: かっこいい👍」「見せ方がうまいですね」「1行1スレッド的に表示している感じ」


同リポジトリより

「しかもコードはたった170行ですって」「できてから1か月も経ってないからすごく新しい」
「ところでこのRubyコードはTabでインデントされていますね」「スペースのインデントじゃないのか」「これは珍しい」

「デモ画像で凄いと思えるのがいい👍」

🔗 ViewComponentsとTurbo-Streamingを組み合わせる(Hacklinesより)


つっつきボイス:「thoughtbotの記事です: Turbo-StreamingとViewComponentsは割と仲がよさそうかなと思って取り上げてみました」

github/view_component - GitHub

「なるべくRubyで書きたいならViewComponentsでやってみてもいいかもしれませんね」「なるほど、ViewComponentsはどこかで試してみたい気持ちがちょっとあります」「自分は最終的にHTMLを出力するならRubyで書かなくてもいいのではという気持ちがありますけどね」「それわかります」

# 同記事より
--- a/app/controllers/messages_controller.rb
+++ b/app/controllers/messages_controller.rb

def create
- @message = Message.create(body: params[:body])
+ @message = Message.new(body: params[:body])
+
+ if @message.save
+   Broadcast::Message.append(message: @message)
+ end

  respond_to do |format|
    format.html { redirect_to messages_path }
    format.turbo_stream
  end
end

参考: ViewComponent を試してみた

🔗 Rails 6->Rails 7アップグレード

つっつきボイス:「ぼくの記事だ〜」「アップグレードお疲れさまです」「JavaScriptがなかなか動かなくて書くのに2週間ぐらいかかっちゃいました」

「turbo-railsがちゃんとpinされてる」「pinするだけでJSのコードがCDNからやってくるのってちょっと感動ですよね」「そうそう」

# 同記事より
# config/importmap.rb
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true

# turboはhotwiredのjsにpinづけされてる。
# こうして名前と呼び出し元をマッピングすることでapplication.js内でimportするだけで呼び出しが可能になる。

「importmap-railsだとデプロイが軽くなるのがうれしいですね: Bootstrapもプリコンパイル済みのJSコードでよければimportmap-railsでpinできますし」「最初私もそれでRails 7とimportmap-railsでBootstrapのpinを試してみたらできたんですが、Bootstrapを拡張しようとしたらやっぱりjsbundling-rails経由でビルドが必要みたいです😢」「そうそう、Sassの変数が絡んでくるものがあると無理」「まさにそれだったので、最終的にBootstrapをやめてtailwindcss-railsに宗旨変えしました」「Bootstrapで変数をカスタマイズし始めるとつらくなりがちなので、それでいいんじゃないでしょうか☺️」「変数カスタマイズはつらいですよね」


「ところで、importmapって表記ゆれが甚だしいですよね、import-mapsだったりImport mapsだったり」「そうそう、公式の表記も今ひとつわからないので、自分はimportmap-railsのgem名で揃えることがよくあります」「最近はimportmapと書くのが多いような気もするけどよくわからない」「やべ、自分も表記ゆれ直しておこうっと(直しました)」

WICG/import-maps - GitHub

その後で続きの記事も公開されました↓

参考: Rails7にしたあとRuby3.1 + Yjit & Rails7.0.2にアップグレードする | srockstyle


「これはjnchitoさんの言うとおりで、production環境でconfig.consider_all_requests_local = trueにするのは絶対やめるべき」「そんなことをしたらデバッグコンソールからいろいろ見えちゃう」「経験の少ない人がproductionのデバッグでそうしたくなる気持ちはわからなくもないけど、envが露出したりしたら本気で危ない」

「どのフレームワークでもデバッグコンソールは非常に強力なので、productionでさらしてはいけない」「そうそう」「慣れないうちは程度の差はあれ一度はこのようなことをやってしまいがちですが、しないに越したことはありません」


itexamplespecifyの使い分けか」「どちらかというと英語圏での利便性っぽいですね」「itsubjectがあることを前提にした代名詞的な使い方で、exampleはケースを列挙するときに書くとか、specifyは結果に名前を付けるとか、原則論的にはそういう感じかな」

「自分はRSpecのそういう項目は日本語で書いてます」「自分もそうしてますし、特に日本語で書くなら別にitでいいと思います」「ですよね、itなら短いし」「failして表示されればどれでも同じですから」

「記事を見ると、itは英語で書いてexampleは日本語で書くという流派もあるらしいけど、見たことないかも」「そんなローカルルールがあるんですか」


「ところでこんな感じの書き方↓って、昔BDD(ビヘイビア駆動開発)の流れでテストを自然言語に近い形で書くのが流行ったときにcucumberやturnipでやってたりしましたね」「そういえばturnipはちょっと使ってみたけど結局やめました」

# 同記事より
RSpec.configure do |config|
  config.alias_example_group_to :次の仕様を記述する
  config.alias_example_to :次のような振る舞いを持つこと
end

次の仕様を記述する "四則演算" do
  次のような振る舞いを持つこと "1足す1は2である" do
    expect(1 + 1).to eq 2
  end
end

参考: ビヘイビア駆動開発 - Wikipedia


前編は以上です。

バックナンバー(2022年度第1四半期)

週刊Railsウォッチ: Rubygems.orgのAPIキーに権限スコープが追加、RailsのDBパフォーマンス改善ほか(20220209後編)

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

Ruby Weekly


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。