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

週刊Railsウォッチ(20200601前編)Active Recordに新機能「delegated typing」追加、RuboCopのデフォルト設定アンケートほか

こんにちは、hachi8833です。この記事↓にどよめきました👍🎉😂。

オープニングつっつき: 経産省のnpmモジュールが素晴らしい


つっつきボイス:「そうそう、経産省がnpmモジュール出してましたね」「個人的にめちゃ感動しました」「やだこれ素晴らしいじゃないですか!」「ようやく登場した感」「住所の正規化が公式の方法でできるのはいいですよね」

「これまでは住所の正規化が民間でまちまちだったから、これが正しい住所だという一致チェックがなかなかできませんでしたし」「そうなんですよ」「これ使えば今滞りまくっている特別給付金の支払い作業もはかどるでしょう、きっと」「ちゃんと使われればですけど」「こういう公式のものがちゃんとあれば使う人は割といるはずですし」

「個人的に1つ注文あります: 住所のふりがな表記とローマ字表記のフィールドもぜひ欲しいです😭」「あ〜たしかに」「ふりがなやローマ字表記がないと読みを正確に翻訳できないんですよ」「住所のふりがなって、まったく同じ地域で同じ漢字なのにふりがなが違うことすらありますよね」「そこが問題なんです」「役所の書類上ではこう読むけど地元の人はこう読む、みたいなこともありそうですよね」

「この手の情報で公式にかろうじて近いものといえば日本郵便のKEN_ALL.csvぐらいしかありませんでしたよね↓」「そうそう、頑張ってKEN_ALL.csv使ってました」「自分も」「郵政省の頃からですからKEN_ALL.csvの時代どんだけ長かったのかと」

参考: 郵便番号データダウンロード - 日本郵便

「そういえば経産省のnpmには郵便番号のフィールドがありませんね👀」「ほんとだ」「郵便番号データベースってすべての住所番地を網羅してるわけではなくて、『その他』とかで丸められてるところが多数あるんですよ」「そうそう、私書箱とかもありますし、管理が別なんでしょう」「まあ住所を正規化できれば郵便番号は推測できるかな」「住所からの郵便番号逆引きは可能ですね」

「住所のような情報は国が公式に正規化するのが重要👍、民間でばらばらにやるのってマジで意味ありませんし」「まったくです」


豆知識: 原文に含まれる「人名」「地名・住所(ビル名や店名)」「固有名詞(企業名や商品名など)」「動植物の名前」の正確な翻訳は、汎用の機械翻訳が極めて苦手とする作業です。例: honestyは「正直」以外に「ギンセン草」を表すことがあります。

住所といえば、以下の文庫『地名の謎』『番地の謎』は住所や番地についての研究として自分の知る限り最強です。住所改定や平成の大合併など歴史や由来も網羅していて、熱量の高さが素晴らしいです。住所を扱うときに一読をおすすめします。Kindleで読めないのが残念。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

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

以下のコミットリストのChangelog差分を中心に見繕いました。

新機能: Active Recordにdelegated_typeが追加

DHH自らのプルリクです。説明がかなり長いですが、いずれRailsガイドに載ることを期待します。delegated typingのいい訳語が思いつかないのでそのままにしてあります。

リレーショナルデータベースのテーブルにクラス階層を対応付ける方法はいろいろある。たとえばActive Recordは純粋な抽象クラスを提供してスーパークラスでは属性を永続化しないようにしているし、STI(Single-Table Inheritance)では階層の全レベルの全属性が単一テーブルで表現される。どちらにもそれぞれ使い所はあるが、どちらにも欠点がないわけではない。
純粋な抽象クラスの問題点は、あらゆる具象サブクラスが各テーブルですべての共有属性そのものを永続化しなければならない点(「クラス-テーブル継承」とも呼ばれる)。この場合、階層をまたがるクエリを書くのが難しい。たとえば以下のクラス階層があるとしよう。

Entry < ApplicationRecord
Message < Entry
Comment < Entry

MessageのレコードとCommentのレコードの両方を持つフィードを表示して、楽にページネーションするにはどうすればよいだろうか?これは無理。メッセージの背後にはmessagesテーブルがあり、コメントの背後にはcommentsテーブルがあるが、一貫したOFFSET/LIMITスキームを使って両方のテーブルから一発でデータを取り出すことはできない。

STIはページネーションの問題を回避できるが、今度は全サブクラスにあるすべての属性を単一の巨大なテーブルに押し込めざるを得なくなる。クラスがどれほど違っていようとそうなる。MessageにsubjectがあるがCommentにはない場合、Commentでもsubjectが使えてしまう!つまりSTIが最適なのは、サブクラス同士やその属性同士の違いが極力少ない場合ということになる。

しかしここで第3の方法がある: それが「delegated type」だ。このアプローチでは、「スーパークラス」は独自のテーブルを持つ具象クラスであり、すべての「サブクラス」で共有されるすべてのスーパークラスの属性がそこに保存される。そして各サブクラスも、サブクラスの実装に必要な追加属性用に独自のテーブルを持つ。これはDjangoの「multi-table inheritance」と呼ばれるものに似ているが、実際にはこのアプローチで使うのは継承ではなく、階層を形成して責務を共有するのに「委譲(delegation)」を用いる。

「delegated type」でentry/message/commentを実装した例を見てみよう↓。

# Schema: entries[ id, account_id, creator_id, created_at, updated_at, entryable_type, entryable_id ]
class Entry < ApplicationRecord
  belongs_to :account
  belongs_to :creator
  delegated_type :entryable, types: %w[ Message Comment ]
end

module Entryable
  extend ActiveSupport::Concern

  included do
    has_one :entry, as: :entryable, touch: true
  end
end

# Schema: messages[ id, subject ]
class Message < ApplicationRecord
  include Entryable
  has_rich_text :content
end

# Schema: comments[ id, content ]
class Comment < ApplicationRecord
  include Entryable
end

参考: Module#included (Ruby 2.7.0 リファレンスマニュアル)

ご覧のとおり、MessageCommentも孤立していない。2つのクラスの重要なメタデータは「Entryスーパークラス」にあるが、Entryは特にクエリ送信能力という点では絶対的に独立可能になっている。これでAccount.entries.order(created_at: :desc).limit(50)みたいな書き方が楽にやれる。

commentsとmessagesをまとめて表示するときに欲しいのは、まさしくこれだ。このentry自身は、その「delegated type」として以下のように簡単にレンダリングできるようになる。

# entries/_entry.html.erb
<%= render "entries/entryables/#{entry.entryable_name}", entry: entry %>
# entries/entryables/_message.html.erb
<div class="message">
  Posted on <%= entry.created_at %> by <%= entry.creator.name %>: <%= entry.message.content %>
</div>
# entries/entryables/_comment.html.erb
<div class="comment">
  <%= entry.creator.name %> said: <%= entry.comment.content %>
</div>

concernsとコントローラで振る舞いを共有する

このEntryスーパークラスは、messagesやcommentsの両方に適用する共有ロジックの完璧な置き場も提供してくれるし、主に共有属性として振る舞う。以下で考えてみよう。

class Entry < ApplicationRecord
  include Eventable, Forwardable, Redeliverable
end

上のように書くことで、entriesでForwardsControllerForwardsControllerのように振る舞うコントローラが手に入る。しかもmessagesとcommentsの両方で共有する機能も提供できる。

新規レコードの作成

「delegated typing」を用いてレコードを1件作成するには、次のようにdelegatorとdelegeteeを同時に作成する。

Entry.create! message: Comment.new(content: "Hello!"), creator: Current.user

もっと複雑なコンポジションが必要な場合や、依存バリデーションの実行が必要な場合は、factoryメソッドかクラスを構築して、その複雑なニーズの面倒を見てやるべきだ。これは以下のようにシンプルにやれる。

class Entry < ApplicationRecord
  def self.create_with_comment(content, creator: Current.user)
    create! message: Comment.new(content: content), creator: creator
  end
end

delegationをさらに追加する

「delegated type」は、背後のクラスで何が呼ばれているかという問いに単に答えるだけのものであってはならない。実は、それはほとんどの場合アンチパターンになる。この階層を構築するのは、ポリモーフィズムのメリットを享受するのが目的である。シンプルなコード例を以下に示す。

class Entry < ApplicationRecord
  delegated_type :entryable, types: %w[ Message Comment ]
  delegates :title, to: :entryable
end

class Message < ApplicationRecord
  def title
    subject
  end
end

class Comment < ApplicationRecord
  def title
    content.truncate(20)
  end
end

これでentriesをいくつでもリストしてEntry#titleを呼び出せるようになり、ポリモーフィズムが答えを提供してくれるようになる。
同コミットより大意


つっつきボイス:「時間が取れたので、この長いプルリクメッセージをガガガッと大急ぎで訳してみました(訳さないと理解が遅くて😅)」「delegated typing?」「説明長い〜」

「STI(Single-Table Inheritance)のつらみを避けるためにDHHが実装したということみたい」「普通のモジュールインクルードとどう違うのかな?👀」

参考: STI -- Active Record の関連付け - Railsガイド

「どれどれ、以下みたいな設計でMessageとCommentの両方にあるものをリストとして引っ張ってくるときは、通常ならEntryに対してクエリを投げるけど、当然ながらMessageやCommentのメソッドは呼び出しづらくなるし、STIだと説明にもあるように全サブクラスの属性が1個のテーブルに入ってしまってテーブルがでかくなると」「DHHも『どちらにも使い所はあるけどそれぞれ欠点がある』って書いてる😆」

# 同PRより
Entry < ApplicationRecord
Message < Entry
Comment < Entry

「それがdelegated typingだとそれぞれが独自テーブルを持つからSTIの形にはならなくなるのか」「DHHは『第3の方法』と呼んでますね」「第3😆」「Djangoのmulti-table inheritanceにも似てるみたい」

参考: Multi-table inheritance -- Models | Django documentation | Django

「うん、サンプルコード↓を見るとEntryもMessageもCommentも単にApplicationRecordを継承してる: なるほどだんだん見えてきた😋」「delegate typingでは、共通のカラムはEntryにあるけど差分のテーブルはMessageやCommentにあって、それらがデータベース上Entryへの参照を持っていると」「EntryはEntryableモジュールを取ってくることでワンクッション置いてるのね☺️」

# 同PRより
# Schema: entries[ id, account_id, creator_id, created_at, updated_at, entryable_type, entryable_id ]
class Entry < ApplicationRecord
  belongs_to :account
  belongs_to :creator
  delegated_type :entryable, types: %w[ Message Comment ]
end

module Entryable
  extend ActiveSupport::Concern

  included do
    has_one :entry, as: :entryable, touch: true
  end
end

# Schema: messages[ id, subject ]
class Message < ApplicationRecord
  include Entryable
  has_rich_text :content
end

# Schema: comments[ id, content ]
class Comment < ApplicationRecord
  include Entryable
end

「これもうマージされてます?」「されました😋」「おぉ〜、じゃ次のRailsをバージョンアップしたら入ってくるのかな?😋」「masterブランチにマージされただけだとどのバージョンに入るかは決まりませんけど、でかい機能追加なのでRails 6.1あたりになりそうですね☺️」「これはいい機能👍」

「DHHの説明長いので、ウォッチ公開した後で切り取って別記事にしようと思います: Railsガイドに追加されるまでのつなぎということで」「お〜いいんじゃないでしょうか☺️」

STIよもやま話

「delegated typing、STIキライな人によさそう😆」「この書き方もっと前からできててもよかったかも」「公式にこれが入ったことが大きいですね😂」「このプルリクに『いいね👍』が大量に付いてました」「STIキライな人多いので😆」

「もちろんSTIでも問題なくやれる場合はあります: STIで一番問題になりやすいのは、まさにこのサンプルコードでやっているみたいに、リッチテキストのようなむちゃくちゃ大きいコンテンツが入ってくるカラムをSTIで増やすと行サイズがバカでかくなること😇」「そうですね😅」「ほとんどのカラムが共通カラムで、ごく一部のカラムだけが特定のテーブル用に追加される形(integerカラムをちょっと足す程度)なら、STIでも大したことにはなりませんし🧐」「ふむふむ」「STIで特定のテーブルにしかないカラムが長大になると(text型とか)、データベース上の無駄が多すぎるんですよ😢」

「そういう長大なカラムを使う場合は明らかにこのdelegated typingの方がいいでしょうね❤️: データモデル的にもキレイですし、オブジェクト指向プログラミングでよく言われる『なるべく委譲しよう』的な考え方ですし🧐」「おぉ😍」「サブクラスを増やして継承するより先にデリゲートを検討しましょう、コンポジションしましょうというヤツ☺️」「なるほど!」

参考: Rubyによるデザインパターン5原則 - Qiita

「もちろんどんな場合でもdelegated typingがいいわけではないと思います: STIの方が速くなることも普通にありますので🚀」「設計の選択肢が増えるのはいいこと👍」

スキーマにENGINE=InnoDBをデフォルトで含めないようになった

スキーマをよりagnostic(=決め打ちしない)にするため、デフォルトのエンジンENGINE=InnoDBをダンプしないようにした。
Changelogより

# Changelogより
# before
create_table "accounts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
end

# after
create_table "accounts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
end

つっつきボイス:「@kamipoさんの改修です」「そうそう、昔はMySQLでENGINE=InnoDB ROW_FORMAT=DYNAMICしないとutf8mb4でインデックス作れなかった、なつかしい」「今のMySQLではENGINE=InnoDBは不要だから出さなくなるのもわかる」「そこは時代が変わったということで」

MySQLのencodingをutf8からutfmb4に変更して寿司ビール問題に対応する

「MyISAMを使い続ける人、今もたまに見かけますよ」「いるんですか?」「ソシャゲ界隈あたりだったかな?、たぶん枯れているものしか使わないポリシーなのかも」「MyISAMはログDBに向いてるって昔教わったことあります」「MyISAMで参照が発生しないカラムサイズにうまく押し込められればいいでしょうね: MyISAMは長年チューニングされてますし」

参考: 漢(オトコ)のコンピュータ道: MyISAMからInnoDBへ切り替えるときの注意点

キャッシュフラグメントキーに関連テンプレートをすべて含めるようになった

まとめ

Railsビューは、アクションに対応するテンプレート(show.html.erbなど)のレンダリング時に(レイアウトやパーシャルなどの)複数のテンプレートにも依存することがある。現在レンダリング中のビューファイルをトラッキングしてキャッシュフラグメントキーで使うテンプレートツリーのダイジェストを生成するために、Action Viewはスタックを使っているが、スタックのトップには@current_template変数経由でアクセスされる(1581cabで導入)。
以下のテンプレートで考える:

<!-- home.html.erb -->
<%= render layout: "wrapper" do %>
  <%= cache "foo" %>
    HOME
  <%= end %>
<%= end %>

上のrenderヘルパーに渡されるブロック内の@current_templateは、(home.html.erbではなく)wrapper.html.erbテンプレートに対応する。そしてキャッシュフラグメントキーで使われるテンプレートツリーダイジェストの生成にはwrapper.html.erbが使われるので、home.html.erbが変更されてもキャッシュフラグメントの有効期限が期限切れしなくなってしまう。さらに、2番目のテンプレートでwrapper.html.erbレイアウトが使われ、キーが同じキャッシュフラグメントを含んでいると、2つのテンプレートのキャッシュフラグメントキーが同一になってしまい、あるビューのキャッシュの中身が別のビューにお漏らししてしまう(#38984)。
このプルリクは、renderヘルパーにブロックを与えてテンプレートをレイアウトとしてレンダリングした場合にスタックにテンプレートを追加する。これにより、@current_templateが現在レンダリング中のビューファイルに正しく対応するようになり、キャッシュフラグメントダイジェストが一意かつ有効期限も正しく期限切れになる。さらに、CacheHelperで見つけたvirtual_pathキーワード引数を削除し、そこに関数が置かれることのないようにした。(@current_templateの導入後、virtual pathは単独の変数としてではなく、@current_template.virtual_path経由でアクセスされるようになっていた)。最後に、@virtual_pathインスタンス変数をコードベースから完全に削除し、そこへの参照をすべて@current_template.virtual_pathに置き換えた。

その他の変更点

actionview/test/template/render_test.rbで使われていたコントローラーキャッシュストアがnilだったのを:memory_storeが設定されるようにした(このプルリクで修正された「失敗するテスト」を提供するために必要)。

その他の情報

このプルリクは2つのコミットに分かれている。最初のコミットは、テストがすべて通ってしまう#38984のバグを最小限の形で修正するのに必要。次のコミットは、余分な@virtual_path変数をコードベースから完全に取り去って@current_template.virtual_pathに置き換えるリファクタリング。2つに分けたのはレビューしやすくするためで、#38984の修正で変更された点や無関係なリファクタリングを追いやすくする目的だが、マージの前に1つのコミットにrebaseしてもらえるとうれしい。
同コミットより大意


つっつきボイス:「これも説明長かったので同じくガガッと翻訳してみました」「『キーが同じキャッシュフラグメントを含んでいると2つのテンプレートのキャッシュフラグメントキーが同一になってしまう』ようになってたとは」「自分はこんなにキャッシュフラグメントキーを細かく指定したことがほぼないのでこれ踏んだことなかった」「キャッシュフラグメントキー、昔使ってみたけどややこしいことになりました」

<!-- 同PRより -->
<!-- home.html.erb -->
<%= render layout: "wrapper" do %>
  <%= cache "foo" %>
    HOME
  <%= end %>
<%= end %>

<%= cache "foo" %>の部分って普通はActive Recordとかのオブジェクトを指定することが多いので、そういうものなら同一のものを指していても問題ないんですけど、サンプルコードみたいに固定値を指定するとこういうことが起きちゃってたんですね」「キャッシュフラグメントキーなつかしいから後でちょっと試してみようかな」

参考: 1.3 フラグメントキャッシュ -- Rails のキャッシュ機構 - Railsガイド

groupを重複フィールドで集約することが非推奨化

クエリのannotateで付けたメッセージの重複も非推奨化されています。


つっつきボイス:「重複例ではキーが[1, 1]みたいに配列になってますね↓」

accounts = Account.group(:firm_id)

# グループ化のフィールドが重複する(非推奨化)
accounts.merge(accounts.where.not(credit_limit: nil)).sum(:credit_limit)
# => {
#   [1, 1] => 50,
#   [2, 2] => 60
# }

# グループ化するフィールドの重複解消には今後`uniq!(:group)`を使うこと
accounts.merge(accounts.where.not(credit_limit: nil)).uniq!(:group).sum(:credit_limit)
# => {
#   1 => 50,
#   2 => 60
# }

「新しく追加されたuniq!(:group)↓で重複解消して欲しいそうです」「その方がよさそう」「これを追加したから重複を非推奨化したという流れなんでしょうね」

# 同PRより
    # Deduplicate multiple values.
    def uniq!(name)
      if values = @values[name]
        values.uniq! if values.is_a?(Array) && !values.empty?
      end
      self
    end

uniq!(:group)って書き方、ぱっと思いつけるだろうか?」「思いつけなさそう」「ともあれこういう正しい書き方が提供されたところに意味がありますね👍」

キーワード引数関連の改修多数


つっつきボイス:「@kamipoさんのキーワード引数関連改修です」「みんな大好きキーワード引数」

# activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L376
-     def column(name, type, **options)
+     def column(name, type, index: nil, **options)
        name = name.to_s
        type = type.to_sym if type

        if @columns_hash[name]
          if @columns_hash[name].primary_key?
            raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
          else
            raise ArgumentError, "you can't define an already defined column '#{name}'."
          end
        end

-       index_options = options.delete(:index)
-       index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options
        @columns_hash[name] = new_column_definition(name, type, **options)
+
+       if index
+         index_options = index.is_a?(Hash) ? index : {}
+         index(name, **index_options)
+       end
+
        self
      end

「お、column()index:キーワード引数が追加されたのかと思ったら、index:オプションが**optionsから取り出されてキーワード引数になったということか」「なるほど!」「今まではindex:オプションをoptions = {}で受けていた」「index:の指定は必須かなと思ったら、デフォルト値がnilだからindex:キーワードは省略できるのね」「options = {}というオールドスタイルな書き方も**optionsに置き換えられた」「options = {}が置き換わっていくのうれしい」

Railsフレームワークで多用される「options = {} 」引数は軽々しく真似しない方がいいという話

Rails

「RuboCopのデフォルト設定」に関するアンケート結果

ruby-jp Slackで知りました。最新のRuby Weeklyでもトップに上がっていました。


つっつきボイス:「ruby-jp Slackでも話題になってました🎉」「そうそう、ちょうどさっきkoicさんがLayoutLineLengthのデフォルトを120文字に変えた記事↓を見てマジ感動しました」

1行の最大文字数はいくつがいいか

「line lengthは120文字欲しい!」「これはオレたちの待ち望んでいた改修」「koicさん記事の"いつかこの変更をするのが目標のひとつだった"、わかる」「80文字のつらさは異常」「80はない」「コンソールが80文字x25行だったのって相当昔ですよね」「まあ1行があまりにもでかいのも考えものなんですけど、ビジネスロジックを真面目に書いているとせめて120文字は欲しい」「メソッドチェインのたびに改行するのもあまりやりたくないかも」

文字列リテラルは""で囲むか''で囲むか

「クォートのスタイルはどんな結果になったかな?」「シングルクォート派は58%」「Rubyのシングルクォート' 'とダブルクォート" "は意味が違うので、自分は両方使い分けるでいいと思います」「同感」「悩むぐらいならRuboCopに直させればいいと思います」

参考: 文字列リテラル -- リテラル (Ruby 2.7.0 リファレンスマニュアル)

Rubyの式展開(string interpolation)についてまとめ: `#{}`、`%`、Railsの`?`

コレクションリテラルのケツカンマはどのスタイルがいいか

# 同記事より
# ケツカンマなし
{ one: 1,
  two: 2 }

# 複数行リテラルの場合のみケツカンマ
{ one: 1,
  two: 2, }

{ one: 1, two: 2 }

# 常にケツカンマ
{ one: 1,
  two: 2, }

{ one: 1, two: 2, }

「trailing comma付けない人多いんだ」「付けたくない気持ちもわかる」「まあこれもRuboCopにお任せで」

参考: 末尾のカンマ - JavaScript | MDN

andorは使うか使わないか

「優先順位の低いand演算子やor演算子は以前から使わない方がいいって言われてますし」「記事にもありますけど、例外はRailsコントローラのリダイレクト and returnみたいなフローコントロールのとき」「Railsではイディオムになってますね」「そうそう、Railsでこの書き方は認められてますし、自分もあっていいと思いますけど、なくすのはそれはそれでありかもって思います」

参考: 演算子式 (Ruby 2.7.0 リファレンスマニュアル)
参考: 2.2.14 二重レンダリングエラーを避ける -- レイアウトとレンダリング - Railsガイド

!!は使うか使わないか

!!は好きでもキライでもないかな」「ダブルビックリ」

Rubyの否定演算子2つ重ね「!!」(double-bang)でtrue/falseを返す

ハッシュリテラル{}のすぐ内側にスペースを置くか置かないか

「ハッシュリテラルのすぐ内側にはスペース置きたい派({ key: :value })」「私もスペースないと窮屈な感じします」「ある方が見やすいけどたまに書き忘れます」

Kernelメソッドの引数を()で囲むか囲まないか

Kernelメソッドの引数に()付けるか問題」「自分は付けない」「exitなら付けるかもだけどputsには付けないかな」「自分はあんまり意識しませんし、そもそもKernelメソッドかどうかも気にしてない」

参考: module Kernel (Ruby 2.7.0 リファレンスマニュアル)

Metrics copは便利か

「Metrics copキライな人が多いから聞いてみたとありますね」「まあうるさいのはたしか」「でもないよりはある方がいい」「うるさかったらRuboCopの設定変えれば済みますよね」「記事の下に書いてあるこのyaml設定↓でMetricsをまとめて殺せるとありますね」「Metricsを全部無効にしたいと思うことってあんまりなさそうですけど」「たしかに」

Metrics:
  Enabled: false

参考: Metrics Cops - RuboCop: The Ruby Linter that Serves and Protects

RuboCopを素の設定で使ってどのぐらい幸せになれるか

「素のRuboCop設定でどのぐらい幸せか問題」「これはもう人それぞれでしょう」「単なるhappyが一番多いけど、very happyとまではいかないということ?」「デフォルトでいいと思ってる人が大多数なんでしょうね」「デフォルトだとちょっとつらいかも」「デフォルトのスタイルがあることが大事だと思います: スタイルにデフォルトがないと迷ったときに不便」


「そういえばRailsがジェネレートするRubyコードって素のRuboCopに怒られますよね」「そうそうめっちゃ怒られる、オレが書いたんじゃないのに」「RailsジェネレーターのRubyコードはもっと素のRuboCopに寄せてもいいと思います」

delete_in_batches: Active Recordのバルク削除(Ruby Weeklyより)

# 同リポジトリより
# Slow
Tweet.where(user_id: 1).delete_all
# DELETE FROM tweets WHERE user_id = 1

# Faster
Tweet.where(user_id: 1).in_batches(of: 10000).delete_all
# SELECT tweets.id FROM tweets WHERE user_id = 1 ORDER BY id LIMIT 1000
# DELETE FROM tweets WHERE user_id = 1 AND id IN (1, 2, 3, ...)
# ...

# Fastest
Tweet.where(user_id: 1).delete_in_batches
# DELETE FROM tweets WHERE id IN (SELECT id FROM tweets WHERE user_id = 1 LIMIT 10000)
# DELETE FROM tweets WHERE id IN (SELECT id FROM tweets WHERE user_id = 1 LIMIT 10000)
# ...

つっつきボイス:「ankaneさんのARバルク削除gemだそうです」「こんなのあるんですね」

「fastestつおい」「これはロックを小さくしてDELETEする感じかな」「おぉ?」「slowのTweet.where(user_id: 1).delete_allだとロックがかかって遅くなるけど、fasterだとDELETE FROM tweets WHERE user_id = 1 AND id IN (1, 2, 3, ...)の中にトランザクションが挟まれるようになって速くなるんだろうと想像してます🚀: DELETEする対象が大量にある場合に速くするということなんでしょう」「よさそう」「Railsのfind_in_batches↓と対をなす感じの名前ですね」

TRUNCATEにご注意

「ところでREADMEに『全部消すならTRUNCATEが最速だぞ』って書いてあるんですけど」「それは爆速だけど危険」「これをギャグだとわかって読める人が使うべきでしょう」「READMEに書くと本気にする人出そうでコワイ」「TRUNCATE書いてもコードレビューで却下されるでしょうね」

# 同リポジトリより
ActiveRecord::Base.connection.execute("TRUNCATE tweets")

参考: TRUNCATE と DELETE の違い - オラクル・Oracleをマスターするための基本と仕組み
参考: PostgreSQLでTRUNCATEをROLLBACKした際の内部の挙動を追いかけてみた | Developers.IO

命名の基準


つっつきボイス:「jnchitoさんのQiita記事です」「どこかでちらっと見かけたかも」「そもそも基準ってあるのかと思ったら、Stack Overflowレベルで一応それっぽい回答はあるらしい」「実は前に自分のブログでこのネタ書きました」

「ActionだっけActiveだっけって割と迷いますよね」「迷います」「モデル層に属するコンポーネントはActiveという考え方は割と腑に落ちます😋」「Action Viewはビューに近いからActionになったとは」

「ところでActive StorageとActive Supportを省略すると、どちらもASになっちゃう」「Action MailerとActive ModelもAMになってしまう」「惜しい」「この辺に一意な省略形があるといいんですけどね〜」

Rails製ゲームサイトの応答速度を5倍改善(Ruby Weeklyより)

Evil Martians記事です。


つっつきボイス:「Evil Martiansが手掛けているらしいMayhemとかいうゲームコミュニティプラットフォームのレスポンスを改善したということらしいです」「Mayhem見たことない」「自分もゲーム音痴なので詳しくはわかりません」「全然ちゃんと見てないけど、ゲームのロビーマッチングをやってるサービスなのかしら?」「サイトにもlobby nameってありますね」

「記事ではGraphQLや、以前ウォッチでちょっとだけ取り上げた同社のAction Policyを使ってるようです(ウォッチ20180420)」「GraphQLになるのわかる」「Action Policyはauthorizationのgemか」「それをGraphQLと使ってるっぽい」

「そしてレスポンスが5倍改善したと↓」「開発方法がちょっと特殊になったので開発者を集めて特訓したそうです🏋🏻‍♀️」「Evil Martians、ゲーム系もやってるのね」


同記事より


「Mayhemって『暴動』みたいな意味らしくて、同名のヘビメタバンドもあるのでややこしい」「Mayhemってギリシャ語とかラテン語あたりの神様っぽい雰囲気のスペルかな?⛩」「そういえばどことなくヘブライ語っぽい響きかも?🇮🇱」

後で調べるとmayhemは古フランス語由来のようです。

参考: mayhem | Origin and meaning of mayhem by Online Etymology Dictionary


前編は以上です。

おたより発掘

バックナンバー(2020年度第2四半期)

週刊Railsウォッチ(20200526後編)Rubyでよくやるスレッドバグ、Kubernetesでよくあるミス10、CSS/SVG/Canvasの使い分けほか

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

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

Rails公式ニュース

Ruby Weekly


CONTACT

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