こんにちは、hachi8833です。
- 各記事冒頭にはでパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です
- 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください
今回は新しい試みとして、TechRacho記事でもお馴染みのWingdoor様が福岡エンジニアカフェで主催したつっつき会に東京からリモート接続する形で開催いたしました。Windoor様およびご参加いただいた皆さまありがとうございます!
つっつきボイス:(接続テストの後)「こういう遠隔イベントはやっぱりZoom↓が強い」(ご挨拶・会社紹介など)「それでは始めま〜す」「よろしくお願いしま〜す」「今回はモバイル開発やPHPやJSなどをやってる方もいらっしゃるようなのでそのつもりで進めます」
「そういえば今度の土曜日は平成Ruby会議01ですね(注: イベントは終了しました)」「お、もう今週か」「そういえばなぜ平成」「平成生まれのRubyエンジニアが集う場ですって」「私アウト」「私アウト」「私アウト」「…の続きは、平成生まれだけでなく各世代で交流ですって」
「キーノートは@koicさんに@yui-knkさん、2スロットもあるアツいカンファレンスですね」「スライドもいろいろ上がってくると思いますので楽しみ」
行ってまいりました。最高です。スライドは以下に続々アップ中です。
臨時ニュース: Rails 6.0.2がリリース
こちらはつっつきの後で出たリリースです。細かな改修のみで、セキュリティ関連の修正は見当たりませんでした。作り中のアプリで早速bundle update
しました。
Rails: 先週の改修(Rails公式ニュースより)
以下から見繕いました。ドキュメントの修正が目立ちました。
class_names
ヘルパーを追加
<div class="<%= class_names(active: item.for_sale?) %>">
# actionview/lib/action_view/helpers/tag_helper.rb#L293
+ # Returns a string of class names built from +args+.
+ #
+ # ==== Examples
+ # class_names("foo", "bar")
+ # # => "foo bar"
+ # class_names({ foo: true, bar: false })
+ # # => "foo"
+ # class_names(nil, false, 123, "", "foo", { bar: true })
+ # # => "foo bar"
+ def class_names(*args)
+ safe_join(build_tag_values(*args), " ")
+ end
つっつきボイス:「class_name
?」「これで何するんだろ?」「これはCSSのクラスでは?」「あ〜Rubyのクラスではなかったか」「ハッシュの値がtrueだったらキーをCSSのクラスとして出力するとかができるようになったのね」「元の書き方とどっちが読みやすいかはちと微妙ですけど」
<!--Before:-->
<div class="<%= item.for_sale? ? 'active' : '' %>">
<!--After:-->
<div class="<%= class_names(active: item.for_sale?) %>">
「他の言語でこういう書き方してたのを取り入れたのかな?」「…Vue.jsでこういう書き方があったような気がします」「お、そうでしたか」
おたより発掘
class_namesヘルパーやっと入ったのか…npmのclassnamesライブラリとほとんど同じ挙動っぽいね。React使うときにめっちゃ重宝するライブラリだからReact on Railsなプロジェクトで役立ちそう…でもないか所詮はActionView
Rails 6.0.2がリリース – https://t.co/RxqEAfIz2T
— Jaga Apple (@jagaapple_tech) December 16, 2019
インスタンス変数をload
に移動
インスタンス変数
@records
と@loaded
は#exec_queries
ではなく#load
に置くべき。#load
は#exec_queries
を実行し、@record
はloaded?
がtrueの場合しか代入されず、@loaded
は#loaded?
がtrueの場合しか設定されず、どっちみち#load
で代入すべきなのでこの方が明快。なおこれはとある内部gemでインスタンス変数が代入される場所がおかしかったことで気づいた。
同PRより
# activerecord/lib/active_record/relation.rb#L628
def load(&block)
- exec_queries(&block) unless loaded?
+ unless loaded?
+ @records = exec_queries(&block)
+ @loaded = true
+ end
self
end
...
def exec_queries(&block)
skip_query_cache_if_necessary do
- @records =
+ records =
if where_clause.contradiction?
[]
elsif eager_loading?
@@ -826,12 +829,11 @@ def exec_queries(&block)
klass.find_by_sql(arel, &block).freeze
end
- preload_associations(@records) unless skip_preloading_value
+ preload_associations(records) unless skip_preloading_value
- @records.each(&:readonly!) if readonly_value
+ records.each(&:readonly!) if readonly_value
- @loaded = true
- @records
+ records
end
end
つっつきボイス:「これはリファクタリングだなってわかりました」「インスタンス変数のメモ化周りを整理したということでしょうね」
Active Jobにdefault_retry_jitter
設定を追加
config.active_job.default_retry_jitter
は、失敗したジョブをリトライする際に算出されるdelay timeに”jitter”(ランダムなオフセット値)を適用する。デフォルトは0.15。
guides/source/configuring.mdより大意
つっつきボイス:「おぉジッターじゃないですか: ジッターって自分で設定するものなんだろか?」「デフォルト値があってカスタマイズもできるみたいですね」
「お集まりの皆さんはジッター(jitter)ってわかります?」「電気方面で見たことある気がします」「ネットワーク用語などでは『ばらつき』みたいなネガティブな意味で使われるんですけど、ここではActive Jobのリトライのジッターなので違うニュアンスですね」「おぉ?」「たとえばジョブのリトライを5秒待つ場合、普通は5秒きっかり待つんですけど、それに1秒のジッターを与えるということは待ち時間を4秒から6秒の間でばらつかせるということだと思います」
参考: ジッター - Wikipedia
「ここはある程度想像ですけど、ジッターを与える理由があるとすれば、ワーカーがたくさんあってそれらが同時に失敗した後リトライがまったく同時に発生してしまうと負荷が集中してしまうので、ジッターを与えることで負荷をそこそこ分散させる、なんてのが考えられますね」「なるほど!」「特にサーバー負荷が高いせいでジョブが同時にたくさん失敗した場合は、ジッターがないとリトライのタイミングが揃いすぎてまた同じことが繰り返し起きてしまいますので」「ならしたいというか均質化したいというか」「みんな出口に同時に押し寄せると詰まるから適度に譲り合うみたいな」
「ジッターがゼロだと永遠にリトライが終わらないことがあるかもしれないので、そういう場合にジッターを付ければ、たまたま最初にリトライしたジョブが成功して、ジッターの設定次第ですがジョブが少しずつでも片付いていくでしょうね: あえて設定するかどうかは別ですが」
jitterはもともと「神経過敏でピリピリする」といったニュアンスのようです。
ジッターの連想でMichel Legrandアレンジの「Jitterbug Waltz」です。Jitterbugと「ジルバ」が同じものだと知ってびっくりした覚えがあります。
入力値が正しくならない問題のリグレッションを修正
hidden_field_tag('token', value: [1, 2, 3])
↓
<input type="hidden" value="">
# actionview/lib/action_view/helpers/tag_helper.rb#L94
def tag_option(key, value, escape)
case value
when Array, Hash
- value = build_tag_values(value)
+ value = build_tag_values(value) if key.to_s == "class"
value = escape ? safe_join(value, " ") : value.join(" ")
else
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
end
value = value.gsub('"', """) if value.include?('"')
%(#{key}="#{value}")
end
private
def build_tag_values(*args)
tag_values = []
args.each do |tag_value|
case tag_value
- when String
- tag_values << tag_value if tag_value.present?
when Hash
tag_value.each do |key, val|
tag_values << key if val
end
when Array
tag_values << build_tag_values(*tag_value).presence
+ else
+ tag_values << tag_value.to_s if tag_value.present?
end
end
tag_values.compact.flatten
end
つっつきボイス:「リグレッションを修正したようです」「hidden fieldのvalue: [1, 2, 3]
が消える…だと?」「valueにarrayを渡すなんてことがあるとは」
「arrayを渡すとどうなるのが正解なんでしょうね?」「テストを見ると本来はスペース区切りになるのか↓」「そうでしたか!」「まあどうせ文字列になりますし」「自分ならこういう使い方はまずやりませんけど、そういう挙動なんだへぇ〜という気持ち」
# actionview/test/template/tag_helper_test.rb#283
str = content_tag("p", "limelight", class: [1, 2, 3])
assert_equal "<p class=\"1 2 3\">limelight</p>", str
データベースURLで=
をサポート
=
はPostgreSQLコネクションでデータベースURLフォーマットを用いてoptions
を渡すのに必要。
同PRより大意
つっつきボイス:「ぽすぐれ対応みたいです」「なるほど、こういうoptions=
でオプションを渡せるようになったと」
# 同PRより
# config/database.yml
development:
url: postgresql://localhost/railsdevapp_development?options=-cmysetting.debug=on
「以前も話題にしましたけど、postgresql://
みたいな書き方ってデータベースごとに独自過ぎますよね」「オレオレ度高い」
番外: 不要なrequire
を削除
# activesupport/lib/active_support/callbacks.rb#L3
require "active_support/concern"
require "active_support/descendants_tracker"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/class/attribute"
- require "active_support/core_ext/kernel/reporting"
- require "active_support/core_ext/kernel/singleton_class"
require "active_support/core_ext/string/filters"
require "thread"
つっつきボイス:「とてもささやかな修正ですが年末お掃除感あったので」「最近地味に進められている、不要なrequire
を消すヤツですね」「想像ですけどZeitwerk導入で要らなくなったのかな?」「自動で探せそうな気もしますね」
番外: Active Recordの最適化
つっつきの後で見かけたツイートです。
重要なタスクの進捗が出て気持ちが軽くなったのでひさしぶりにアクティブレコードはやくするパッチ書いたわhttps://t.co/dFu3s2bpbc
— Ryuta Kamizono (@kamipo) December 13, 2019
Rails
RailsでネストしたAPIパラメータのバリデーションをActiveModel::Validations
でやってみた(Awesome Rubyより)
つっつきボイス:「ネストしたJSONパラメータのバリデータをこんな感じに作ってみたという記事っぽい」
# 同記事より
# lib/affiliate/lead/params_validation/main.rb
module Affiliate
module Lead
module ParamsValidation
class Main < Base
validate :validate_customer
private
def validate_customer
customer_validator = Customer.new(data[:customer])
return if customer_validator.valid?
add_nested_errors_for(:customer, customer_validator)
end
end
end
end
end
# lib/affiliate/lead/params_validation/customer.rb
module Affiliate
module Lead
module ParamsValidation
class Customer < Base
validates_presence_of :name
validate :validate_address
private
def validate_address
address_validator = Address.new(data[:address])
return if address_validator.valid?
add_nested_errors_for(:address, address_validator)
end
end
end
end
end
# lib/affiliate/lead/params_validation/customer/address.rb
module Affiliate
module Lead
module ParamsValidation
class Customer
class Address < Base
validates_presence_of :postal_code
end
end
end
end
end
「こういうのってSwaggerというか最近だとOpenAPIとか使ってやるのかな?(ウォッチ20180806)」「同じ記事でgrape↓もおすすめされていて、ウォッチでもちらっとだけ取り上げたことがあったのを思い出しました(ウォッチ20170804)」「grapeはかなり昔からRailsで使われてますね」「APIやるgemなのか」「今はRailsもAPIモードありますけど」「grapeの定義の仕方は個人的にちょい苦手」
「今回モバイル系の方もいらっしゃるのでおたずねしますけど、クライアントとサーバーの間の仕様の握りって何を使ってます?Excel仕様書?」「Excel仕様書もあるにはあります」「うちはGraphQLとか」
「GraphQLでこういうネストの深いJSONとか複雑なデータ構造を扱うときってつらくなったりします?」「印刷して収まる範囲なら何とか」「シンプルなAPIならGraphQLでさくっとできそうですね」「GraphQLだとクライアントが投げるクエリによってはしれっと重くなったりしません?」「まあありますね」「インデックス張ってないところを取ろうとしたときとか」「クライアントは今のところそんなに重くないんで何とかなってます」
「GraphQLの記事はひところに比べて落ち着いた印象ありますけど」「観測範囲では使われているところでは使われていますね」「まあちょろいSELECTクエリのために実装してAPI仕様を渡すよりはGraphQLの方が楽といえば楽かも」
secret_key_base
をお漏らししないために
secret_key_base が漏洩したときに備えてMarshalをJSONなどに切り替える。漏洩した/しそうな場合にすぐに変更できるようにしておく。 / タイトルから中身が想像できないから損してそう / 1件のコメント https://t.co/05RRZhwD9H “Railsの上で走る – ooooooo_qの日記” https://t.co/pjoG1upiJg
— (@tkmkg8m) December 11, 2019
つっつきボイス:「RubyのMarshal.load
にユーザ入力由来の値が入ると危険、そりゃそうだ」「あぁなるほど、ActiveSupport::MessageVerifier
とActiveSupport::MessageEncryptor
がデフォルトでMarshalになっているのが潜在的に危険だからデフォルトをJSONにしようねという話か」「まあ言われてみれば」
「今回はPHPやってる人もいますけど、PHPにも任意のオブジェクトをstringに変換する機能がありましたよね?たしかvar_dump
だったかな?」「あります」「あの辺がRubyで言うMarshalに相当します」
追記(2019/12/16)
こちら↓などの方が近そうです。
tapping_device gemに新機能
- 元記事: Want to know more about your Rails app? Tap on your objects! - DEV Community
- リポジトリ: st0012/tapping_device: A gem that helps you tracking method calls
tap_on!
とtap_sql!
です。
# 同記事より
def index
@posts = Post.all
tap_on!(@posts) do |payload|
puts(payload.method_name_and_location)
end
end
# 同記事より
tap_sql!(@posts, exclude_by_paths: [/activerecord/]) do |payload|
puts("Method `#{payload.method_name}` generates sql: #{payload.sql}")
puts(" From #{payload.filepath}:#{payload.line_number}")
end
つっつきボイス:「こちらは私の友だちのst0012さんが最近熱心にメンテしている、主にRailsを想定したRubyのデバッグ用gemで、それに新機能を足したという記事です」
「tap_sql!
はSQLを生成したタイミングで取れるみたいな?」「そんな感じです」
Method `count` generates sql: SELECT COUNT(*) FROM "posts"
From /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:3
Method `each` generates sql: SELECT "posts".* FROM "posts"
From /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:16
Method `count` generates sql: SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = ?
From /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:31
「tap_on!
はメソッドがどこで呼ばれたかを追えるようです」「ほほぉ」
Method: :eager_load_values, line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/relation.rb:668
Method: :includes_values, line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/relation.rb:669
Method: :eager_loading?, line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/relation/calculations.rb:226
Method: :includes_values, line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/relation/calculations.rb:226
Method: :has_include?, line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/relation/calculations.rb:128
Method: :distinct_value, line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/relation/calculations.rb:234
「新機能の実装がえらくしんどいと言ってました↓」「こういうふうにオブジェクトに触られた瞬間を全部追いかけたいと思ったらこういうgemを使うしかないですね〜」
実装をどっちに置くか
つっつきボイス:「この記事はRails以外でも通用する感じですね」「永遠の課題というか」「モバイルの人たちにも関係する話だ」
「最近だとAPIサーバー的な、データを処理するロジックだけサーバーでやって、後はクライアントでやるというのが世の中的に増えてきてる感じですが、モバイルの方から見ていかがでしょう?」「記事ではサーバーに寄せる方針でやってるようですね」「実はサーバーもクライアントも一人で作っているのであれば、この辺は割とどうでもよかったりする」「たしかに」
「ただね〜、バックエンドとフロントエンドを別の会社が作って連携するとなるとね…特にiOSとAndroidも別会社だったりするとなおさら」「」「そういう状況でサーバー側に機能を寄せるといろいろつらくなりがちなので、サーバーはAPIに専念する方がマシという気持ちにはなる」「そこら辺は案件によると言ってしまえばそれまでですが、割とサーバーよりはクライアント側で頑張る感じかなと」「なるほど」
「サーバー側で頑張るならいっそWebViewにしちゃうのがいいかなって思ったり」「ruby-jp Slackでもその辺が話題になってたみたいですね」「ある意味永遠のテーマ」「モデルをいくつも使うような複雑なバリデーションならサーバー側に寄せたいし」「iOSとAndroidが両方あるとそれぞれにロジック書くのはつらいし」「iOSとAndroidで表示がぶれるとイヤですね〜」「そこはクライアント側で頑張って欲しいですけど」
参考: WebViewとネイティブのメリットデメリット - Qiita
「以下はruby-jp Slackで見かけた別のスライドですが、Monomicrolithという異様な言葉が気になりました」「これはKubeConのスライドでマイクロサービス寄りの話なので上とはつながりはないかな〜」「そうでした」「お集まりの皆さんの中でマイクロサービスで開発してる方は…?」「見事にいない」
「マイクロサービスをきれいに作れる気がしないとか大規模なシステムでないと効果が薄いとかはありますけど、絶対に守りたい機能をマイクロサービスで固く固く作ってソースもめったに変えないとかならあってもいいかも」
非推奨になったfind系メソッドを殺して回る(Hacklinesより)
つっつきボイス:「おぉ〜超なつかしい書き方↓、久しぶりに見た」「」「Rails 2の頃のfindメソッドですけど、自分Rails 3からだったので自分から書いたことありませんし(古いRailsで仕方なしに書いたことはありますけどっ)」
# 同記事より
Post.find(:all, conditions: { published_on: 2.weeks.ago }, limit: 5 ...)
「Synvertというgemを使って古い記法を修正したそうです↓」「へぇ〜こうやって変換してくれるんだ」「そういえばウォッチでも扱ったかな(ウォッチ20170908)」「AST解析してやってくれるのね」
# 同記事より
# Handles find with hash options
$ synvert -r rails/convert_models_2_3_to_3_0
# Handles dynamic finders
$ synvert -r rails/convert_dynamic_finders
- リポジトリ: xinminlabs/synvert: synvert is used to convert ruby code to better syntax.
- サイト: Documentation · Synvert
「信頼していいんでしょうか?」「まあ機械的に置換できるものならやっちゃっていいと思いますよ」「思わず記事の日付確認しちゃったけど新しい記事だし」「Rails 2ってRuby 1.8ぐらいの頃でしょうか?」「ですね: 今は亡きRuby Enterprise Editionとか使われてた頃」「」「ソースコードの冒頭に# coding: UTF-8
とか書いてた時代」
Ruby Enterprise Editionは2012年に終了していたんですね↓。
参考: Welcome — Ruby Enterprise Edition
参考: Ruby で UTF-8 なのにマジックコメントが必要なケース - Qiita
その他Rails
- 元記事: Best Resources to Learn Ruby on Rails in 2020 – The Phrase (formerly PhraseApp) Blog(Hacklinesより)
- 元記事: RubyMine 2019.3 Released: RuboCop Severities Mapping, Better Run Anything, and More | RubyMine Blog(Ruby Weeklyより)
- 元記事: A Q&A with Eileen Uchitelle of GitHub — RailsコアコミッターEileen Uchitelleインタビュー(Ruby Weeklyより)
へぇー!
Railsの歴史的に
bundle exec (Rails 4.1 以前)
bin/rake (Rails 5.0 以前)
bin/rails (Rails 5.0 以降)なんだ!!!
rake はちょっと古そうって思ってたけど、bundle exec は最新コマンドだと思ってた
— Yuppy Rails (@Yuppyhappytoyou) December 10, 2019
つっつきボイス:「↑そういえばこの情報って意外とまとまってそうでまとまってませんでしたね」「今はbin/rails
が正というか推奨」「でも今でもbundle exec
って書きますけどっ」
「binstubっていう、プロジェクトのbin/
の下のコマンドは、以前はGitにコミットしないでくれということになってて.gitignoreにも登録されてたんですけど、Rails 5ぐらいからbinstubもプロジェクトにコミットすべきという話が出てコミットされるようになりましたね」
binstubについてピンポイントの情報が見つからなかったので、やや近いStack Overflowを貼ります。
参考: Should I add the Rails 4 bin/ directory to git? - Stack Overflow
前編は以上です。
バックナンバー(2019年度第4四半期)
週刊Railsウォッチ(20191210後編)Ruby 2.7の変更点記事、mrubyで動くmitamae、画像系コラボレーションツールほか
- 20191209前編 Pumaのphased-restartとUnicornのgraceful restart、RailsのTZハックが不要になった話ほか
- 20191204後編 Rubyコードをトランスパイルするruby-next、Cloud Run正式リリース、2019年Web年鑑レポート、V言語ほか
- 20191202前編 Rails 6のimplicit_order_columnはカスタマイズ可能、rubocop-rails 2.4.0リリース、Capistrano記事ほか
- 20191119後編 メソッド参照演算子が廃止、GitHub新機能続々、平成Ruby会議、GitHub OAuthバイパスほか
- 20191118前編 ActiveJob引数のログ抑制、RailsガイドProプランお試し、ファイルアップロードのレジュームgemほか
- 20191112後編 invisible gemで可視性を変えずにパッチ当て、スライド:「型なし言語のための型」、自然言語の言語名を推測ほか
- 20191111前編 Active Recordモデルをprivateで封じ込める、心折れないRailsスキーマ管理、Railsセッションをクロスドメイン共有ほか
- 20191106後編 holiday_japan gemで日本の祝日判定、小さい関数が有害になるとき、Gitブランチのファジー検索ほか
- 20191105前編 Rails 6のデフォルト設定解説、DHHも消したいaccepts_nested_attributes_for、スライド『実践Railsアプリケーション設計』ほか
- 20191029後編 Ruby 2.7.0-preview2、tapping_device gemとhumanize gem、平成Ruby会議ほか
- 20191028前編 RailsにSTI用メソッドsti_class_forとpolymorphic_class_forが追加、RuboCopを変更箇所だけにかけるgem、strftime書式生成サイトほか
- 20191021 Rails 6でhas_many関連の修正やSprockets 4.0対応、Shrine 3.0がリリース、Minitestスタイルガイドほか
- 20191015 スライド「Rails Performance issues and Solutions」を見る、dirtyに*_previously_was が追加、Sidekiq 6.0.1ほか
- 20191008後編 Ruby 2.7のInteger#[]でバイナリチェック、rubyzip gemは強力、13KBのJavaScriptゲームほか
- 20191001後編 RedisとRubyをつなぐredis-object gem、Fullstaq Rubyの新バージョン、COUNT(*)とCOUNT(1)の速度ほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp Slackなど)です。