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

週刊Railsウォッチ(20200721後編)『パーフェクトRuby on Rails』増補改訂版発売間近、scan_left gemでレイジーなinjectほか

こんにちは、hachi8833です。今週木金は祝日のため、来週7/27、7/28の週刊Railsウォッチは通常記事となります🙇。

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

臨時ニュース: 『パーフェクトRuby on Rails【増補改訂版】』が7/25より発売🎉

つっつきの後で知りました。紙の書籍と電子版同時発売だそうです。

Kindle/EPUB版もあるのがありがたいです🙏。詳しくは以下をどうぞ。

Ruby

Polyphony: Rubyでfine grainedコンカレンシー(Ruby Weeklyより)


同サイトより

# 同サイトより
require 'polyphony'

# Kernel#spin returns a Fiber instance
counter = spin do
  count = 1
  loop do
    sleep 1
    puts "count: #{count}"
    count += 1
  end
end

puts "Press return to stop this program"
gets

名前が好きです。


つっつきボイス:「RubyのFiberをいい感じに使えるらしい」「fine-grainedって日本語だと高粒度?この方面ではあまり日本語では言わないかな〜」

「Railsアプリのビジネスロジックでコンカレンシーをふんだんに使うことはあまりないと思いますけど、それこそRackサーバーを自分で書いて複雑なことをさせたいときとか、RubyでSocketをリッスンするようなアプリを書くときとか、後はサイトにもあるようにデータベースコネクタのコネクションプール↓なんかだとコンカレンシーを制御したくなるでしょうね」「そういえばPolyphonyにはHTTPSもできるフル機能のWebサーバーも含まれているそうです」「Webサーバーはまさにコンカレンシーが求められるところですね」

# 同サイトより
DB_CONNECTIONS = Polyphony::ResourcePool.new(limit: 5) do
  PG.connect(DB_OPTS)
end

def query_records(sql)
  DB_CONNECTIONS.acquire do |db|
    db.query(sql).to_a
  end
end

「通常のコンカレンシーだとスレッドの管理になりますけど、PolyphonyはFiberまで使うところがfine-grainedということなんでしょう」「Fiberレベルのコンカレンシーgemということですね」

Huginn: IFTTTやZapier的なWeb連携自動化(Ruby Weeklyより)


同リポジトリより

参考: Webサービスの連携を自動化する Huginn の紹介 - Qiita


つっつきボイス:「ずっと前のウォッチでごく簡単に触れたことがありますが、IFTTTやZapier的なことをやれるRailsアプリだそうです」「オープンソースなのね」

参考: IFTTT: Every thing works better together
参考: Zapier | The easiest way to automate your work

「こういうWeb連携アプリって自分で運用すると大変なので誰かにやって欲しい😆」「そうかも」「インスタンス管理とかOSアップデートとかの面倒見るのがほんとつらいし、止まったときにいろんなところでお困りが発生しますし」「それを考えると、高くてもZapierを使いたくなるかな〜」

「まあ今だとKubernetesが流行ってますし、このHuginnをコンテナでサクッとそこにデプロイできるのであればそんなに大変じゃないかもしれませんけど」「コンテナならやりやすくなりそうですね」

scan_left: injectをレイジー&インクリメンタルに(Ruby Weeklyより)

# 同リポジトリより
require "scan_left"

# 比較のために#injectも記載

ScanLeft.new([]).scan_left(0) { |s, x| s + x } == [0]
[].inject(0) { |s, x| s + x }                  == 0

ScanLeft.new([1]).scan_left(0) { |s, x| s + x } == [0, 1]
[1].inject(0) { |s, x| s + x }                  == 1

ScanLeft.new([1, 2, 3]).scan_left(0) { |s, x| s + x } == [0, 1, 3, 6]
[1, 2, 3].inject(0) { |s, x| s + x }                  == 6

# オプション: `ScanLeft`クラスの利用を明示的に回避したい場合は
# 以下のようにEnumerableでrefinementを使う手もある
#
# このrefinementは`#scan_left`メソッドをEnumerableに直接追加して
# 構文をより明瞭にできる

using EnumerableWithScanleft

[].scan_left(0) { |s, x| s + x }        => [0]
[1].scan_left(0) { |s, x| s + x }       => [0, 1]
[1, 2, 3].scan_left(0) { |s, x| s + x } => [0, 1, 3, 6]

つっつきボイス:「injectのオルタナですか」「そういえばここにもinjectの好きな人が約1名」「ワイのことだ〜😋」「記事にあるように、Enumerableでlazy的なinjectをやれそうですね😋」

遅延評価

「遅延評価は、使う側がどこかで処理を止めたいときには速くなりますね」「遅かれ早かれやらなければならない処理はスキップしようがないので、遅延評価にすれば速くなるわけではないでしょうけど」「やらなくてよかった計算をスキップできれば効果が大きい」

「@kamipoさんがRailsの改修でよくやっていますけど、Active Recordベースのカラムって参照されないものの方が多いなんてこともよくあるじゃないですか」「そうそうっ」「カラムはいっぱいあるけど実はidしか取ってなかったとか」「そういうオブジェクトを毎回作ると無駄が大きい」

「ライブラリコードなら使うかどうかわからないようなものを遅延評価で書いておけば効果は大きいと思いますけど、サービスのコードだと、ある程度下のレイヤならともかく、遅延評価を取り入れたところでどうせ全部実行されることの方が多そうですし😆」「おっしゃるとおり😆」

「逆にそこら辺を遅延評価で書いてしまうとプロファイラで見ても評価が後ろにずれてしまってどこが遅いのかわかりにくくなりますし」「遅延評価でデバッグしづらくなるの、あるある」「まあ遅延評価にすればいいってもんじゃないよということで」


なお、作者はこの機能リクエストをRuby本体にも投げているそうです↓(審議中)。

無名structリテラル続報(Ruby Weeklyより)


つっつきボイス:「前回も扱った無名structリテラルですが(ウォッチ20200707)、今日久しぶりにつっつきに参加いただいたkazzさんに見せたかったので」「こういうふうに${}でstructを定義できるヤツです↓」「ははぁ〜、なるほど〜😋」

# 同記事より
roxie = ${name: "Roxie", breed: "whippet-cross"}

「無名structリテラル、あってもいいですよね」「私も欲しい〜」「むしろこれがなかったから今までRubyのstructがあまり使われなかったんじゃないかって思ったり」

「Rubyのstructってnewする割にはインスタンス1個のまま使われるイメージあるかな」「structはイミュータブルハッシュ的に使われることが多いですよね、自分もそうやって使ってますけど」「たしかにイミュータブルハッシュは欲しい!」

「でも考えてみれば、本来イミュータブルハッシュってハッシュでやるべきじゃないんですよね」「そうそうっ」「無名structを使うとイミュータブルであることを明示できるのがいいよね、と思うわけですよ」「あぁ〜わかりますそれ😂」

「無名structリテラルで作ったものはまさにValue Objectとして使えるわけですけど、Value Objectを手軽に定義できるのはとても嬉しい」「structで書くのタイプ量多くてダルいですし😆」「それでついついハッシュに流れてしまうという😆」「無名structリテラル、入ったら使おうかな〜」「もういくつ寝ると来るんでしょうね」

Rails tips: Value Objectパターンでリファクタリング(翻訳)


同記事より:

  • 無名structリテラルが有用な理由:
    • structならキーのタイポを防げる
    • 無名structはイミュータブルであるという意図がはっきり伝わる
    • structならドット記法でシンプルにアクセスできる
    • OpenStructは遅い
  • 同プロポーザルにも「今はハッシュの方が速い」とあるが、ハッシュの利用頻度が高い分最適化が進んでいるだけではないか。

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

その他Ruby


つっつきボイス:「ScientistはGitHubの公式gemなのね」「科学系か教育用かなと思ったけど、carefully refactoring critical pathとあるし、パフォーマンスをチェックしながらリファクタリングするgemっぽいですね」

# 同リポジトリより
require "scientist"

class MyWidget
  def allows?(user)
    experiment = Scientist::Default.new "widget-permissions"
    experiment.use { model.check_user?(user).valid? } # old way
    experiment.try { user.can?(:read, model) } # new way

    experiment.run
  end
end

DB

ENUMは銀の弾丸ではない


つっつきボイス:「ぽすぐれのenum型の話: データベースレベルで所定の値だけが入れられるようバリデーションする方法として以下が紹介されてますね」

  1. ENUM型
  2. シンプルなCHECK制約
  3. CHECK制約とFUNCTIONの組み合わせ
  4. 外部キー

「1.のENUMはスキーマの一部で、項目が増えたり減ったりするとスキーマを変更することになる: 増えるときはともかく減らすときは単純にALTER TABLEするわけにもいかなくて面倒になりがちかな」「デメリットとしてはユーザー側で項目を増やしたり減らしたりできない: すぐに変わらないアプリケーションロジックと密に絡むENUMならまあ大丈夫かと思いますが、Redmineの選択項目みたいに運用で項目が増減する可能性があるなら果たしてENUMがいいのかどうかは気にするといいでしょう」

-- 同記事より
CREATE TYPE gender AS ENUM ('male', 'female');

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    gender gender NOT NULL
);

「なおRailsのenumだと文字列やシンボルを数値に置き換えますが、ぽすぐれのENUMはENUM型なのでSQLレベルではちゃんと要素がmaleとかfemaleみたいに現れますね」「なるほど、RailsのenumとPostgreSQLのENUMは違うんですね」「Railsのenumを使うとSQLクエリで数値になるのが読みづらくて、あまり好きでないかな〜」

「他にPostgreSQLのENUMのデメリットとしては、ENUMの値はそのままでは文字列を結合したりサブストリングを取り出したりできない: ぽすぐれだと一応キャストはできるようですが、意識しておく必要はありますね」

「ENUMに向いているデータとして記事では性別が挙げられてますが、バッチのステータスなんかもまず増減しないので向いているでしょうね」


「2.のCHECK制約で書く方法」

-- 同記事より
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    gender TEXT CHECK (gender IN ('male', 'female'))
);

「2.のデメリットとして項目が増えすぎると破綻することがあると、なるほど↓」「ぽすぐれにはENUMがあるからこの書き方はせずに済みますけど、ENUMが入る前のMySQLでこの書き方を使った覚えがありますね」

-- 同記事より

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    country TEXT CHECK (country IN (
        'ad',
        'ae',
        'af',
        'ag',
        'ai',
        'al',
        'am',
        'an',
        'ao',
        ...
    ))
);

参考: ENUM型 | MySQLの使い方


「3.はCHECK制約の参照先をFUNCTIONにするというもので、単なるCHECK制約だと同じ項目を別のところでも使うとDRYでなくなってしまいますけど、FUNCTIONにすることで回避できます」

-- 同記事より

CREATE OR REPLACE FUNCTION valid_gender(TEXT) RETURNS BOOLEAN AS $$
BEGIN
    RETURN ($1 IN ('male', 'female'));
END
$$ LANGUAGE plpgsql;


CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    gender TEXT CHECK (valid_gender(gender))
);

「4.は正規化レベルを1つ上げて外部キーとして扱うというもの: これが一番RDBらしい解決方法かなと思います」「デメリットとして追加テーブルがめちゃくちゃ増える可能性があるというのもごもっとも」

-- 同記事より
CREATE TABLE countries (
    code TEXT PRIMARY KEY
);

INSERT INTO countries (code) VALUES ('ad'), ('ae'), ('af'), ('ag'), ('ai'), ('al'), ('am'), ('an'), ('ao'), ...

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    country TEXT NOT NULL,

    FOREIGN KEY (country) REFERENCES countries(code)
);

「記事の末尾にまとまっているこの表↓は実装方法を複数知っておくうえでなかなかいいですね」「Railsの場合、Railsのenumという列がもうひとつ加わるかな」「タイトルの銀の弾丸云々も、どちらかというとENUM以外の方法もあるよという点に重きを置いている感じですね」「実践的でいい記事だと思います👍」

JavaScript

Elmとは

Haskellライクな言語だそうです。

// https://guide.elm-lang.orgより
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

main =
  Browser.sandbox { init = 0, update = update, view = view }

type Msg = Increment | Decrement

update msg model =
  case msg of
    Increment ->
      model + 1

    Decrement ->
      model - 1

view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (String.fromInt model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

参考: Railsで愉快な言語Elmを使う - Qiita


つっつきボイス:「エラーメッセージが読みやすいとかTSとReduxより楽という噂を聞いたのでElmをエントリしてみました」「まあ使いたい人が使うヤツでしょう」「一応Webpackerで入れられるそうですが、Railsでの利用例の記事は今のところあまり見かけない感じでした」「最近だとフロントエンドとバックエンドでリポジトリを分けて、RailsはAPIとかGraphSQLを担当するみたいなのが増えてる感じはあるので、ElmみたいなのをRailsと同じリポジトリに入れることはそんなになさそうな気もしますけどね」

CSS/HTML/フロントエンド/テスト/デザイン

QUIC


つっつきボイス:「この記事はてブで見ましたけど、よくここまでやったなという感じ」「最近はDNSもQUIC実装が進んでたりしますけど、この記事みたいにBGP over QUICやるのはアツい」「まあBGPを触る機会がそもそもありませんけど😆」

参考: BGP(Border Gateway Protocol)とは

「結局、TCPは便利だけどあらゆるニーズに応えられるとは限らないということでしょうね: フローコントロールを自分でやりたいならTCPはいらないでしょうし」

その他フロントエンド


つっつきボイス:「前にも話題にした気がしますけど(ウォッチ20200204)ついに始まったのね」「受験料1万円か〜」「試験を受ける金銭的なメリットはぱっと思いつきませんけど、セキュリティを勉強するエンジニアが理解度を確かめるのには有用だと思います👍」「徳丸先生のことだから問題もそれなりに難しそうだし、範囲も相当広いんじゃないかしら」

番外

ゲーム音楽サイトはRails+React製


同サイトより


つっつきボイス:「ゲーム音楽って知らない世界なんですが、はてブ民が泣いて喜んでたので」「泣かないけど全俺が喜んだ: ありがとうございます、ありがとうございます😂」

「そうそう、このサイトのソースコードがGitHubリポジトリに上がってますよ↓: しかもRailsベースのGraphQLとReactベースのフロントエンド」「お〜マジですか!」「作者のブログもありますし↓」

「しかしいつの間に?」「いや単に自分の好きな曲がなかったので、どうやってデータをクローリングしてるのかなと思って昨日ソースコードを見てた🤣」「そうでしたか🤣」「シューティングの曲が少ないな〜と思いながらざっと見た感じでは、どうやらクローラじゃなくて自分でデータ入れてるみたい」「それもスゴい💪」


後編は以上です。

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

週刊Railsウォッチ(20200720前編)10月開催「Kaigi on Rails」CFP募集中、enumにデフォルト値設定機能、RailsでBitemporal Data Modelほか

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

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

Ruby Weekly


CONTACT

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