- Ruby / Rails関連
週刊Railsウォッチ: Rails 7アセットパイプライン解説記事、ロジックをapp/operatorsで整理ほか(20211101前編)
こんにちは、hachi8833です。直近ですが、明日11/2(火)19:30より「大江戸Ruby会議09 出前Edition」がオンライン開催されます。Rails界隈で知られた「あの人」や「あの人」も登壇するそうです。皆さんもぜひ!
Asakusa.rb主催の大江戸Ruby会議09 出前Editionが週明け火曜の11/02 19:30より開催されます。Railsエンジニアなら一度は聞いたことのある「あの人」も登壇するとても豪華なイベントなので、まだ参加申込みしていない人はぜひ! #oedo09 https://t.co/nu9cR0EIea
— 銀座Rails (@GinzaRails) October 30, 2021
また、Kaigi on Rails 2021の全動画がYouTubeチャンネル↓で公開されました 🎉
Kaigi on Rails 2021 アーカイブ動画を公開しました!
先日、2日間に渡り開催された本編全25本の動画をYouTubeに公開しました✨
当日観逃した方、お気に入りのトークをまた観たい方、ぜひご覧ください!https://t.co/jdZmOXx10I#kaigionrails— Kaigi on Rails (@kaigionrails) October 30, 2021
🔗Rails: 先週の改修(Rails公式ニュースより)
以前の公式更新情報で拾いきれなかったものから見繕いました。
🔗 Action MailテンプレートにDOM idを追加
自分のチームでは、CapybaraとSeleniumの代わりにCypressですべてのエンドツーエンドテストをやっている。その中で、すべてのメイラーでMailer Previewのアクションがエラーなしに開いてレンダリングできることをテストしている。メイラーやメイラーのアクションがたくさんあるのでバグの早期発見に役立っている。
自分たちが直面した問題は、メールのto
やfrom
やsubject
などにある重要なdd
要素のほとんどに一意のセレクタがなくDOMで追いかけづらいというもの。このプルリクはこの問題を修正する。
私たちのCypressによるテストは以下のような感じになっている。
context('Mailer Preview', () => {
it('works for all mailer actions', () => {
cy.visit('/rails/mailers')
cy.get('li a').each($a => {
const href = $a.attr('href');
cy.log(href)
cy.visit(href)
cy.get('#from').then($dd => {
cy.log('FROM :' + $dd.get(0).innerText)
//expect($dd.get(0).innerText).to.eq('from@example.com')
})
cy.get('#to').then($dd => {
cy.log('TO: ' + $dd.get(0).innerText)
//expect($dd.get(0).innerText).to.eq('foo@bar.com')
})
cy.get('#subject').then($dd => {
cy.log('SUBJECT: ' + $dd.get(0).innerText)
//expect($dd.get(0).innerText).to.eq('Test subject')
})
cy.get('#mime_type').then($dd => {
cy.log('MIME TYPE: ' + $dd.get(0).innerText)
//expect($dd.get(0).innerText).to.eq('HTML email')
})
cy.get('[download="icon.png"]').should('exist')
})
})
})
同PRより
つっつきボイス:「Action MailテンプレートのDOM idを増やしたそうです」「そうそう、idがあるとシステムテストを書いているときにとても取りやすいんですよ」「たしかに」「順序で指定したりすると後で仕様が変わったときにハマりやすいので、これはありがたい修正👍」「自分もテストのためにこんな感じでフィールドにid
を振ったりしたことがあります↓」
<!-- railties/lib/rails/templates/rails/mailers/email.html.erb#56 -->
<dl>
<% if @email.respond_to?(:smtp_envelope_from) && Array(@email.from) != Array(@email.smtp_envelope_from) %>
<dt>SMTP-From:</dt>
- <dd><%= @email.smtp_envelope_from %></dd>
+ <dd id="smtp_from"><%= @email.smtp_envelope_from %></dd>
<% end %>
<% if @email.respond_to?(:smtp_envelope_to) && @email.to != @email.smtp_envelope_to %>
<dt>SMTP-To:</dt>
- <dd><%= @email.smtp_envelope_to %></dd>
+ <dd id="smtp_to"><%= @email.smtp_envelope_to %></dd>
<% end %>
<dt>From:</dt>
- <dd><%= @email.header['from'] %></dd>
+ <dd id="from"><%= @email.header['from'] %></dd>
<% if @email.reply_to %>
<dt>Reply-To:</dt>
- <dd><%= @email.header['reply-to'] %></dd>
+ <dd id="reply_to"><%= @email.header['reply-to'] %></dd>
<% end %>
<dt>To:</dt>
- <dd><%= @email.header['to'] %></dd>
+ <dd id="to"><%= @email.header['to'] %></dd>
<% if @email.cc %>
<dt>CC:</dt>
- <dd><%= @email.header['cc'] %></dd>
+ <dd id="cc"><%= @email.header['cc'] %></dd>
<% end %>
<dt>Date:</dt>
- <dd><%= Time.current.rfc2822 %></dd>
+ <dd id="date"><%= Time.current.rfc2822 %></dd>
<dt>Subject:</dt>
- <dd><strong><%= @email.subject %></strong></dd>
+ <dd><strong id="subject"><%= @email.subject %></strong></dd>
<% unless @email.attachments.nil? || @email.attachments.empty? %>
<dt>Attachments:</dt>
🔗 BASIC認証に無効な認証文字列が渡されたときの挙動を修正
BASIC認証で保護されているコントローラに、コロンが抜け落ちている誤った認証情報でリクエストを送信すると、以下のように
NoMethodError: undefined method 'bytesize' for nil:NilClass
エラーが発生する。
class UsersController < ApplicationController
http_basic_authenticate_with name: "king", password: "secret"
def index
render plain: "Something"
end
end
credentials=$(echo -n king secret | base64)
curl 'http://localhost:3000/users' -H "Authorization: Basic $credentials"
同PRより
つっつきボイス:「BASIC認証の認証情報が不備だった場合のエラーがこれまでNoMethodErrorだったのを、has_basic_credentials?
がfalseを返す形に修正したようです」「たしかにNoMethodErrorだと違いますよね」「今まではNoMethodErrorがhas_basic_credentials?
を突き抜けてしまっていたのか」
# actionpack/test/controller/http_basic_authentication_test.rb#115
test "has_basic_credentials? should fail with credentials without colon" do
@request.env["HTTP_AUTHORIZATION"] = "Basic #{::Base64.encode64("David Goliath")}"
assert_not ActionController::HttpAuthentication::Basic.has_basic_credentials?(@request)
end
🔗 inflectorに登録した略語を削除できるようにした
ActiveSupport::Inflector
の略語(acronym)を削除しようとすると実装が壊れ、別の略語を登録しようとするとTypeError
が発生する。
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "activesupport", require: "active_support/all"
end
ActiveSupport::Inflector.inflections do |inflect|
inflect.clear :acronyms
inflect.acronym "HTML" # => '[]=': no implicit conversion of String into Integer (TypeError)
end
これは
#clear
がinstance_variable_set "@#{scope}", []
のように@acronyms
に新しいArrayを設定していたのが原因。デフォルトの初期値はHashになっている↓。
# activesupport/lib/active_support/inflector/inflections.rb#L78-L79
def initialize
@plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {}
このプルリクでは、
ActiveSupport::Inflector::Inflections
の#clearと
#clear(:all)`を拡張して、従来できなかった略語の削除をできるようにもしておいた。
同PRより
つっつきボイス:「Inflectorは単数・複数形単語の登録や独自の固有名詞・略語の登録を行える機能ですね↓: #clear
などで削除できない問題と、その後で略語を再登録できない問題が修正された」「日本語だとあまり使わない機能だと思いますが、たしかに登録できるなら削除もできて欲しいですよね」
参考: Rails API ActiveSupport::Inflector::Inflections
# api.rubyonrails.orgより
acronym 'RESTful'
underscore 'RESTful' # => 'restful'
underscore 'RESTfulController' # => 'restful_controller'
titleize 'RESTfulController' # => 'RESTful Controller'
camelize 'restful' # => 'RESTful'
camelize 'restful_controller' # => 'RESTfulController'
acronym 'McDonald'
underscore 'McDonald' # => 'mcdonald'
camelize 'mcdonald' # => 'McDonald'
🔗 ジェネレータのCSSプロセッサリストにBootstrapとBulmaを追加
# railties/lib/rails/generators/rails/app/app_generator.rb#L265
- class_option :css, type: :string, desc: "Choose CSS processor [options: tailwind, postcss, sass]"
+ class_option :css, type: :string, desc: "Choose CSS processor [options: tailwind, bootstrap, bulma, postcss, sass... check https://github.com/rails/cssbundling-rails]"
つっつきボイス:「ci skipとあるのはだいたいドキュメント関連の改修」「そういえばCSSフレームワークのBulmaは、以前#43110でsass-railsへのデフォルト依存が削除されたときに見かけました(ウォッチ20210921)」「Bulmaは使ったことないな〜」
参考: Bulma: Free, open source, and modern CSS framework based on Flexbox
🔗 Railsガイドのスタイル改修
- PR: Close guides dropdown with Escape keypress by intrip · Pull Request #43113 · rails/rails
- PR: Fix docs spacing [ci-skip] by ChloeLiang · Pull Request #43250 · rails/rails
- PR: Change diff highlighting background for dark mode by jharrilim · Pull Request #42989 · rails/rails
つっつきボイス:「Railsガイドのスタイルにいくつか細かな修正が入っていました」「地道な修正大事ですね」
「ガイドの目次ドロップダウンを開いたらEscキーで閉じられるようにする、なるほど」「edgeガイドに反映されていました」
// guides/assets/javascripts/guides.js
document.addEventListener("keyup", function(e) {
if (e.key === "Escape" && guides.classList.contains("visible")) {
guides.classList.remove("visible");
}
});
「#43250の修正はどこだろう?」「モバイル表示の左右マージンが調整されて、フッターのmargin-bottom
がわずかに小さくなっていた↓」「diffを見る方が早いかも」
「#42989はダークモードのdiffが見づらかったのを修正」「お〜なるほど」「個人的にダークモードってどうも不要な文明感がありますけど😆」「同じく」
🔗Rails
🔗 Rails 7.0のアセットパイプライン周り解説記事
つっつきボイス:「これはいい記事でしたね👍」「歴史と現状と見通しのまとめが凄いですね」「ふわっとさせずに詳細を解説しきっていて、Simpackerにも言及しているのがさすが」
参考: Simpacker: Rails と webpack をもっとシンプルにインテグレーションしたいのです - クックパッド開発者ブログ
「この間話題にしたPropshaft(ウォッチ20211018)も、記事によるとRails 7で主要な選択肢のひとつになりそうで、Propshaftは思っていたより進んでいるんですね」「Rails 7は今はAlpha2ですが、次のAlpha3あたりでPropshaftが入るかもしれませんね」「お〜」
後で調べると、Propshaftはv7.0.0.alpha2ブランチではまだジェネレータのapp_base.rb
には取り込まれていませんでしたが、masterブランチのapp_base.rb
には取り込まれていました。
🔗 ビジネスロジックをapp/operatorsで整理(Ruby Weeklyより)
つっつきボイス:「記事ではTrailbrazer↓を使ったりしてみたけど自分に合わなかったのでapp/operatorsで整理したらしい」「Trailbrazerにも名前の似たOperationsという概念があるんですね」
「app/operatorsは前回取り上げたapp/contextsに似ている感じでしょうか?(ウォッチ20211025)」「記事冒頭でも前回のcontext記事↓を引用しているので意識はしているでしょうね」
参考: Organizing business logic in Rails with contexts
「前回のcontextsもそうでしたけど、この記事のコードに出てくる何とかOperatorもまさにGoF本で言うFacade(ファサード)ですね↓」「なるほど、名前が違う感じですか」
# 元記事より
# app/operators/invoice_operator.rb
class InvoiceOperator < BaseOperator
def update(params:)
@record.assign_attributes(params)
# do things before updating the invoice eg:
# update/create related records (like InvoiceItems)
# a few more examples:
calculate_total
calculate_vat
state = @record.save
# do your other business eg.:
# send emails,
# call external services and so on
Result.new(state: state, record: @record)
end
def create(params:)
@record.assign_attributes(params)
# do things before creating the invoice eg:
# create related records (like InvoiceItems)
# a few more examples:
calculate_total
calculate_vat
state = @record.save
# do your other business eg.:
# send emails,
# call external services and so on
Result.new(state: state, record: @record)
end
private
def new_record
Invoice.new
end
def calculate_total
# you can write the logic here,
# or call a class that handles the calculation
end
def calculate_vat
# you can write the logic here,
# or call a class that handles the calculation
end
end
「よりシンプルなパターンを作る試みは常にありますけど、実際に使ってみたときにシンプルになるとは限らないのが悩ましいところなんですよ」「もしかするとService Objectも出た当初はシンプルだと言われてたのかも」
「コントローラにActive Recordのメソッドチェーンがたくさんあるのは確かに気持ちよくないので、operatorsやcontextsでFacadeにするのもわかる: 個人的にはRailsでActive Recordが使えるのが嬉しい点だと思うので、そこまでしてActive Recordを直接触らせない形にしなくてもいいかなという気持ちが少しあります」「たしかに」「規模が大きくなればまた違ってくると思いますが」
「元記事末尾によると、このoperatorsはまだ長期のバトルテストは経てないそうです」「アプリが育ってきたときの設計は一概にどれがいいとは言えませんが、大きくする予定のないアプリなら別にoperatorsでやってもいいんじゃないかな」
🔗 RailsアプリのJSテストコードのカバレッジ(RubyFlowより)
つっつきボイス:「RailsプロジェクトにおけるJSテストのコードカバレッジの記事のようですね」「記事に出てくるIstanbulはJS製のカバレッジツールなのか↓」「simplecov(Ruby製のカバレッジツール)も使ってる」
「記事ではIstanbulで集計したJSテストカバレッジのダンプをRSpecからトリガーしているようですね↓」
# 同記事より
# spec/rails_helper.rb
RSpec.shared_context "dump JS coverage" do
after { dump_js_coverage }
end
RSpec.configure do |config|
config.include_context "dump JS coverage", type: :system
...
「JSのテストコードが多いRailsプロジェクトで使いそうなテクニックですね: もっともフロントエンドとバックエンドでリポジトリが分かれているプロジェクトだと少し工夫が必要そうですが」「あ、たしかに」
🔗 GitLab 14.4リリース
つっつきボイス:「14.4が出たのでBPS社内のGitLabもアップデートしておくかな」「DASTって何だろうと思ったらDynamic Application Security Testingなんですね」「静的なセキュリティスキャン機能ですね: 最近のGitLabはこういった機能をよく追加していますね」「なるほど」「よく見たらDASTはGitLabのUltimate版のみなのでFree版では使えないことが判明」「う、残念」
「他の機能はというと、VSCodeからのGitLabリモートリポジトリ参照」「おぉ?」「ローカルにcloneしていないプロジェクトをVSCodeから読み取り専用で参照できるようですね: おそらく動画↓で動いているVSCode拡張用のインターフェイスをGitLab側に用意したということかな」「リモートリポジトリをちょっと見たいときにローカルにcloneしなくてもVSCodeで見られるのはよさそうですね😋」
「GitLabは機能が着々と増えていますし、セキュリティパッチもちゃんと出し続けているので、出たらとりあえず当てることにしています」
🔗 その他Rails
これ、実はグローバルの rdbg コマンド経由で rails を起動させれば Gemfile に debug.gem 追加しなくてもデバッガは起動できるんだよねえ / ruby/debugのChrome Devtools連携をRailsで動かす|TechRacho by BPS株式会社 - https://t.co/VM9GcCDt0z
— バンビちゃん@実際 (@pink_bangbi) October 27, 2021
これ、動いてはいるんだけど README をよく見たら Gemfile に追加する必要があるっぽいんだけどどうなんだろう
— バンビちゃん@実際 (@pink_bangbi) October 27, 2021
つっつきボイス:「この間のruby/debug + Chrome Devtoolsの記事にアンサーが付いてたので拾いました」「そうそう、この方は明日10/29(注: つっつきは前日10/28夜でした)の銀座Rails#38に登壇されるosyoさんです」
以下は登壇後の資料ツイートです。
登壇資料と gem です #ginzarails https://t.co/XuuP9EoYN7https://t.co/4dsXsyRoZM
— バンビちゃん@実際 (@pink_bangbi) October 29, 2021
「そしてその後もrdbgについてTwitter上でosyoさんとやり取りしました↓」「あ、続きがあったんですね」「とりあえずの結論としてはbundle exec
経由だとGemfileに追加しないとrdbgが動かないけど、binstub経由ならGemfileに追加しなくてもrdbgが動くということになりました」「お〜そうだったんですね」「Ruby界隈はTwitterでつぶやくと即誰かがコメントしてくれるから便利😊」「そうそう😊」
手元で rails じゃないプロダクトで試してみたら Gemfile に gem "debug" を追加しないと
rdbg -c -- bundle exec ruby sample.rb
の形で動きませんでしたすみません…。
ただ rails のプロダクトで rdbg -c -- rails s すると利用できましたね— バンビちゃん@実際 (@pink_bangbi) October 27, 2021
rails s だけだと確かに bundle コマンドは経由していないので rails 側でなにか吸収してそうですねえ
rails s だけなら Gemfile に追加しなくても動いてそうって感じですが bundle exec 経由でなにかプロセスを実行する場合は Gemfile に追加する必要がありそう。って感じになりそうですね(多分…— バンビちゃん@実際 (@pink_bangbi) October 27, 2021
前編は以上です。
バックナンバー(2021年度第4四半期)
週刊Railsウォッチ: YJITがRuby 3.1向けにマージ、ripperのドキュメント化、crontabの罠ほか(20211026後編)
- 20211025前編 insert_allやupsert_allのタイムスタンプ自動更新、app/contextsにロジックを置くほか
- 20211019後編 ruby/debugをChromeでリモートデバッグ、Rubyアプリの最適化ほか
- 20211018前編 Railsリポジトリで進行中のPropshaft、inverse_ofを自動推論ほか
- 20211012後編 Ruby 3.1にYJITマージのプロポーザル、Rubyのmagic historyメソッド、JSのPartytownほか
- 20211011前編 ServerTimingミドルウェア追加、paramsで数値キーを許可、Railsで多要素認証ほか
- 20211006後編 ruby/debug 1.2.0リリース、Railsにはthorが入っている、tendejitほか
- 20211004前編 Rails 7でbyebugがruby/debugに変更、GitHub Codespacesをサポートほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)