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

週刊Railsウォッチ(20191216前編)Rails 6.0.2がリリース、平成Ruby会議01開催、古いRailsのfindメソッド置き換えほか

こんにちは、hachi8833です。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

今回は新しい試みとして、TechRacho記事でもお馴染みのWingdoor様が福岡エンジニアカフェで主催したつっつき会に東京からリモート接続する形で開催いたしました。Windoor様およびご参加いただいた皆さまありがとうございます!


つっつきボイス:(接続テストの後)「こういう遠隔イベントはやっぱりZoom↓が強い💪」(ご挨拶・会社紹介など)「それでは始めま〜す🔔」「よろしくお願いしま〜す🎉」「今回はモバイル開発やPHPやJSなどをやってる方もいらっしゃるようなのでそのつもりで進めます」


zoom.usより

「そういえば今度の土曜日は平成Ruby会議01ですね(注: イベントは終了しました)」「お、もう今週か😳」「そういえばなぜ平成😆」「平成生まれのRubyエンジニアが集う場ですって」「私アウト😇」「私アウト😇」「私アウト😇」「...の続きは、平成生まれだけでなく各世代で交流ですって😆」


heiseirb.github.ioより

「キーノートは@koicさんに@yui-knkさん、2スロットもあるアツいカンファレンスですね🔥」「スライドもいろいろ上がってくると思いますので楽しみ😋」


行ってまいりました。最高です❤️。スライドは以下に続々アップ中です。

臨時ニュース: Rails 6.0.2がリリース


rubyonrails.orgより

こちらはつっつきの後で出たリリースです。細かな改修のみで、セキュリティ関連の修正は見当たりませんでした。作り中のアプリで早速bundle updateしました。

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

以下から見繕いました。ドキュメントの修正が目立ちました。

class_namesヘルパーを追加

<div class="<%= class_names(active: item.for_sale?) %>">
``````diff
# actionview/lib/action_view/helpers/tag_helper.rb#L293
+ # Returns a string of class names built from +args+.
+ #
+ # ==== Examples
+ # class_names("foo", "bar")
+ # # =&gt; "foo bar"
+ # class_names({ foo: true, bar: false })
+ # # =&gt; "foo"
+ # class_names(nil, false, 123, "", "foo", { bar: true })
+ # # =&gt; "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' : '' %>
<p>">

<!--After:-->
<div class="<%= class_names(active: item.for_sale?) %>">

「他の言語でこういう書き方してたのを取り入れたのかな?🤔」「...Vue.jsでこういう書き方があったような気がします」「お、そうでしたか😳」

おたより発掘

インスタンス変数をloadに移動

インスタンス変数@records@loaded#exec_queriesではなく#loadに置くべき。#load#exec_queriesを実行し、@recordloaded?が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="\">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導入で要らなくなったのかな?🤔」「自動で探せそうな気もしますね☺️」

Rails 6 Beta2時点のZeitwerk情報(要訳)

番外: Active Recordの最適化

つっつきの後で見かけたツイートです。

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の方が楽といえば楽かも😆」

RailsでGraphQL APIをつくる: Part 1 – GraphQLとは何か(翻訳)

secret_key_baseをお漏らししないために


つっつきボイス:「RubyのMarshal.loadにユーザ入力由来の値が入ると危険、そりゃそうだ😆」「あぁなるほど、ActiveSupport::MessageVerifierActiveSupport::MessageEncryptorがデフォルトでMarshalになっているのが潜在的に危険だからデフォルトをJSONにしようねという話か」「まあ言われてみれば😆」

「今回はPHPやってる人もいますけど、PHPにも任意のオブジェクトをstringに変換する機能がありましたよね?たしかvar_dumpだったかな?」「あります」「あの辺がRubyで言うMarshalに相当します」

追記(2019/12/16)

こちら↓などの方が近そうです🙇。

tapping_device gemに新機能

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のスライドでマイクロサービス寄りの話なので上とはつながりはないかな〜☺️」「そうでした😅」「お集まりの皆さんの中でマイクロサービスで開発してる方は...?」「見事にいない😆」


kubecon-jp.connpass.comより

「マイクロサービスをきれいに作れる気がしないとか大規模なシステムでないと効果が薄いとかはありますけど、絶対に守りたい機能をマイクロサービスで固く固く作ってソースもめったに変えないとかならあってもいいかも☺️」

非推奨になった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

「信頼していいんでしょうか?😅」「まあ機械的に置換できるものならやっちゃっていいと思いますよ☺️」「思わず記事の日付確認しちゃったけど新しい記事だし😆」「Rails 2ってRuby 1.8ぐらいの頃でしょうか?」「ですね: 今は亡きRuby Enterprise Editionとか使われてた頃😆」「😆」「ソースコードの冒頭に# coding: UTF-8とか書いてた時代😆」

Ruby Enterprise Editionは2012年に終了していたんですね↓😳。

参考: Welcome — Ruby Enterprise Edition

参考: Ruby で UTF-8 なのにマジックコメントが必要なケース - Qiita

その他Rails



つっつきボイス:「↑そういえばこの情報って意外とまとまってそうでまとまってませんでしたね」「今は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、画像系コラボレーションツールほか

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

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

Rails公式ニュース

Ruby Weekly

Awesome Ruby

Hacklines

Hacklines


関連記事

CONTACT

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