Quantcast
Channel: Ruby on Railsの記事一覧|TechRacho by BPS株式会社
Viewing all 1386 articles
Browse latest View live

週刊Railsウォッチ(20210119前編)PostgreSQLのCTEを使えるActiveRecordExtended gem、2021年初頭のRails展望記事ほか

$
0
0

こんにちは、hachi8833です。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今回は公式更新情報を中心に見繕いました。いくつかは先週のウォッチで先回りしてました(ウォッチ20210112)。


つっつきボイス:「Rails公式の更新情報も新年号っぽく出ていました🎍

⚓ 同じフォームを複数のHTTPメソッドで送信するformmethod:オプション

ブラウザは<form>を送信するときに、送信を開始した要素をFormDataの一部としてシリアライズし、そこにnamevalueといった属性を含めている。
ブラウザが<form>送信でサポートするHTTP verbはGETPOSTのみに限られている。現在のRailsでは、_method="VERB"をシリアライズしてFormDataに加える<input type="hidden" name="_method" value="VERB">をビルドすることでこの制約を回避している。
このコミットは同一のフォーム内で複数のHTTP verbをサポートするために、
フォームのビルド中にform.button formmethod: "..."が呼び出されると、_methodの値をformmethod:で指定した任意の値に書き換える。
同PRより大意

参考: FormData - Web API | MDN


つっつきボイス:「なるほど、buttonヘルパーでformmethod:を使ってHTTP verbを指定できるようになったんですね: 以下のDeleteボタンの場合、form_with側のmethod:で指定しているPUTをDELETEで上書きしてからフォームを送信するようになる」「これをできるようにした気持ちは何となく想像が付きますね」

<!-- changelogより -->
<%= form_with model: post, method: :put do %>
  <%= form.button "Update" %>
  <%= form.button "Delete", formmethod: :delete %>
<% end %>
<!-- changelogより: 上の生成結果 -->
<form action="posts/1">
  <input type="hidden" name="_method" value="put">
  <button type="submit">Update</button>
  <button type="submit" formmethod="post" name="_method" value="delete">Delete</button>
</form>

「どんなときに便利なんでしょうか?」「まさに上のコード例でやっているように、たとえば同じフォームの中にUpdateボタンとDeleteボタンの両方を楽に置きたいときなどですね: 同じことはformmethod:がなくてもできますが、その場合はDeleteボタンのリンクを別途作って、このPostモデルのidをリンクで指定してHTTPのDELETEメソッドを送信できるようにする必要があったんですよ」「そういえばそうだったかも」

formmethod:ができたおかげで、そういうことをしなくてもbuttonメソッドにformmethod: :deleteと書くだけで、モデルのidをリンクで指定せずに同じフォームにDeleteボタンも置けるわけです」「あ、なるほど!」「idを指定せずに書けるのは便利だと思います👍」「上のコード例のような代表的なCRUDのときに便利ですよね」

参考: CRUD - Wikipedia

⚓ Action Textのカスタマイズ機能を強化

拡張可能なレイアウト
(このプルリクは、)リッチテキストコンテンツを「囲む」HTMLのレンダリング方法を、layouts/action_text/contents/_content.html.erbのような拡張可能なテンプレートとして公開する。それによってユーザーランド側でのカスタマイズを促進しつつ、#render_action_text_contentヘルパー呼び出しをaction_text/contents/_content.html.erbパーシャルに移動することで、リッチテキスト自身のレンダリング方法を制御するprivate APIが失われないようにする。

拡張可能かつアタッチ可能なto_attachable_partial_path
あるレコードについてアプリケーションがカノニカルなパーシャルを宣言すると、リッチテキストに変換された場合にそのパーシャルの使われ方をオーバーライドする方法がなかった。

たとえば、デフォルトのPerson < ApplicationRecordインスタンスが#to_partial_pathへの呼び出しに対して"people/person"を返すと、app/views/people/_person.html.erbパーシャルが(問答無用で)レンダリングされる。

改修前は、<action-text-attachment sgid="...">要素があるとAction Textがそれに対応するAttachableインスタンス(普通はActiveRecord::Baseのインスタンス)を取得し、それが持つ#to_partial_pathに対応するパーシャルをレンダリングすることでリッチテキストHTMLに変換していた。

ここで提案する改修は、#to_partial_pathではなくAttachable#to_attachable_partial_pathを呼ぶというものだ。なおデフォルトでは#to_attachable_partial_path#to_partial_pathのエイリアスになる。

ガイド
これらのテンプレートのカスタマイズ方法と、ActionText::AttachableインスタンスがレンダリングされてHTMLになるまでのわかりやすい説明をRails GuidesのAction Text Overviewに追記した。
同PRより大意


つっつきボイス:「Action Textで表示する中身のリッチテキストのレンダリング方法をカスタマイズできるようにした、ということみたい: このあたり↓とかを見ると、attachment(添付ファイル)周りをカスタマイズしたいという要望があるのかも」「ふむふむ」「レンダリング方法をオーバーライドできるようにしたいというのはもっともですね」

# actiontext/app/helpers/action_text/content_helper.rb#L21
    def render_action_text_attachments(content)
      content.render_attachments do |attachment|
        unless attachment.in?(content.gallery_attachments)
          attachment.node.tap do |node|
-           node.inner_html = render(attachment, in_gallery: false).chomp
+           node.inner_html = render_action_text_attachment attachment, locals: { in_gallery: false }
          end
        end
      end.render_attachment_galleries do |attachment_gallery|
        render(layout: attachment_gallery, object: attachment_gallery) do
          attachment_gallery.attachments.map do |attachment|
-           attachment.node.inner_html = render(attachment, in_gallery: true).chomp
+           attachment.node.inner_html = render_action_text_attachment attachment, locals: { in_gallery: true }
            attachment.to_html
          end.join.html_safe
        end.chomp
      end
    end

「サードパーティのWYSIWYG機能をそのまま使うのでなく、Railsの機能でWYSIWYGを作り込んでしまうと、フロントエンド側のメンバーもWYSIWYGを扱おうとするときにconflictが起きてしまうので、慎重に考えたいところです: 特にWYSIWYGは生成したHTMLや中間言語に変換されたテキストがRDBMSの中にデータとして保存されるので、一度使い始めると乗り換えが難しい」「たしかに!」

⚓ Redisの情報を取る#infoを追加


つっつきボイス:「RedisCacheStore#infoができたということは、Redisにもinfoというメソッドがありそう(しばらく探す): コマンドのところにあった↓」

参考: INFO – Redis

「このINFOコマンドにもstatsがあるから、たぶんそれに相当する情報を取得するんでしょうね」「あるなら欲しい値ですね」

# activesupport/lib/active_support/cache/redis_cache_store.rb#L
      def stats
        redis.with { |c| c.info }
      end

⚓ Linkヘッダーを無効にするオプションを追加

stylesheet_link_tagjavascript_include_tagを使うとLinkヘッダーを自動生成するサポートがPR #39939で追加された。しかしすべてをプリロードすべきとも限らない(例: IEはこのヘッダをサポートしていないのでレガシーIEスタイルシートへのリンクのプリロードは不要だし、ブラウザによってはstylesheet_link_tagjavascript_include_tagがIEの条件付きコメントの中に入り、使わない場合でもプリロードがトリガされる)。これによって帯域幅のコストが増加してアプリケーションのパフォーマンスが落ちる。
Linkヘッダーの複雑なニーズを抱えているサイトで柔軟性を高めるために、本コミットでは同ヘッダーを完全に無効にするオプションを追加して、Linkヘッダーの生成方法をアプリケーション側で決定できるようにする。
メモ: 本コミットの意図は、6-1-stableにバックポートすることと、new_framework_defaults_6_1.rbにそれ用のエントリを追加して、アプリケーションを6.1にアップグレードするときにデフォルトのオプションをどうするかを決められるようにすること。多くのサイトはレガシーIEの影響を受けるので。
同PRより大意


つっつきボイス:「stylesheet / javascript helperで読み込むコンテンツを常にプリロードしたいとは限らない」「IE対応している場合、RailsがLinkヘッダーを常に直接生成するのはうれしくないときがあるので、生成するかどうかを制御するようにした: たしかにこれが欲しい場合ありますね」「if lt IE 7みたいな書き方をするレガシーIE対応、最近だんだん見かけなくなってきましたけどそういえばありましたね↓」「あったあった」

参考: 条件付きコメントを使ったIE対策コーディング - HTML・CSSテックラボ - [SMART]

<!-- rfs.jpの同記事より -->

<!-- IE6以下のみ表示 -->
<!--[if lt IE 7]><html class="ie6" lang="ja"><![endif]-->

<!-- IE7のみ表示 -->
<!--[if IE 7]><html class="ie7" lang="ja"><![endif]-->

<!-- IE7のみ表示 -->
<!--[if IE 8]><html class="ie8" lang="ja"><![endif]-->

<!-- IE8より上、もしくはIE以外のみ表示 -->
<!--[if (gt IE 8)|!(IE)]><!--><html lang="ja"><!--<![endif]-->

⚓ Action Viewの#excerptヘルパーのパフォーマンスを改善

ここからはコミットリストから見繕いました。


つっつきボイス:「#excerptってメソッドあったのね↓」「excerptは”抜粋”か」「お〜、以下のQiita記事を見るとWeb上の長いテキストの一部を取り出してそれ以外を...とかに置き換えてくれるメソッドなのか」「なるほど、Googleの検索結果とか、表紙の記事リストで記事冒頭だけ抜粋するときとかで使われるヤツですね」「これ自分で実装したことあったんですが、それ用のメソッドがあったとは…」

参考: Rails TextHelperまとめ - Qiita

# api.rubyonrails.orgより
excerpt('This is an example', 'an', radius: 5)
# => ...s is an exam...

excerpt('This is an example', 'is', radius: 5)
# => This is a...

excerpt('This is an example', 'is')
# => This is an example

excerpt('This next thing is an example', 'ex', radius: 2)
# => ...next...

excerpt('This is also an example', 'an', radius: 8, omission: '<chop> ')
# => <chop> is also an example

excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
# => ...a very beautiful...

「Railsの#excerpt↓はsplitを使ってやってる: 当時の自分が実装した方法とは違うな」「今回改良したのは、このメソッドのコア部分のcut_excerpt_partのパフォーマンスだったんですね」

# File actionview/lib/action_view/helpers/text_helper.rb, line 175
def excerpt(text, phrase, options = {})
  return unless text && phrase

  separator = options.fetch(:separator, nil) || ""
  case phrase
  when Regexp
    regex = phrase
  else
    regex = /#{Regexp.escape(phrase)}/i
  end

  return unless matches = text.match(regex)
  phrase = matches[0]

  unless separator.empty?
    text.split(separator).each do |value|
      if value.match?(regex)
        phrase = value
        break
      end
    end
  end

  first_part, second_part = text.split(phrase, 2)

  prefix, first_part   = cut_excerpt_part(:first, first_part, separator, options)
  postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)

  affix = [first_part, separator, phrase, separator, second_part].join.strip
  [prefix, affix, postfix].join
end

「自分のときはどう実装したかな(一同で当時のコードを見る): RubyのStringScannerscan_untilを使って実装してた」「お〜、これですか」「このときは他の処理もやってたので少し複雑でしたね」

excerptヘルパー、いつからあったんだろう?」「コミットリストを見ると13 years agoとあるので随分前からあったらしい」「Railsにこれだけたくさんメソッドがあると、知らないものもまだまだありそうですね」

⚓ 番外: ルーティングテーブルのダークモード用CSSを修正


同PRより


つっつきボイス:「修正はシンプルですが、ダークモード対応がちょっと面白かったので拾いました」「1行おきに背景色を変えて縞々にしてる」「CSSのtr:nth-child(even)を追加するという方法でやってますね↓」

# actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb#L53
  @media (prefers-color-scheme: dark) {
+   body {
+     background-color: #222;
+     color: #ECECEC;
+   }
+
    #route_table tbody tr:nth-child(odd) {
      background: #333;
    }

+   #route_table tbody tr:nth-child(even) {
+     background: #444;
+   }
+

参考: :nth-child() - CSS: カスケーディングスタイルシート | MDN

⚓Rails

⚓ 2021年初頭にRailsの現状を展望する(Hacklinesより)


つっつきボイス:「見出しからして、Railsは2021年にもありか?という感じの記事かな」「はい、新年っぽいRailsの現状展望記事を拾いました」

「こういう記事のグラフやデータは、何を母集団とした調査かによって変わってくるので、そこを踏まえたうえで読みたい」「そうですね」「2021年のRailsコミュニティに関するセクションとか面白そう」「Railsは画期的に新しいサービスの構築に向いているという感じの締めくくりですね」

「眺めた限りですが、記事の立場としては割とRails擁護的かな: こういう記事に書かれているようなことは、Railsを長年使っている開発者ならだいたい心得ているものですが、これからRailsを学ぼうかどうしようか迷っている人にはよさそうですね」

「実はここ1年ほど、以下の名記事↓のような『Railsは今後もありか?』とか「Railsを選ぶ理由」とか「Railsは死んだ」的ないい英語記事がなかなか見当たらなくて、ウォッチで取り上げられそうなものがめったになかったんですよ」「お、そうでしたか😆」「記事の件数は多いんですが、この1年ほどは主観で書いたような記事ばかり目について、こういういい記事はもう出てこないんだろうかと思い始めていたところでした」

Railsは2018年も現役か?: 前編(翻訳)

Railsは2019年も「あり」か?#1(翻訳)

「言われてみればこの記事はややRails寄りっぽくはあるものの、エビデンスや出典を示すなど比較的ちゃんと書かれたもののように思えますね」「私もそんな気がしています: この記事なら翻訳してもいいかなと思いました」

⚓ 🌟 ActiveRecordExtended: RailsにPostgreSQL独自の機能を追加(Ruby Weeklyより)🌟

GeorgeKaraszi/ActiveRecordExtended - GitHub


つっつきボイス:「ActiveRecordExtendedはPostgreSQLを使うRails向けの拡張だそうです」「どれどれ」

「なるほど、ぽすぐれのArrayカラム用のANYやALLのような機能がRailsのメソッドとして使える↓: こういうのを自分で作ったことありました」「allは既存のメソッドと名前がかぶってますけどね」

# 同リポジトリより
alice = User.create!(tags: [1])
bob   = User.create!(tags: [1, 2])
randy = User.create!(tags: [3])

User.where.any(tags: 1) #=> [alice, bob]
# 同リポジトリより
alice = User.create!(tags: [1])
bob   = User.create!(tags: [1, 2])
randy = User.create!(tags: [3])

User.where.all(tags: 1) #=> [alice]

「IPアドレスを扱うメソッドなどもある↓」「全般にこのgemは、PostgreSQLに組み込まれている特定の型を活用するメソッドを提供するもののようですね: 使うかどうかは好みが分かれそうではありますが」「なるほど」

alice = User.create!(ip: "127.0.0.1/16")
bob   = User.create!(ip: "192.168.0.1/16")

User.where.inet_contains(ip: "127.0.0.254") #=> [alice]
User.where.inet_contains(ip: "192.168.20.44") #=> [bob]
User.where.inet_contains(ip: "192.255.1.1") #=> []

「やや、このgemはPostgreSQLのCTE(Common Table Expression)のメソッド↓をサポートしてるじゃない!!」「おぉ?」「これはちょっとアツいかも: CTEのためだけにこのgemを使ってもいいんじゃないかって思いました」「俄然色めき立ってきましたね」

# 同リポジトリより
alice = User.create!
bob   = User.create!
randy = User.create!
ProfileL.create!(user_id: alice.id, likes: 200)
ProfileL.create!(user_id: bob.id,   likes: 400)
ProfileL.create!(user_id: randy.id, likes: 600)

User.with(highly_liked: ProfileL.where("likes > 300"))
    .joins("JOIN highly_liked ON highly_liked.user_id = users.id") #=> [bob, randy]

「しかもwithjoinsをこうやってつなげられる↓」「おおぉ〜」「これでついにぽすぐれのWITH句が使える!」

User.with(highly_liked: ProfileL.where("likes > 300"), less_liked: ProfileL.where("likes <= 200"))
    .joins("JOIN highly_liked ON highly_liked.user_id = users.id")
    .joins("JOIN less_liked ON less_liked.user_id = users.id")

# OR

User.with(highly_liked: ProfileL.where("likes > 300"))
    .with(less_liked: ProfileL.where("likes <= 200"))
    .joins("JOIN highly_liked ON highly_liked.user_id = users.id")
    .joins("JOIN less_liked ON less_liked.user_id = users.id")

「こんなふうにあっさりPostgreSQLのCTEを使えるというのは相当なものですね👍」「今までActive RecordではWITH句をうまく書けないなってずっと思っていたんですが、こんなふうに書けるなら使ってみたい」「後はこれをどのぐらいハードに使っても大丈夫なのかが知りたいですね」「単純なCTEは十分動きそうな感じ」

「CTEサポートって、ありそうでなかったんですね」「冒頭のanyallみたいなのは簡単にメソッドにできるんですけど、CTEだと以下のようにWITH句が先行するので↓、単純に#fromの引数などを使って書き換えたりできないんですよ: でもこのgemでやっているということは可能なのか」

WITH "highly_liked" AS (SELECT "profile_ls".* FROM "profile_ls" WHERE (likes >= 300))
SELECT "users".*
FROM "users"
JOIN highly_liked ON highly_liked.user_id = users.id

「どうやって実現してるんだろう?」「リポジトリを掘ると、このあたり↓でWITHを増やしてますね」「merge_ctes!build_withとか、いろいろ頑張ってる感ある」「やるな〜」

#ActiveRecordExtended/lib/active_record_extended/active_record/relation_patch.rb#7
module ActiveRecordExtended
  module RelationPatch
    module QueryDelegation
      delegate :with, :define_window, :select_window, :foster_select, to: :all
      delegate(*::ActiveRecordExtended::QueryMethods::Unionize::UNIONIZE_METHODS, to: :all)
      delegate(*::ActiveRecordExtended::QueryMethods::Json::JSON_QUERY_METHODS, to: :all)
    end

    module Merger
      def normal_values
        super + [:union, :define_window]
      end

      def merge
        merge_ctes!
        super
      end

      def merge_ctes!
        return unless other.with_values?

        if other.recursive_value? && !relation.recursive_value?
          relation.with!(:chain).recursive(other.cte)
        else
          relation.with!(other.cte)
        end
      end
    end

    module ArelBuildPatch
      def build_arel(*aliases)
        super.tap do |arel|
          build_windows(arel) if window_values?
          build_unions(arel)  if union_values?
          build_with(arel)    if with_values?
        end
      end
    end
  end
end

「CTEの機能だけ切り出してくれる方がいいのかも?」「他の機能は単なる拡張ですし、別にあっても困らないので大丈夫ですよ」「あ、そうでしたか」「へ〜、window関数やUNIONもこのgemで使えるようですが、こういう機能が一緒に入っても構いませんし、あれば使うかもしれませんね」「なるほど」「ALLやANYのためにこういうgemを入れようとは思いませんが、このgemでないとできない機能であるCTEサポートメソッドを使えるなら入れたい」

「ActiveRecordExtended、誰もがやりたいと思っていたCTEのメソッド化を実現したのは個人的にポイント高いですね: どれ、GitHubの★ポチっちゃおう」「私もポチりました: 久々にウォッチで🌟が出せます」

というわけで🌟を進呈します。おめでとうございます!

追いかけボイス:「CTE(WITH句)自体はPostgreSQL独自のものではなく、最近のMySQLでもサポートしているんですが(SQL標準ではSQL99以降)、このGemはMySQLで使えるとは書いていないのでMySQL版のCTE Gemも探せばあるかもしれないですね」

参考: MySQL :: MySQL 8.0 Reference Manual :: 13.2.15 WITH (Common Table Expressions)

⚓ Sidekiqやgood_jobがRactor導入を構想中(Ruby on Rails Discussionsより)

I’m the author of GoodJob 13; I plan to support Ractors once Ractors are supported in Rails. I’ve also seen Mike Perham mention similarly 15 about Sidekiq.
同コメントより(by bensheldon)

mperham/sidekiq - GitHub

bensheldon/good_job - GitHub

good_jobは以前取り上げました(ウォッチ20200803)。


つっつきボイス:「Rails Discussionsでたまたま見かけたんですが、sidekiqの作者とgood_jobの作者がRactorを取り入れたいと発言してました: まだ構想を述べてる段階ですが」「この流れはわかりますね: この辺のgemを作ってる人はRactor使いたいと思いますよ」「Ractorやりたいでしょうね」

「ジョブのスレッドをRactor化することはできると思いますけど、Railsのジョブはいろんなものを読み込むので、Ractorによってパフォーマンスがどのぐらいよくなるかはやってみないとわからないかもしれませんね」「たしかに」「RactorはRubyのクラス変数に自由にアクセスできなかったと思うので、Ractorを使うにはgemの改造が必要になるのかもしれないとちょっと思いました」「ふ〜む」

参考: ruby/ractor.md at master · ruby/ruby

⚓ その他Rails


つっつきボイス:「先週StimulusReflexの記事↓を書いてくれたWebチームのebiさんが読みたそうかと思って貼ってみました」

StimulusReflex の Setup~Quick Start をやってみる


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210113後編)Ruby 3.0 Ractor解説記事、Vercelホスティングサービス、教育用OS xv6ほか

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

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

Rails公式ニュース

Ruby on Rails Discussions

Ruby Weekly

Hacklines

Hacklines


週刊Railsウォッチ(20210120後編)Ruby 3.0の新機能で遊ぶ、RubyスニペットをJSに変換するRuby2JS、rspec-parameterized gemほか

$
0
0

こんにちは、hachi8833です。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

⚓Ruby

⚓ Ruby3.0の新機能で遊んでみた(Ruby Weeklyより)


つっつきボイス:「そうそう、これこの間ちょっと読みました: いい記事👍」「なるほど、画面に映っているブラウザで記事リンクがクリック済みになってるのが見えますね」「右代入記号=>編注: 右代入記号については本項末尾を参照)はこう使えるとか、パターンマッチングの話などの新機能の使い方が書かれていて、短いけど読み応えあります」

「たとえばこれ↓、=>でこんなことができます」「ええと、真ん中の1と2はそのままで、両側にある要素をsplatの*で取り出せる…のか!」「=>の右辺と左辺で共通する1と2でピン留めする感じでこうやって両端を取り出せるんですよ」「へ〜!」「ちょっと不気味かも…」「Rubyコミッターの@mametterさんが会心の笑みを浮かべそう」「こう書けたらうれしいよね、と思いつつやってみたら本当にできたという驚きがありますね」

# 同記事より
[-1, 0, 1, 2, 3] => [*left, 1, 2, *right]

# left == [-1, 0], right == [3]

「この書き方を何て呼んだらいいのかわからない…」「お気持ち代入とでも呼んでみたくなる」「人間の直感には沿っていますよね」「動くのがわかっていてもちょっぴり不安になりますね」「なるなる」

「さっきと同じような中間のピン留めを変数でもやりたい場合は、以下のようなpin operator ^が使える↓」「おぉ〜、^ってこうやって使えるんですね」「中間の変数は代入の対象ではなく、その変数の評価値をピン留めに使って欲しいということを^で伝えないと、Rubyがどう代入したらいいのか判断できないはずなので、この^がパターンマッチングへのヒント的に渡されるんでしょうね」「なるほど!」

# 同記事より
int = 1

[-1, 0, 1, 2, 3] => [*left, ^int, *right]

# left == [-1, 0], right == [2, 3]

「この記事を読んで、パターンマッチングと=>でこんなことができるんだという発見がいろいろあったのがとてもよいです」「同感ですね」「=>を入れたがってた人たちはたぶんこういうことを考えていたのかなと想像して思わず感動しました」「この記事好き!」

⚓ =>はパターンマッチング用

「同じことを普通の代入でもできないのかな?」「Rubyのドキュメント↓を見ると、どうやら=>は右代入ではなくてパターンマッチング構文の一部ということみたいですね」「あ、ホントだ」「記号の形は右代入っぽく見えますよね」「Ruby難しい…」

# docs.ruby-lang.orgより
<expression> => <pattern>

<expression> in <pattern>

後で通常の代入=で試してみるとエラーになりました。

irb(main):001:0> [*left, 1, 2, *right] = [-1, 0, 1, 2, 4]
Traceback (most recent call last):
        3: from /Users/hachi8833/.anyenv/envs/rbenv/versions/3.0.0/bin/irb:23:in `<main>'
        2: from /Users/hachi8833/.anyenv/envs/rbenv/versions/3.0.0/bin/irb:23:in `load'
        1: from /Users/hachi8833/.config/anyenv/envs/rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>'
SyntaxError ((irb):1: syntax error, unexpected '=', expecting end-of-input)
[*left, 1, 2, *right] = [-1, 0, 1, 2, 4]

⚓ Ruby2JS: RubyスニペットをJSに変換する(Ruby Weeklyより)

ruby2js/ruby2js - GitHub


つっつきボイス:「RubyコードをJavaScriptに変換するといえば、@youchanさんがRubyKaigiなどで精力的に発表しているOpalが知られていますけど、他にもあるとは」

opal/opal - GitHub

「記事を見てみた感じでは、Ruby2JSはこういうRubyの小さなコードスニペットをJSに変換するのが中心のようですね」

# 同記事のRuby2JSのサンプルより: Rubyコード
class MyClass
  def my_method(str)
    ret = "Nice #{str} you got there!"
    ret.upcase()
  end
end
# 同記事のRuby2JSのサンプルより: 変換後のJavaScript
class MyClass {
  myMethod(str) {
    let ret = `Nice ${str} you got there!`;
    return ret.toUpperCase()
  }
}

「Opalの場合は以下のようにもっと大きなJSコードに変換される↓」「おぉ」「Opalが持つランタイムを呼び出すためなので、コードが増えるのはある程度仕方がない」「Ruby2JSとOpalの使い分けについても記事に書かれていますね」

/* Generated by Opal 1.0.3 */
(function(Opal) {
  var self = Opal.top, $nesting = [], nil = Opal.nil, $$$ = Opal.const_get_qualified, $$ = Opal.const_get_relative, $breaker = Opal.breaker, $slice = Opal.slice, $klass = Opal.klass;

  Opal.add_stubs(['$upcase']);
  return (function($base, $super, $parent_nesting) {
    var self = $klass($base, $super, 'MyClass');

    var $nesting = [self].concat($parent_nesting), $MyClass_my_method$1;

    return (Opal.def(self, '$my_method', $MyClass_my_method$1 = function $$my_method(str) {
      var self = this, ret = nil;


      ret = "" + "Nice " + (str) + " you got there!";
      return ret.$upcase();
    }, $MyClass_my_method$1.$$arity = 1), nil) && 'my_method'
  })($nesting[0], null, $nesting)
})(Opal);

⚓ rspec-parameterized: テスト構文をパラメータ化

tomykaira/rspec-parameterized - GitHub


つっつきボイス:「これまで知らなかったんですが、rspec-parameterizedは日本のRubyistの方が中心になって作っているgemのようです」「なるほど、README冒頭のサンプルコード↓でやりたいことは見当が付きました」

# 同リポジトリより
# Table Syntax Style (like Groovy spock)
# Need ruby-2.1 or later
describe "plus" do
  using RSpec::Parameterized::TableSyntax

  where(:a, :b, :answer) do
    1 | 2 | 3
    5 | 8 | 13
    0 | 0 | 0
  end

  with_them do
    it "should do additions" do
      expect(a + b).to eq answer
    end
  end
end

「たとえば上のコードの真ん中に置かれている表の要素を変数に順次入れて調べるテストコードを書ける、というものですね」「なるほど、こうやって表の形でデータを与えられるのか」

「普通の書き方だとテストケースが増えたときにテストが網羅できているかどうかを把握するのが難しくなってくることもあるので、普段は全ケースをテストしないとしても、網羅できてるかどうかが不安なときにこういうgemを要所要所で使うのもよさそう👍」「なるほど」「テストの書き方によっては表の項目を増やすとテストの実行時間が指数関数的に増加する可能性もあるので、テストケースをどれくらい増やすのかは実行時間とのバランスも見ながら考えたいですね」

⚓ その他Ruby

rbspy/rbspy - GitHub

Rails: Kubernetes内のRubyプロセスをrbspyでデバッグする(翻訳)


つっつきボイス:「rbspyなどの作者であるJulia Evansさんのブログですが、docker-compose↓を初めて使って感激したと書かれていたのがちょっと意外だったので拾ってみました」

docker/compose - GitHub

「Julia Evansさんの記事を覗いてみると、docker-composeでDNSサーバーを誰が動かしているのかが早速気になって調べているのがスゴい」「さすがですね」

「自分もこの間BPS社内勉強会で発表する前に同じことが気になって調べたので気持ちわかります」「そうだったんですね」「docker-composeを使っているうちに気がつくとついついdocker-composeの中の仕組みを追いかけていたという感じですか」「だって気になるじゃないですか」「わかりますその気持!」

「docker-composeといえば、docker contextにAWS ECSを追加することで、ローカルで実行するdockerコマンドのDocker HostとしてAWSのECSやFargateを使うことができるようになりましたね↓」

参考: Docker ComposeによるAmazon ECS対応がGAに!コンテナをローカル環境と同じノリでECS環境で起動できるぞ!! | Developers.IO


追いかけボイス:「参考までに、以下はECSのタスク定義にdocker-compose構文が使えるというECSのドキュメントです」

参考: Docker Compose ファイル構文の使用 - Amazon Elastic Container Service

⚓DB

⚓ activerecord-postgres_enum: PostgreSQLのenum型をサポート(Ruby Weeklyより)

bibendi/activerecord-postgres_enum - GitHub


つっつきボイス:「TechRachoの翻訳記事でお馴染みのEvil Martiansがスポンサーになっているgemです」「activerecord-postgres_enum、名前から想像が付く感じですね」

⚓ RailsとPostgreSQLのenum型

「ところでRailsではPostgreSQLのenum型を標準でサポートしているのかな?」「あ、どうだったかな…」「記事をググってみると、自力でPostgreSQLのenumをサポートしないといけない感じなのかな」「うーむ」

「RailsにEnumってありませんでしたっけ?」「あ、それは以下のActiveRecord::Enumのことですよね?値をintegerなどに変換して持つヤツ」「あ、そうでした😅」「今話しているのはPostgreSQLがサポートしているEnum型の方です」

「RailsのEdge Guides↓を見ても、PostgreSQLのサポートするenumについては”Currently there is no special support for enumerated types.”と書かれている」「ドキュメント自体WIP(work in progress: 作業中)となっていますね」「しかもEdge GuideにはPostgreSQLのEnumははVALUEをDROPできないと書かれてる…」「ありゃ」

参考: Active Record and PostgreSQL — Ruby on Rails Guides

「個人的にはですが、Active RecordのEnumよりもぽすぐれのEnumの方がどちらかというと好きかな: Active Recordの層で変換するかPostgreSQLの層で変換するかの違いですけど、Rails以外でPostgreSQLを使うときはよくEnumを使っています」


追いかけボイス:「せっかくなのでactiverecord-postgres_enum gemとRailsガイドのマイグレーション方法を並べてみました」

# activerecord-postgres_enum gemで書けるマイグレーション(同リポジトリより)
create_enum :mood, %w(happy great been_better)
create_table :person do
  t.enum :person_mood, enum_name: :mood
end
# Rails Edge Guidesのマイグレーション(Edge Guidesより)
def up
  execute <<-SQL
    CREATE TYPE article_status AS ENUM ('draft', 'published');
  SQL
  create_table :articles do |t|
    t.column :status, :article_status
  end
end
# NOTE: It's important to drop table before dropping enum.
def down
  drop_table :articles
  execute <<-SQL
    DROP TYPE article_status;
  SQL
end

追いかけボイス:「RailsのActiveRecord::EnumではなくDBMSのENUMを使うメリットについて補足: ActiveRecord::EnumだとDBに入っている数値と意味の対応をソースコードから読み解く必要があるので一手間かかるのに対し、DBMSのENUMを使う場合は実行されるSQLにhuman readableなVALUEが含まれるようになるので、RailsだけでなくRedashやBIツールとかからデータ参照するSQLを書いたり読んだりするときにぱっと見で読みやすくなる、というものがあります」

「もちろん値変換済みのVIEWを別途作っておけばよいのですが、『DB定義書やソースは手元にないけど、DBに入ってるデータをとりあえずSELECTして眺めたい』というようなケースは運用上よくあることなので、そういうときには便利ですね」

参考: Redash helps you make sense of your data

⚓その他

⚓ NandGame


つっつきボイス:「BPSの社内Slackにあがっていて知りました」「そうそう、これ面白そうなロジックゲームですよね、自分はやってませんけど」「上の記事タイトルにネタバレ注意があるので、見ないように頑張ってます!」

「NandGameって、教育用途ではなくてゲームということかな?」「教育用にも使えそうですけどね」「いかにも最近の大学で使ってそうですし、これで全加算器とか半加算器とかを作れるならよさそう」「たしかに原理的にはNAND回路を組み合わせればどんなロジックでも作れますね」「まあそうなんですけど😆」「デジタル論理回路の理解を助けるゲーム、いいですよね」

参考: NANDゲート - Wikipedia

「へ〜、ラッチ回路やらALUやらいろいろある」「こういうのはだいたいパタヘネ本↓を読めば作れるようになります」「たしかに😆」「NandGame、さすがにOpcodesあたりから先の複雑な回路はどう組んだらいいかすぐには見当がつかない…」(以下延々)


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210113後編)Ruby 3.0 Ractor解説記事、Vercelホスティングサービス、教育用OS xv6ほか

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

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

Ruby Weekly

Serverless Status

serverless_status_banner

Rails 6.1で form_withのデフォルトが「remoteなし」に戻った(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Rails 6.1で form_withのデフォルトが「remoteなし」に戻った(翻訳)

Railsのベテランなら、従来のあらゆるformヘルパーと同様、form_withヘルパーもHTMLレスポンスを返すことを期待するでしょう。しかし、form_withのデフォルトオプションはAjaxレスポンスを返すように変わっていました(訳注: これはRails 6.0と5.2が該当します: )。

この変更によってJavaScriptレスポンスがレンダリングされると、Railsの初心者が戸惑いがちでした。幸い、ごく最近のプルリク(#40708)によってこの変更が取り消され、デフォルトでHTMLレスポンスをレンダリングするようになりました。

改修前

改修前のform_withは、デフォルトでremoteフォームを生成しました。

<%= form_with url: tags_url do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end >

上のerbコードはフォームをAjaxリクエストとして送信します。この挙動はlocalオプションで変更可能です。local: trueを指定すると、フォームを従来のHTMLリクエストとして送信します。

改修後

改修後の新しいRails 6.1アプリケーションは、すべてデフォルトでremoteなしのフォームを生成するようになりました。Railsフォームのこのデフォルトオプションは、以下の設定で上書きすることもできます。

config.action_view.form_with_generates_remote_forms

このform_with_generates_remote_formsにはtrueまたはfalseを指定できます。

関連記事

Rails 5.1〜6: ‘form_with’ APIドキュメント完全翻訳

Rails 5.1〜6.1: ‘form_with’ APIドキュメント(翻訳)

$
0
0
  • 更新(2019/09/13): Rails 6.0時点のドキュメント更新を反映。
  • 更新(2020/03/09): Rails 6で削除された項目を反映。
  • 更新(2021/01/22): Rails 6.1の変更を反映(edge APIドキュメントに基づいています)。6.1 edgeドキュメントで追加または変更されたパラグラフ冒頭には「(6.1 edge)」を追加しています。

こんにちは、hachi8833です。先週リリースされたRails 5.1の目玉機能のひとつである#form_withのAPIドキュメントを翻訳いたしました。

なお、5.1より前のform_forform_tagはその後非推奨になりました。5.1以降はこのform_withだけを使いましょう。

参考: Provide form_with as a new alternative to form_for/form_tag · Issue #25197 · rails/rails


概要

MITライセンスに基づいて翻訳・公開いたします。

原文の更新や訳文の誤りにお気づきの方は、@hachi8833までお知らせください。

Rails 5.1〜6.1: #form_with APIドキュメント(翻訳)

# API呼び出し
form_with(model: nil, scope: nil, url: nil, format: nil, **options)

URL、スコープ、モデルの組み合わせを元にformタグを作成します。

⚓ URLのみを指定する

<%= form_with url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>

# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="title">
</form>

⚓ inputフィールド名にスコープのプレフィックスを追加する

<%= form_with scope: :post, url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>

# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

⚓ 渡されたモデルからURLとスコープを自動推測する

<%= form_with model: Post.new do |form| %>
  <%= form.text_field :title %>
<% end %>

# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

⚓ 既存のモデルを更新するフォームで、モデルの値をフィールドに表示する

<%= form_with model: Post.first do |form| %>
  <%= form.text_field :title %>
<% end %>

# =>
<form action="/posts/1" method="post" data-remote="true">
  <input type="hidden" name="_method" value="patch">
  <input type="text" name="post[title]" value="<postのtitle>">
</form>

⚓ フォームのフィールドは、必ずしもモデルの属性と対応してなくてもよい

<%= form_with model: Cat.new do |form| %>
  <%= form.text_field :cats_dont_have_gills %>
  <%= form.text_field :but_in_forms_they_can %>
<% end %>

# =>
<form action="/cats" method="post" data-remote="true">
  <input type="text" name="cat[cats_dont_have_gills]">
  <input type="text" name="cat[but_in_forms_they_can]">
</form>

フォームのパラメータは、コントローラでパラメータのネストに沿ってアクセスできます。つまり、inputフィールドにtitlepost[title]というフィールド名がある場合、コントローラではそれぞれparams[:title]params[:post][:title]としてアクセスできます。

フォームのinputフィールド名 コントローラのparams
title params[:title]
post[title] params[:post][:title]

更新(2021/01/22)
6.1 edge)このパラグラフはRails 5.1〜6.0まで存在しましたが、6.1で削除されました。詳しくは『Rails 6.1で form_withのデフォルトが「remoteなし」に戻った(翻訳)』をどうぞ。

6.1 edge)上述のコード例では、比較しやすさのため送信ボタンを省略しています。また、UTF-8サポートを有効にする自動生成のhiddenフィールドや、CSRF(Cross Site Request Forgery)保護に必要な認証トークンも省略しています。

⚓ リソース指向のスタイル

上述のコード例の多くでは、単にform_with:modelを渡しています。これはRESTfulなルーティングのセットに対応しており、そのほとんどはconfig/routes.rbのresourcesで定義されます。

したがって、そうしたモデルのレコードを1件渡せば、RailsがURLやメソッドを推測します。

<%= form_with model: @post do |form| %>
  ...
<% end %>

上のコードは以下のようなコードと同等になります。

<%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>
  ...
<% end %>

新規レコードについても同様です。

<%= form_with model: Post.new do |form| %>
  ...
<% end %>

上のコードは以下のようなコードと同等になります。

<%= form_with scope: :post, url: posts_path do |form| %>
  ...
<% end %>

⚓ #form_withで利用できるオプション

:url
フォームの送信先URLを指定します。
渡せる値は、url_forlink_toで渡せる値と似ています。たとえば、名前付きルートを直接渡すこともできますし、:urlなしで:scopeを渡すと、現在のURLにフォームを送信することもできます。
:method
フォーム送信時のHTTPメソッド(verb)を指定します。
通常は:get:postを指定します。
:patch:put:deleteを指定すると、隠しinput名の後ろに_methodが追加され、POST verb上でこれらのHTTP verbをシミュレートします。
:format
フォーム送信先であるルーティングのフォーマットを指定します。
:jsonなど通常と異なるリソースタイプを送信するのに便利です。
:urlがオプションに渡されている場合、このオプションはスキップされます。
:scope
inputフィールド名のプレフィックスにスコープを追加します。これにより、送信されたパラメータをコントローラでグループ化できます。
:namespace
フォームの要素でid属性を一意にする名前空間です。namespace属性で指定した名前にアンダースコアを追加したものが、生成されたHTML idの前に追加されます。
:model
:url:scopeの自動推測に使うモデルオブジェクトを指定し、inputフィールドにモデルの値を表示します。
たとえば、title属性の値が"Ahoy!"ならtitleの入力フィールドの値に"Ahoy"と表示されます。
モデルが新しいレコードの場合は作成用フォームが生成され、モデルが既存のレコードの場合は更新用フォームが生成されます。
デフォルトの動作を上書きするには、:scope:urlを渡します(params[:post]params[:article]に変更するなど)。
:authenticity_token
フォームで使う認証トークンを指定します。
カスタムの認証トークンを指定して上書きすることも、falseを渡して認証トークンのフィールドをスキップすることもできます。
有効なフィールドのみに制限されている支払用ゲートウェイへのような外部リソースにフォームを送信する場合に便利です。
config.action_view.embed_authenticity_token_in_remote_forms = falseを指定すると、埋め込み認証トークンがremoteフォームで省略されることがあります。この指定はフォームでフラグメントキャッシュを使う場合に便利です(remoteフォームがmetaタグから認証トークンを取得するようになるので、JavaScriptがオフになっているブラウザをサポートする場合を除けば認証トークンをフォームに埋め込む必要がなくなります)。
:local
6.1 edge)デフォルトでは、フォームを典型的なHTTPリクエストとして送信します。local: falseを指定するとremoteの「控えめな(unobtrusive)」XHR送信が有効になります。Railsの設定でconfig.action_view.form_with_generates_remote_forms = trueを設定すると、remoteのXHR送信をデフォルトで有効にできます。
:skip_enforcing_utf8
trueを指定すると、送信時にutf8という名前の隠しフィールドが出力されなくなります。
:builder
フォームのビルドに使うオブジェクトをオーバーライドします。
:id
HTMLのid属性を指定します(オプション)。
:class
HTMLのclass属性を指定します(オプション)。
:data
HTMLのdata属性を指定します(オプション)。
:html
上以外のHTML属性を使う場合に指定します(オプション)。

⚓

#form_withにブロックを渡さない場合は、開始formタグを生成します。

# 名前付きパスを指定する場合
<%= form_with(model: @post, url: super_posts_path) %>

# スコープを追加する場合
<%= form_with(model: @post, scope: :article) %>

# フォーマットを指定する場合
<%= form_with(model: @post, format: :json) %>

# トークンを無効にする場合
<%= form_with(model: @post, authenticity_token: false) %> 

ルーティングをadmin_post_urlのように名前空間化する場合は以下のようにします。

<%= form_with(model: [ :admin, @post ]) do |form| %>
  ...
<% end %>

たとえばリソースに関連付けが定義されているとします。ルーティングが正しく設定されているdocumentにcommentを追加したい場合は次のようにします。

<%= form_with(model: [ @document, Comment.new ]) do |form| %>
  ...
<% end %>

上のdocumentには@document = Document.find(params[:id])が既に与えられているとします。

更新(2020/03/09)以下はRails 6.0で削除されました。

⚓ 他のフォームヘルパーと組み合わせる

#form_withではFormBuilderオブジェクトが使われていますが、単独のFormHelperのメソッドやFormTagHelperのメソッドと共存させることもできます。

<%= form_with scope: :person do |form| %>
  <%= form.text_field :first_name %>
  <%= form.text_field :last_name %>

  <%= text_area :person, :biography %>
  <%= check_box_tag "person[admin]", "1", @person.company.admin? %>

  <%= form.submit %>
<% end %>

同様に、FormOptionsHelperのメソッド(FormOptionsHelper#collection_selectなど)と共存させたり、DateHelperのメソッド(ActionView::Helpers::DateHelper#datetime_selectなど)と共存させることもできます。

⚓ HTTPメソッド(verb)の指定方法

以下のHTTP verbの完全な配列をoptionsハッシュに渡すことができます。

method: (:get|:post|:patch|:put|:delete)

verbがGETPOST以外の場合(この2つはHTMLフォームでネイティブでサポートされます)、フォームそのものにはPOST verbが設定され、_methodという名前の隠しinputフィールドには指定の verbが設定され、後者がサーバーで解釈されます。

⚓ HTMLオプションの設定方法

HTMLのdata-*属性はdata:ハッシュで直接渡せますが、id:class:を含む他のすべてのHTMLオプションについては次のようにhtml:ハッシュの中に置く必要があります。

<%= form_with(model: @post,
              data: { behavior: "autosave" },
              html: { name: "go" }) do |form| %>
  ...
<% end %>

上のコードから以下のHTMLが生成されます。

<form action="/posts/123" method="post" data-behavior="autosave" name="go">
  <input name="_method" type="hidden" value="patch" />
  ...
</form>

⚓ 非表示のモデルidを出力しないようにする

#form_withメソッドを使うと、自動的にモデルidが隠しフィールドとしてフォームに含まれます。このモデルidは、フォームデータとそれに関連付けられているモデルとの関連を保つために使われます。

ORMシステムによってはネストしたモデルでこうしたidを使わないものもあるので、その場合は次のようにinclude_id: falseを指定することで隠しフィールドのモデルidを出力しないようにできます。

<%= form_with(model: @post) do |form| %>
  <%= form.fields(:comments, skip_id: true) do |fields| %>
    ...
  <% end %>
<% end %>

上の例では、NoSQLデータベースにPostというモデルがひとつと、それに一対多で関連付けられるCommentというモデルが保存されています。:commentsには主キーはありません。

⚓ フォームビルダをカスタマイズする

FormBuilderクラスをカスタマイズしてフォームをビルドすることもできます。カスタマイズするには、FormBuilderを継承してサブクラスを作り、必要なヘルパーメソッドを定義またはオーバーライドします。

次のコード例では、フォームのinputにラベルを自動追加するヘルパーを作成済みであることが前提です。

<%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
  <%= form.text_field :first_name %>
  <%= form.text_field :last_name %>
  <%= form.text_area :biography %>
  <%= form.check_box :admin %>
  <%= form.submit %>
<% end %>

上のようにコードを書いてから、次のコードを書きます。

<%= render form %>

これにより、people/_labelling_formというテンプレートを使ってレンダリング(=HTML生成)され、フォームビルダを参照するローカル変数の名前はlabelling_formになります。

特に指定しない限り、カスタムのFormBuilderクラスは、ネストした#fields_for呼び出しのオプションと自動的にマージされます。

上のようなコードを別のヘルパーにも含めておきたい場合は、以下のように書くこともできます。

def labelled_form_with(**options, &block)
  form_with(**options.merge(builder: LabellingFormBuilder), &block)
end

関連記事

Rails 6.1で form_withのデフォルトが「remoteなし」に戻った(翻訳)

週刊Railsウォッチ(20210125前編)Railsリポジトリのデフォルトブランチがmainに変更、Rails 6.1はMySQLのENUM型に対応済みほか

$
0
0

こんにちは、hachi8833です。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今回は以下のコミットリストを中心に見繕いました。

⚓ type_for_attribute周りを修正

マージされたプルリク#39929によって、cast_type (:encrypted)でprev_decoratorstoreの中のserialize)を用いると#41138のリグレッション(注: 修正したバグが再発すること)が発生した。

複数のデコレーション(元の属性のstore:encrypted)はこれまで全くサポートされていなかったので、これまで動いていたのは偶然に過ぎないが、サブクラスの型の省略を許すことと引き換えに複数のデコレーション機能が動かなくなるようにはしたくない。

個人的には、たとえ以下のようにサブクラス側で型を省略可能だとしても、開発者がサブクラス側で型を省略することはおすすめしたくない。

# 同PRより
# app/models/post.rb
class Post < ActiveRecord::Base
  attribute :x, :integer
end

# app/models/special_post.rb
class SpecialPost < Post
  # :integerは省略可能だが、明快さが落ちる
  attribute :x, default: 42

  # :integerは以下のように省略なしで記述することを推奨
  attribute :x, :integer, default: 42
end

コメント94ba417#r41923157の視点もこれに似ている。

修正されたissue: #41138
同PRより大意

参考: リグレッション(りぐれっしょん) - ITmedia エンタープライズ


つっつきボイス:「そういえばRailsのActiveRecord::Attributesにはこういう機能がありますね(自分はこの書き方はあまりしないんですが): 現在は上のように、元のクラスを継承した後でActiveRecord::Attributesを上書きすると、元のattributeのintegerが上書きされて消えることになる」「おぉ」「上のコードの末尾にあるこの:intergerは、実際には省略しないで明示的に書くべきという指摘もされてますね」

「issue #41138の方が見やすいかな↓: 元のコード(上)にあったものが、継承したコード(下)で上書きされている」「なるほど!」「プルリクはこのリグレッションの修正ということですね」「修正に伴って#39929も取り消されていました」

# issue #41138より
class Cache < ApplicationRecord
  store :data
  encrypts :data
end
# issue #41138より
def encrypts(name)
  attribute name, :encrypted, subtype: type_for_attribute(name)
end

「元のクラスの他のattributeは自分が使わないとしても、storeencryptedあたりは使うこともあるでしょうから、知らないうちにこの問題を踏むということはありそう」「名前が重複するから、これまで上書きされていたのも無理はないか」「内部の処理が見えないと、この問題を踏んでも何が起こったのか見当がなかなかつかなくて厄介でしょうね」

Rails5: ActiveRecord標準のattributes APIドキュメント(翻訳)

⚓ timeのunknown type errorを定義時にraiseするようにした


つっつきボイス:「これもActiveRecord::Attributesの改修かな: unknown type errorをランタイム時ではなく定義時に出力するように変わってますね」

# activerecord/lib/active_record/attributes.rb#L215
        when Symbol
-         type = cast_type
-         cast_type = -> _ { Type.lookup(type, **options, adapter: Type.adapter_name_from(self)) }
+         cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self))

「postgresql/datatype_test.rbのテストにも手が入っているから、PostgreSQLアダプタ関連の修正なのかな」「#41166のコメントを見ると、PostgreSQLアダプタでの場合分けが必要だったみたい」

「定義時にエラーとわかったらその時点でエラーにしようよということですね」「検知できるエラーは早いタイミングで出すべきというのはもっともですね」

⚓ 高速化: empty?length == 0に変更


つっつきボイス:「こちらは高速化のプルリクです」「条件のassociations.nil?を冒頭に移動したのと、records.empty?records.length == 0に変えたのと、2つの変更が行われてますね↓」「あ、タイトルの変更以外にもうひとつ変更があったのか」「associations.nil?の方が処理が速いのかもしれない」

# activerecord/lib/active_record/associations/preloader.rb#L100
      def call
-       return [] if records.empty? || associations.nil?
+       return [] if associations.nil? || records.length == 0

        build_preloaders
      end

「改修後は1.3倍ほど速くなったようです↓」「そういえばRubyの書き方としては== 0より.empty?の方がRubyらしいとよく言われますね」「== 0にするとメソッド呼び出しを行わなくなる分速くなるのかな?」「ともあれ、この測定結果だけでは、== 0への変更とassociations.nil?の移動のどちらがどれだけ効いているかはわからないですね」「たしかに」

before:

Warming up --------------------------------------
prefill associations   252.000  i/100ms
Calculating -------------------------------------
prefill associations      2.549k (± 1.4%) i/s -     12.852k in   5.043288s
After:

Warming up --------------------------------------
prefill associations   335.000  i/100ms
Calculating -------------------------------------
prefill associations      3.361k (± 0.5%) i/s -     17.085k in   5.082847s

【保存版】Rubyスタイルガイド(日本語・解説付き)総もくじ

⚓ rails newのGitデフォルトブランチ指定オプションを--mainに変更


つっつきボイス:「お、ついにrails newするときのGitデフォルトブランチ名指定オプションが--masterから--mainに変わったのか」「はい、とうとう」「今はいろんなところでGitのデフォルトブランチ名をmainにするのが世の中の流れですよね」「--no-master--no-mainに変わってる」

参考: 新しいGitHubリポジトリではmainブランチがデフォルトに


なお昨年にも同様のPRが上がっていたのを見つけましたが、こちらはopenのままです↓。

🔗 Railsリポジトリのmasterブランチもmainに変更された

「上の--mainオプションの他にこの変更も行われました」「--mainなしのrails newでもmainブランチができる…のかと思ったら、RailsフレームワークそのもののGitHubリポジトリまでデフォルトブランチがmainブランチに変わったのか!」「はい、『先週の改修』用に今週のコミットリストを取ろうとしたらいつものURLが無効になっていたことで気が付きました↓」


github.com/rails/railsより

「ついにRails本体までmainに変わるのかー」「RailsのCIもいろいろ変えないといけなくなるんでしょうか?」「Rails本体のmasterが急に消えるとえらいことになりそうなので、さすがにしばらくはmastermainが共存しそうな気がしますけどね(調べる): …本当にmasterブランチない↓」

「もしかするとmasterがGitHubのプルダウンにないだけで、masterブランチそのものは取れるんじゃないかな?: やっぱりなかった↓」「完全にmainに移行しちゃったんですね…」

「普通はあまりしないと思いますが、自分たちのCIでGitHubのrails/railsリポジトリを指定するときに気を利かせてmasterブランチまで指定していたら落ちそうではある」「あ、そうかも」


念のため自分も後でやってみましたが、やはりmasterブランチはありませんでした。

$ git clone https://github.com/rails/rails.git; cd rails
# 略
$ git co master
error: pathspec 'master' did not match any file(s) known to git
$ git branch -a
* main
  remotes/origin/1-2-stable
  remotes/origin/2-0-stable
  remotes/origin/2-1-stable
  remotes/origin/2-2-stable
  remotes/origin/2-3-stable
  remotes/origin/3-0-stable
  remotes/origin/3-1-stable
  remotes/origin/3-2-stable
  remotes/origin/4-0-stable
  remotes/origin/4-1-stable
  remotes/origin/4-2-stable
  remotes/origin/5-0-stable
  remotes/origin/5-1-stable
  remotes/origin/5-2-stable
  remotes/origin/6-0-stable
  remotes/origin/6-1-stable
  remotes/origin/HEAD -> origin/main
  remotes/origin/activemodel-multiparam-attrs
  remotes/origin/add-github-actions-to-rails
  remotes/origin/add-spaces-integration-test
  remotes/origin/add_autoload_paths_to_load_path
  remotes/origin/backport-30638
  remotes/origin/bump-z
  remotes/origin/conductor6
  remotes/origin/ditch-premature-optimization-for-collection-cache-keys
  remotes/origin/do-no-shallow-name-errors
  remotes/origin/fix-array-builder-wheres
  remotes/origin/fix-as-with-api-apps
  remotes/origin/fix-build
  remotes/origin/fix-duration-modulo
  remotes/origin/fix-find-root-on-ruby-2-2
  remotes/origin/fix-nil-params-in-ap
  remotes/origin/fixtures-refactor
  remotes/origin/freeze-configuration_hash
  remotes/origin/fxn/docker
  remotes/origin/fxn/load-paths-per-environment
  remotes/origin/issue-33155
  remotes/origin/json-visitor
  remotes/origin/main
  remotes/origin/matthewd/inotify
  remotes/origin/modernize-scaffold
  remotes/origin/new-autoloading-guide
  remotes/origin/no-html-fallback
  remotes/origin/postgresql_type_map_lookup_fix
  remotes/origin/proxy-pg-result
  remotes/origin/railties-split
  remotes/origin/record-timezone-when-writing-from-user
  remotes/origin/remove-json-tests
  remotes/origin/remove-qc-ivar
  remotes/origin/settings-file
  remotes/origin/test-remove-marshal-support-from-schema-cache
  remotes/origin/tzinfo2
  remotes/origin/use-any-bundler
  remotes/origin/verify-release
  remotes/origin/ಠ_ಠ

rails/rails - GitHub

⚓ closed: ‘Directly inheriting’メッセージを修正


つっつきボイス:「これはclosedですね?」「マージされてないプルリクですが、気になったので拾ってみました」

「プルリクのコメントを読むとclass #{subclass} < ActiveRecord::Migration[4.2]4.2は、元々Rails 4.2以前の仕様でバージョン番号なしで実装されていたものに対するエラーメッセージなんですが、Rails 4.2以前の仕様で書かれたものを最新のmigaration number(最新では6.1)に書き換えて使ってしまうのは、前方互換が無くなった機能などを踏むリスクがより高いので適切ではないだろうということのようですね」「あ、そういうことでしたか!」

# activerecord/lib/active_record/migration.rb#L556
    def self.inherited(subclass) #:nodoc:
      super
      if subclass.superclass == Migration
+       major = ActiveRecord::VERSION::MAJOR
+       minor = ActiveRecord::VERSION::MINOR
        raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
-         "Please specify the Rails release the migration was written for:\n" \
+         "Please specify the Active Record release the migration was written for:\n" \
          "\n" \
-         "  class #{subclass} < ActiveRecord::Migration[4.2]"
+         "  class #{subclass} < ActiveRecord::Migration[#{major}.#{minor}]"

⚓Rails

⚓ Rails 6.1はMySQLのENUM型に対応済み


つっつきボイス:「BPSの社内Slackに貼っていただいた@kamipoさんのツイートです」「そうそう、MySQLについてはいち早くRailsが6.1でEnumに対応しているということでしたね」

「先週のウォッチ(ウォッチ20210120)でRailsとPostgreSQLのENUMサポートについての話になったときに、RailsがデータベースのENUM型に対応していた話を以前もしていたような気がして仕方がなかったんですが、あれはこのMySQLのENUM型をサポートしたときの話だったのかも」「私も思い出せませんでした😅

このコミットは昨年7月にマージされていました↓。

enumset:stringとして型キャストされるが、現時点の:string型はスキーマダンプでの再利用方法が正しくない。
あるカラムのキャスト型は常にsql_typeと同じとは限らないので、このプルリクではenumsetのスキーマダンプが(typeではなく)sql_typeを正しく使うよう修正した。
同PRより大意

「この追加機能はマイグレーションに多少影響しそう」「DBMS依存の部分も結構ありそうな気がしました」

⚓ 最近のRailsテストに関するアンケート(Ruby Weeklyより)


つっつきボイス:「TechRacho翻訳記事でもお世話になっているarkencyによるツイートまとめ記事です」「記事に貼られているツイートはこれね↓」「RSpec派は8割、Minitest派は2割か」

「これ↓を見ると受け入れテスト(acceptance test: ここではCucumberなどによるRailsの自動テスト)を書いている人もそれなりにいるのね」「ホントだ」

「今思ったんですが、ここで受け入れテストを書いていると回答している人は、昔BDD(Behavior Driven Development)が流行った頃にそのプロジェクトでCucumberやTurnipのテストが書かれたからじゃないかな」「それ、ありそうですね」「書かれたからにはメンテし続けないといけなくなるので、そういうプロジェクトの人がそう回答していることが多いのかなと想像してみました」

参考: ビヘイビア駆動開発 - Wikipedia

cucumber/cucumber - GitHub

jnicklas/turnip - GitHub

「プロジェクト規模: データベースのテーブルが100個を超えてるプロジェクトが3割ほどある↓」「テーブル100個ですか…」「Railsアプリが育つとテーブル数もこのぐらいにはなりますよ」「Railsで書かれたプロジェクトがそれだけ育って大きくなったという傾向をある程度表しているかなと思います: ただこのアンケートの母数が142とあるので調査としてはかなり小規模ですが」「そのつもりで読まないといけないということですね」

「チームの規模↓は三分の一ぐらいが1〜3人ぐらいか: Railsだとそのぐらいの規模のチーム編成が多いでしょうね」

「テスト1件あたりの実行時間↓は、半分以上が5秒以内」「あ、5分かと思ったら5秒だった😅」「テスト全体じゃなくてテスト1件あたりの実行時間ならこのぐらいの感じかな」「しかしシングルテストで1分以上かかるようなテストを書くことってあるかな?」「結合テストなんかだとそのぐらいかかることもあるかもしれませんね」

「アンケートの母数の小ささを割り引く必要はありますが、Railsアプリ開発のテストに関するアンケートとしてはだいたい自分の感じるところに近そうに思えますね」

⚓ その他Rails


つっつきボイス:「今ならDockerでやるかなと思いつつ、新し目の記事なので一応貼ってみました」「Ruby 2.7.2を使ってる」「WSLにも2が付いてませんね」

参考: WSL 2 と WSL 1 の比較 | Microsoft Docs

「お、この記事ではWindows版のPostgreSQL↓をインストールしてますね」「あ、ホントだ」「GUIインストーラですか」

参考: PostgreSQL: Windows installers

「それにしてはapt-getも使ってるのが妙だな…起動もLinuxのsudo -u postgres -iコマンドだし」「あ、pgAdmin↓をインストールするのにapt-getを使ってるのか」

「Qiitaによくありそうなやってみた系記事かなという印象ですね」


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210120後編)Ruby 3.0の新機能で遊ぶ、RubyスニペットをJSに変換するRuby2JS、rspec-parameterized gemほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20210126後編)Google Cloud FunctionsがRubyをサポート、Ruby 3のパターンマッチングでポーカーゲームほか

$
0
0

こんにちは、hachi8833です。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

⚓Ruby

⚓ Google Cloud FunctionsがRubyをサポート(Ruby Weeklyより)

# 同記事より
require "functions_framework"

FunctionsFramework.http "hello_http" do |request|
  "Hello, world!\n"
end

つっつきボイス:「そうそう、Google Cloud FunctionsでRubyサポートが始まった🎉」「最近のGoogle Cloudは少しずつRubyのサポートが増えてきてるようですね」

「Google Cloud Functionsはまだそれほど使ったことはありませんが、AWS Lambdaで作ったものを公開APIにするにはAPI Gatewayと組み合わせる必要があるのに対して、Cloud FunctionsはHTTPトリガとして動かす部分までがセットで設定できるので、何もないところからRubyでAPIを書くのはGoogle Cloud Functionsの方が楽だろうなと思いました」「なるほど!」

参考: AWS Lambda を Amazon API Gateway に使用する - AWS Lambda

⚓ MacにRuby 3.0をインストールする(Ruby Weeklyより)


つっつきボイス:「よくある記事だと思いますが、RubyをMacにインストールする最近の方法がリストアップされていたのでピックアップしてみました」「リストのトップにあるasdfって何だろう?」「あ、見たことなかった」「この記事では推奨ツールになってますね」「サイトを見ると『Manage multiple runtime versions with a single CLI tool』と書かれている↓ので、anyenv的に複数の言語やツールを一括管理する新しめのツールみたい」「へ〜!」「asdfって覚えにくい名前…」

asdf-vm/asdf - GitHub

「ちなみに自分は最近anyenvで管理してます」

anyenv/anyenv - GitHub

🔗 Rails GirlsサイトのRuby/Railsインストール手順ページ

「もうひとつ、以下は日本で活動しているRails Girls Japan↑のWebサイトに掲載されているRubyとRailsのインストール手順です↓」「なるほど、Rails Girlsらしいページですね」

「インストール手順がまめにアップデートされているようなので、RubyやRailsの初心者が環境構築するときはここを見るとよさそうに思えました」「Rails GirlsはRails初心者をサポートするコミュニティとしては比較的知られていると思います」

「少なくともこのサイトにある現時点のインストール手順はrbenv2.6.5とRuby 2.7.0とRails 6.0.2.1に対応済みなので、じきにRuby 3.0とRails 6.1にも対応するのかなと個人的に期待しています🙏

以下はつっつき後に見つけたツイートです。

「そういえば日本のRails Girlsの動向はあまり知りませんでしたが、サイトのイベントページを見ると結構盛んに活動しているみたい」「さすがに昨年はコロナ禍でペースが少し落ちてるようですが、今はどこもそうなので仕方がないですよね」


注: 上で紹介しているrailsgirls.jpは日本で活動しているRails Girls Japanコミュニティのサイトです。同サイトの末尾に、以下のグローバルな英語版Rails Girlsサイト(以下railsgirls.comと略します)へのリンクがあります。

  • サイト: Rails Girls — ドメイン: railsgirls.com

railsgirls.comの最初のイベントはフィンランドのヘルシンキで開催されたそうです(railsgirls.comプレスリリース)。

ついでに探してみると、railsgirls.comにもrailsgirls.jpと同じフォーマットでRubyとRailsのインストール手順が掲載されているのを見つけました↓。railsgirls.jpはファビコンなども含めてrailsgirls.comのフォーマットを踏襲しているんですね。

railsgirls.comのインストール手順ページはRuby 2.6.5とRails 6.0.2.1で説明されているので、現時点ではrailsgirls.jpのインストール手順の方が少し新しいことがわかりました。

⚓ Factory Botのfactoryをネストさせる(Ruby Weeklyより)


つっつきボイス:「Factory Botのfactoryをネスト、この書き方のことか↓: 必ずassociateされるデータを作るときはこういうふうに書くしかなかったりしますね」

# 同記事より
FactoryBot.define do
  factory :user do
    username { Faker::Internet.username }
    password { Faker::Internet.password }

    factory :physician_user do
      role { 'physician' }
    end
  end
end

以下は銀座Rails #13で発表されたfixtureについてのスライドですが、factory botにも少し言及されているので参考までに貼っておきます。

なお、銀座RailsではTechRachoでお馴染みの弊社Webチームリーダーmorimorihogeも「出張!Railsウォッチ」枠で毎回発表しています。こちらもどうぞよろしくお願いします。

銀座Rails#13で「出張Railsウォッチ」発表させていただきました

🔗 Ruby 3のパターンマッチングをポーカーゲームに応用する(Ruby Weeklyより)


つっつきボイス:「TechRachoの翻訳記事でもお世話になっているBrandon Weaverさんの記事です」「なるほど、トランプのポーカーの手をRuby 3.0のパターンマッチング機能で評価するという企画ですね♣」「役のことをscoreって言うの?、へ〜!」「あれ、scoreでよかったのかな?思い出せない…」「わかりませんが、とりあえずこのコードの定義的にはそうなってますね😆」「このコードではhandが手札を指すみたいですね」

# 同記事より
SCORES = %i(
  royal_flush
  straight_flush
  four_of_a_kind
  full_house
  flush
  straight
  three_of_a_kind
  two_pair
  one_pair
  high_card
).reverse_each.with_index(1).to_h.freeze
# 同記事より
def hand_score(unsorted_hand)
  hand = Hand[unsorted_hand].sort_by_rank.cards

  is_straight = -> hand {
    hand
      .map { RANKS_SCORES[_1.rank] }
      .sort
      .each_cons(2)
      .all? { |a, b| b - a == 1 }
  }
  # 略
}

後でWikipedia英語版を見ると、ポーカーの役は一般に「hand」と表記されていることを知りました↓。調べると、どうやら英語圏ではカードゲームの役も手札も「hand」と呼んでいるようです。

参考: List of poker hands - Wikipedia
参考: トランプ用語一覧「て」- Wikipedia
参考: ポーカールームで使える英会話集 — English | 幸晋平チャンネル(仮)

麻雀だと「手がいい」「手が悪い」などと言うように、役というより「現在の手札」を指すように思えます。

「なるほど、ロイヤルストレートフラッシュなどの役がRubyらしいコードで記述されてますね↓」「こうやって書くのか」「先週のウォッチでも話題にしたRuby 3.0パターンマッチングのpin演算子(^)が使われてる(ウォッチ20210120)」

# 同記事より
return SCORES[:royal_flush] if hand in [
  Card[s, '10'], Card[^s, 'J'], Card[^s, 'Q'], Card[^s, 'K'], Card[^s, 'A']
]

後で調べると、パターンマッチング構文の提案にpin演算子も含まれていて、Elixirのpin演算子が由来だったことを知りました↓。

参考: Feature #14912: Introduce pattern matching syntax - Ruby master - Ruby Issue Tracking System

| ^var # Ditto. It is equivalent to pin operator in Elixir.
#14912コメント(by ktsj)より

「新しい機能はこうやって一度自分で書いて動かしてみると納得しやすいですね: この記事のようにルールのロジックを自分で書いてみるといい練習になります」「あ、たしかに!」

「そういえば自分も学生時代にハーツのネットワークゲームを書いて動かして遊んでたなあ」「ハーツって何ですか?」「4人でやるトランプのカードゲームです↓: 当時作ったのは4人のうち1人がサーバーになって後の3人がクライアントとしてtelnet接続するゲームでしたね」

参考: ハーツ (トランプゲーム) - Wikipedia

「トランプのカードゲームは、ルールがシンプル過ぎず複雑すぎずなので、こういうプログラミング練習のお題にちょうどいいんですよ」「なるほど」「ルールが複雑すぎないのと、カードの枚数も大して多くないのがポイントですね」「麻雀ルールのロジック化は難しそう…」「麻雀だと急に難しくなりますね」


Rubyでわかる「時計もモノイドの一種」(翻訳)

⚓DB

⚓ ちょっと珍しいデータベース移行の話(DB Weeklyより)


つっつきボイス:「記事冒頭を見ると、自分が参加したプロジェクトでテキストファイルを自力で排他制御してデータストアとして使っていたという話なのかな」「はい、私もまだ冒頭しか見ていませんがそんな感じです」

🔗 テキストファイルをデータストアにする

「Web開発ではテキストファイルをデータストアとして使う文化はほとんど見かけませんが、それ以外の開発だと、たとえばクライアント/サーバーモデルに沿ったアプリケーションを経験したことのない開発者がこんなふうにデータをテキストファイルに保存して使っていることはときどきありますね」「あ、そうなんですね」「ときどきですが、自分もテキストファイルを独自のデータストアとして使っているようなシステムを見かけたことがありますよ」「なるほど」

参考: クライアントサーバモデル - Wikipedia

「Web開発ではDBMSにデータを保存するのがあまりに当たり前になっていますが、たとえば組み込み系ソフトウェア開発などではDBMSを使う余裕がなかったりしますし、要件上DBMSが必要なければDBMSを使わなくてもいいんですよ」「それもそうですね」

「テキストファイルのデータストアは、以前ウォッチで話題になったユニケージ↓とは違うんでしょうか?」「昔ユニケージ開発やってましたけど、あれはDBMSを使わずに主にUnixシェルコマンドを用いて処理を構築するものなので、この記事のようにテキストファイルをアプリのデータストアにしてロック機構を独自に構築するのとは違いますね」「そうでしたか、ありがとうございます🙇

参考: ユニケージ開発手法 - Wikipedia

「あれはPHP 4が出るか出ないかの頃だったかな、当時読んだPHP入門書に載っていたWeb掲示板アプリが、まさにこの記事のようにテキストファイルをデータストアとして使っていたのを思い出しました」「おぉ」「当時はJSONすらない時代でしたし、そのアプリで使っていたテキストファイルのデータ形式も完全にその本の独自でしたね」「へ〜!」

参考: JavaScript Object Notation (JSON) - Wikipedia

「システム開発の世界全体を考えれば、Web開発のようにDBMSにデータを保存するのが当たり前の文化の方が、むしろ少数派だと思います」「それもそうですね」「今でこそAndroidスマートフォンにもSQLiteが入っていたりWebブラウザにもIndexed DBが内蔵されていたりしますけど、特に昔のシステム開発だとデータストアが常に使えるとも限りませんでしたし、あっても排他制御できるとも限らなかったんですよ」「そう思うと今はそういうところがやりやすくなってきたんですね」

参考: SQLite を使用してデータを保存する  |  Android デベロッパー  |  Android Developers
参考: Indexed Database API - Wikipedia

「今でもDBMSなしでテキストファイルのデータストアを独自構築しているところは割とあるんじゃないかな?」「DBMSを使っていればmutexなどを管理しなくても排他制御などがDBMSでできるから楽ですよね」

参考: ミューテックス - Wikipedia


後で記事の続きを読むと、テキストファイルのデータストアから以下のetcd.ioという分散キーバリューストアに移行したのだそうです。そのためにtailetcというクライアントソフトウェアも自社で作ったそうです。

参考: etcd | Home

tailscale/tailetc - GitHub

以下はつっつき後に見つけたツイートです。

⚓JavaScript

⚓ 2020のJavaScript


つっつきボイス:「ランキングそのものより、今どんなものが出回っているのがが見たくてはてブで拾いました」「なるほど、GitHubスターの個数を取り出して比較してるのね」

「お、フロントエンドフレームワークの部でStimulusが10位に入ってる」「ホントだ」「自分もStimulusに★付けましたけど使ってません😆」「★付けたけど使ってない人は相当いそう」「★で調べた調査はそこを念頭に置いて読みたいですね」

参考: Stimulus: A modest JavaScript framework for the HTML you already have.

Reactエコシステムの部の1位はNext.jsか」

参考: Next.js by Vercel - The React Framework

CSS in JavaScriptの部はStyled Componentsの1位が安定しつつある印象がちょっとありますね」

参考: styled-components

「JavaScriptのランキング、また来年になったらごっそり変わりそう」「私もそんな気がします」「それぞれでできることは大きく違わないと思うので、基本的には使いたいものを使えばいいと思っています: ただ、流行りが終わって更新されなくなるとつらくなるので、それは避けたい」「まったくですね」

⚓言語/ツール/OS/CPU

⚓ Reactの用語


つっつきボイス:「ReactのReducerとかは、自分も一般用語なのかどうかが気になってググったりしたことありますが、一応他の言語にもあったと思います」

参考: Reduce (Composing Software). Note: This is part of the “Composing… | by Eric Elliott | JavaScript Scene | Medium
参考: Transducers: Efficient Data Processing Pipelines in JavaScript | by Eric Elliott | JavaScript Scene | Medium

「ReactもRailsも大きなフレームワークなので、もしかすると最近Railsを新しく覚え始めた開発者も、同じようにRailsエンジニアの中で一般用語のように使われているRails用語に戸惑いを感じてたりするかもしれないと思いました」「言われてみれば、 PORO(Plain Old Ruby Objects)とか、RailsのActive RecordをARと略したり、Active SupportをASと略したりするあたりなどは、知らないと戸惑いそうですね」

参考: Railsで処理を別クラスに切り出す方法について - メドピア開発者ブログ — RailsのモデルでPOROを使った記事です

「一般用語が説明なしで書かれると、初めて学ぶ人が戸惑いそう」「一般用語がダブルミーニングになってなければ調べやすいので許容範囲かなと思いますね: 一般の用語やパラダイムがその言語やフレームワークで違う意味で使われていたりするのは困りますが」「たしかに」「逆に、違う名前が付いているけど調べてみたら既存の用語や概念と同じだったということもありますね」

「RaiisはAction何とかとかActive何とかのような、他とかぶりにくい用語が使われることが多いのでその点はありがたい」「Active Recordは、Martin Fowlerの『Active Recordパターン』↓とかぶっているかなと思いましたが」「今のところRails以外でActive Recordパターンの機能にActive Recordと名付けているのは見たことがないから、Railsの機能名と考えていいんじゃないかなと思いました」「そうですね」

参考: デザインパターンから見たActive Record | TECHSCORE(テックスコア)

⚓その他

⚓ 娘のためにマイクラサーバーを立てた話


つっつきボイス:「これ読みました」「お父さんが娘がマイクラサーバーを立てるのを見守りつつサポートしてあげた話」「娘さんが自分でサルベージしたデータも復旧できた」「いい話でしたよね」「まとめ記事の方が見やすそうです↓」

「業務だといかにもありそうな話ですけど、娘さんに頼まれてというのが新鮮」「ほのぼのしました」「自分も昔こういうのを自力で何とかしてました💪」(以下略)

🔗 Red Hat Enterprise Linuxが無料ライセンスプログラムを開始


つっつきボイス:「ちょっと時間が余ったので、ちょうど今日発表されたRed Hat Enterprise Linuxの無料ライセンスの話をしましょうか↑」「え、そうなんですか?」「Enterpriseが無料ですか?」「そうそう」

「リリース記事を見るとsmall production workload向けに16システム分のライセンスまで無料になるとある」「smallだけどproduction用途なのがスゴい」

「ひとつ気になるのがRed Hatのアカウントを作る必要がある点: 個人ユーザーのアカウント作成は無料なんですが、個人ユーザーがアカウントを複数登録してよいのかいけないのかとか、そのあたりの規約や情報が今の時点ではまだ見当たらなかったんですよ」「あ、そうでしたか」「そのうちまとめ記事が出ると思いますのでそちらに期待しています」

つっつき後の以下の記事では「Red Hatから今後追加発表がある見通し」と報じられていました。

参考: 個人開発者はRed Hat Enterprise Linuxを無料で最大16システムまで利用可能に、本番環境もOK。Red Hatが開発者向けプログラムの拡大を発表 - Publickey


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210125前編)Railsリポジトリのデフォルトブランチがmainに変更、Rails 6.1はMySQLのENUM型に対応済みほか

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

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

Ruby Weekly

DB Weekly

db_weekly_banner

Rails: 本当にやった「責務過剰クラス」の事例(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rails: 本当にやった「責務過剰クラス」の事例(翻訳)

鉛筆は、何かものを書くのに使います。それ以上のものではありません(お尻に消しゴムが付いている鉛筆もありますが、この際考えないことにしましょう)。

今は何でもデジタルに移行する時代ですよね。鉛筆本体に簡単なOCRスキャナを取り付けて、書いたものが片っ端からデジタル化できたらさぞ便利でしょう。

私は会議で意見を交換中、手元で書いたちょっとした技術用の図を共有しようとしてじたばたするのが常だったので、「この鉛筆にミニプロジェクター機能が付いていたら最高なのに」と思ってしまいます。外付けのプロジェクターがなくても壁に手元の図面をさっと映せる、そんな機能です。

そうそう、レーザーポインタも付けないと。レーザーポインタで遊ぶのは楽しいですよね。

名付けて「The Pencil 3000 ™

前置きはこのぐらいにしましょう。私は、Railsのクラスを作ったり更新したりするときにもついそういうことを考えてしまいます。

本当にやった「責務過剰クラス」の事例

私がそんな調子で「この機能が欲しい」「あの機能も欲しい」を繰り返すうちにやってしまった事例を以下にご紹介します。

🔗 1. モデル

私は、Railsのビューでしか使わないメソッドをモデルの中に直書きしてしまったことが何度もありました。

はっと気がつくと、モデルの中にコールバックメソッドを書いてしまったことも数え切れないほどです(メールを送信するコールバックや、モデルにデフォルト値を追加するためのコールバックなど)。

  • あのときの自分に伝えたい:「メール送信はユーザー操作に伴う副作用だろうから、コールバックじゃなくて普通にコントローラでトリガーしてみたら?」「モデルにデフォルト値を設定したいならActiveRecord::Attributes APIを使ってみたら?」。

ああ、そういえば昔バリデーションメソッドをインラインで書きまくっていたことも思い出してしまいました、この場で謝ります。ごめんなさい。

  • あのときの自分に伝えたい:「新しいクラスを追加するコストはゼロなんだから、RailsのValidatorクラスを作ってそれでやるといいよ、んじゃまた」。

🔗 2. Form Object

「背後にデータベースがないのに、Railsビューのドロップダウンボックスのオプションをselect_tagで作ること」は有罪です。私はこれまで、Railsビューのselectタグで使うオプションを提供するためだけに、このメソッドをForm Objectに書いてしまったことが何度もありました。

  • あのときの自分に伝えたい:「そこまで大ごとじゃないから安心して、でもそれこそPresenter Objectを使ってみたらどう?どうせビューでしか使わないんだから」。

ここまで書いているうちに、何だか私は懺悔して許しを請うている気分になってきました。どうか皆さんが私と同じ罪を犯すことがありませんように。それはともかく、あとひとつだけご紹介しましょう。

🔗 3. Page Object

テストでは「セットアップ」「実行」「アサーション」「終了処理」の4つのステップが必要になるのが普通です。そしてWebアプリケーションをテストするということは、ユーザーがHTMLドキュメントに対して操作を行うということです。

そんなときはPage Object(by Martin Fowler)です。Page Objectを用いてHTMLをラップし、テストコードで使う有用なAPIをそこで提供するのです。ここで言うテストコードには「実行」「アサーション」のステップも含まれます。

率直な感想ですが、Page Pbjectで何か新しいメソッドを書いて、「インスタンスを生成する」「何かを更新する」「環境変数を設定する」といったテストのセットアップに必要なことを行うのは、実に簡単で、しかもとても楽しい作業です。

  • あのときの自分に伝えたい:「テストspecのうち、何かを作成する部分はサポート用のモジュールに切り出しておく方がPage Objectの意図がぶれなくなるよ、簡単だし楽しいよ」。

さて、冒頭に書いた「The Pencil 3000 ™」を皆さんは覚えていますか?もしそんなスーパーな鉛筆があったら、目移りする機能がありすぎて私は鉛筆の削り方も忘れてしまうでしょう。

単一責任の原則

ソフトウェアの各モジュールを変更する理由は、ただひとつでなければならない。

詳しくは以下の良記事をどうぞ。

参考: The Single Responsibility Principle
— Clean Coder Blog

関連記事

肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)

Rails API: ActiveRecord::Attributes::ClassMethodsの#attributeと#define_attribute(翻訳)

週刊Railsウォッチ(20210201前編)Webpackerのガイドがマージ、RailsはRuby 3でどのぐらい速くなったかほか

$
0
0

こんにちは、hachi8833です。

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今回は以下の公式更新情報が出ていました。


つっつきボイス:「今週は新年第2号の公式更新情報出てるんですね」「Railsリポジトリのmaster->mainブランチ変更は既にウォッチでも報じました(ウォッチ20210125)が、公式更新情報で以下のツイートにもリンクしていました↓」「今はブランチ名をmaster->mainに移行するのが世の中の流れですね」

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

🔗 WebpackerガイドがEdge Guidesに追加される


つっつきボイス:「BPSの社内Slackに流れてた情報ですね」

「Rails Guides(英語)とEdge Guides(英語)を見てみたところ、WebpackerガイドはまだEdge Guidesにしかありませんでした↓」「それはまだmaster、じゃなくてmainブランチにしか入っていないからですね: 通常は次のリリースで正規のRails Guidesに載ることになります」「あ、そうでした」

Edge Guides: Webpacker — Ruby on Rails Guides

Rails Guides: Ruby on Rails Guides

「WebpackerのREADME↓も簡単にチェックしてみたところ、Edge GuidesのWebpackerドキュメントはこれとは違う内容でした」「必要そうなことはWebpacker Guidesにだいたい盛り込まれている感じかな👍

「Webpackerのドキュメント、ありそうでなかったのか」「そうなんです、Rails 5の頃にこんなつなぎ記事↓を書いたのを思い出しました」「Webpackerがリリースされてから随分経つのに今までなかったとはね…」「このプルリクは”いいね”が多めだったので、それだけ待ち望まれていたということでしょうね」

【保存版】Rails 5 Webpacker公式ドキュメントの歩き方+追加情報

🔗 content_typeメソッドがContent-Typeヘッダーをそのまま返すよう修正


つっつきボイス:「なるほど、変更後はcontent_typeメソッドがtext/csv; header=present; charset=utf-16"の部分を加工せずに丸ごと返してくれるようになったんですね」

参考: Content-Type – HTTP | MDN

「Rails Guides(英語版)の資料も更新されていて、デフォルトの挙動をreturn_only_request_media_type_on_content_typeコンフィグで変更できるようになっている↓」「既存のRailsアプリがcontent_typeメソッドを使っていたら、このコンフィグパラメータで以前の挙動に戻せますね」「なるほど」

# guides/source/configuring.md#1050
#### For '6.2', defaults from previous versions below and:
 - `config.action_view.button_to_generates_button_tag`: `true`
 - `config.action_view.stylesheet_media_default` : `false`
 - `config.active_support.key_generator_hash_digest_class`: `OpenSSL::Digest::SHA256`
 - `config.active_support.hash_digest_class`: `OpenSSL::Digest::SHA256`
+- `config.action_dispatch.return_only_request_media_type_on_content_type`: `false`

参考: Rails アップグレードガイド – Railsガイド

「なるほど、MIMEタイプだけ欲しい場合はActionDispatch::Request#media_typeメソッドで取れるのか」

参考: メディアタイプ – Wikipedia

「以下でmedia_typeメソッドにも修正が入ってますね↓」

🔗 コンフィグファイルで互換性を維持するRails

「この変更では、ライブラリに変更をかけるときに既存のコードに与えるインパクトをできるだけ抑えているのがいいですね: いろいろと参考になります👍」「そうですね」

「Railsは互換性をあんまり気にせずに変えるときは変えるのかなと何となく思ってましたけど、そうでもないんですね」「Railsがデフォルトの挙動を変えることはたまにありますが、そういう場合でも以前の挙動が必要な人はコンフィグで調整可能にする余地を作っておくあたりがRailsらしいと思います」「たしかに!」

「きっとこのコンフィグも、今後既存のRailsアプリでrails app:updateするときに生成されるconfig/initializers/new_framework_defaults.rbに追加されるんでしょうね↓」

参考: 1.5 フレームワークのデフォルトを設定する — Rails アップグレードガイド – Railsガイド

「その場合は古い設定のコンフィグが追加されるんでしょうか?」「Railsのこういうコンフィグは後方互換性を維持する方向で生成されるはずですし、少なくともrails app:updateすると”こういう設定があるからチェックして”みたいに知らせてくれますよ」「え、やったことなくて知らなかった」「rails app:updateは基本的に従来のデフォルトの挙動を使うコードがなるべくそのまま使えるように作られていると思います」「そうだったんですね」


ActionDispatch::Request#content_typeがContent-Typeヘッダーをそのまま返すようになった。
従来のActionDispatch::Request#content_typeが返す値にはcharsetパートが含まれていなかった。この振る舞いを変更して、charsetパートをそのままの形で含むContent-Typeを返すようにした。MIMEタイプだけが欲しい場合はActionDispatch::Request#media_typeを使っていただきたい。

# 変更前
request = ActionDispatch::Request.new("CONTENT_TYPE" => "text/csv; header=present; charset=utf-16", "REQUEST_METHOD" => "GET")
request.content_type #=> "text/csv"
# 変更後
request = ActionDispatch::Request.new("Content-Type" => "text/csv; header=present; charset=utf-16", "REQUEST_METHOD" => "GET")
request.content_type #=> "text/csv; header=present; charset=utf-16"
request.media_type   #=> "text/csv"

Rafael Mendonça França
Changelogより大意

🔗 LogSubscriberを設定したコントローラでthrowしたときのエラーを修正


つっつきボイス:「プルリクメッセージを見ると、コントローラで投げた例外がどこで拾われるかという話になっている」「リグレッションが発生したのを修正したのか: &を追加している↓のを見ても、これは改善ではなく修正でしょうね」「修正前はnil参照みたいになる場合があったということかな」

# actionpack/lib/action_controller/log_subscriber.rb#L26
-       if status.nil? && (exception_class_name = payload[:exception].first)
+       if status.nil? && (exception_class_name = payload[:exception]&.first)
          status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
        end

「LogSubscriberをコントローラに設定し、そのコントローラのアクション内でraiseすると、raiseされた例外がLogSubscriberに吸い込まれて、その後コントローラに投げ返してくれなくなったらしい」

f0fdeaa↓を見ると、firstを呼ぶ前のpresent?による存在確認を省略したことでこのリグレッションが起きたようですね」「なるほど、これですか」「一見問題なさそうな最適化が思わぬところに影響したのか」

# actionpack/lib/action_controller/log_subscriber.rb#L25
        if status.nil? && payload[:exception].present?
          exception_class_name = payload[:exception].first
        if status.nil? && (exception_class_name = payload[:exception].first)
          status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)

コントローラのアクションでthrowが使われていると、マッチするものがRackミドルウェア周辺でキャッチされても:exceptionがイベントペイロードにまったく現れなくなっている。
理由は、ActiveSupport::Notifications::Instrumenter.instrument:exceptionをrescueハンドラー内で設定しているため。しかしrescueは以下のようなthrow/catchシナリオでは決して呼び出されない。

catch(:halt) do
  begin
    throw :halt
  rescue Exception => e
    puts "rescue" # ここに到達することはない
  ensure
    puts "ensure"
  end
end

失われたexceptionは実際にはRails 6.1.0より前のバージョンでは扱えていたが、f0fdeaaの最適化のときに、:exceptionが存在することを前提とする部分が更新された。そのコミットを取り消すのが修正としては最も簡単。そういうわけで本PRはリグレッション修正とみなせる。
なお、この問題はRodauthをRailsで使っていて見つけた。
同PRより大意

jeremyevans/rodauth - GitHub

🔗 media=screenstylesheet_link_tagのデフォルトから削除


つっつきボイス:「stylesheet_link_tagを使うとCSSがmedia=screenになってた、ということ?」「それをデフォルトから外してデフォルトがallになったようですね」

# 変更前

stylesheet_link_tag "style"
#=> <link href="/assets/style.css" media="screen" rel="stylesheet" />

# 変更後

stylesheet_link_tag "style"
#=> <link href="/assets/style.css" rel="stylesheet" />

「プルリクタイトルにlegacyって書いてありますけど、media=screenって古いんでしょうか?」「従来はデフォルトでmedia=screenだったけど、わざわざ指定する意味がないから外したのかな?」

参考: メディアクエリの使用 - CSS: カスケーディングスタイルシート | MDN

「DHHが立てたissue↓にこんなことが書かれてる」

これを修正しよう:

歴史的理由によって、media属性は常にデフォルトで”screen”に設定されるので、すべてのメディアタイプを適用するにはスタイルシートで明示的に”all”を設定しなければならない。

stylesheet_link_tag "http://www.example.com/style.css"
# => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />

config.assets.stylesheets.media_default的なものを追加したうえで、既存のアプリではデフォルトを”screen”にすればよいが、新しいアプリでは意味がないので、その後でmedia: "all"による上書きをscaffoldテンプレートから削除しよう(訳注: 本PRで削除されたそうです)。
#41213より(by DHH)

「今のissueに書かれている情報を見た限りではですが、従来のようにデフォルトがmedia=screenだとそうした部分が直感的でないから、デフォルトをallにすることでブラウザ画面用のCSSをデフォルトで印刷にも使うよう修正したんだろうと思います」「たぶんそうでしょうね」

「普段あまり気にしない部分ですが、数年に一度ぐらいはmedia="print"を指定することもあったので、stylesheet_link_tagと書いただけなのにmedia="screen"が付けられるより、その方が自分も嬉しいですね」「たしかに」

🔗 rescue_fromですべてのエラーを拾えるよう修正


つっつきボイス:「なるほど、rescue_fromで拾う例外が従来StandardErrorだけだったのを、すべての例外を拾えるようにしたようですね↓」

このコミットより前は、rescue_fromハンドラで扱われる例外はStandardErrorだけだった。
この変更によって、rescue句がすべてのExceptionオブジェクトをキャッチするようになり、StandardErrorを継承していないExceptionクラスでrescueハンドラーを定義できるようになる。
つまりStandardErrorの外にあるExceptionをrescueするrescueハンドラーは、今回の変更前に扱えなかった他の例外もrescueできるようになるだろう。
同PRより大意

「たしかRubyの仕様では、改修前のように単にrescue => exceptionと書くとStandardErrorを拾うんですよ」「あ、そうでしたっけ」

# activejob/lib/active_job/execution.rb#L41
    def perform_now
      # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
      self.executions = (executions || 0) + 1
      deserialize_arguments_if_needed
      run_callbacks :perform do
        perform(*arguments)
      end
-   rescue => exception
+   rescue Exception => exception
      rescue_with_handler(exception) || raise
    end

「Rubyのドキュメントにも書かれてるのを見つけました↓」「ホントだ」「つまりRubyの仕様がそうなってます: 一般にはStandardErrorを継承しますけどね」

参考: 制御構造 (Ruby 3.0.0 リファレンスマニュアル)

error_type が省略された時は StandardError のサブクラスである全ての例外を捕捉します。Rubyの組み込み例外は(SystemExitInterrupt のような脱出を目的としたものを除いて) StandardError のサブクラスです。
docs.ruby-lang.orgより

Exceptionを継承するのって非推奨なんでしょうか?」「非推奨とまではいかなかったと思いますが、Rubyでアプリケーションの例外を扱うときはStandardErrorを継承するのが一般的だったと思います」「なるほど、アプリ開発のお作法的な感じですか」

「RubyのExceptionのクラス階層↓を見るとわかりますが、ExceptionStandardErrorより上位にあるので、改修後のようにrescue Exception => exceptionと書くとすべての例外を拾えるようになります」「なるほど!」

参考: library _builtin (Ruby 3.0.0 リファレンスマニュアル)


docs.ruby-lang.orgより

Exceptionの直下にあるNoMemoryErrorNotImplementedErrorSecurityErrorのような例外クラスはStandardErrorでは拾えません」「なるほど理解できました」

NoMemoryErrorあたりはアプリで表示する可能性がゼロではないかもしれませんが、SyntaxErrorはコードが解析される時点のエラーなので、まずアプリでは表示されないでしょうね」「アプリが立ち上がる前のエラーだからそうなるのか」

「そういう例外を拾えるように改修したということですね」「なるほど」

「何かの機会に、どうも取れない例外があるなって気づいて修正したのかもしれませんね: たとえばNotImplementedErrorなら、DI(Dependency Injection)を使うつもりでスタブのクラスを書いているときに、まだメソッドが実装されてないのにエラーが出なくておかしいなと思って気づく可能性がそこそこありそう」「ありそうですね」

参考: 依存性の注入 - Wikipedia

🔗 Active Storage向けのfixtureサポートを強化

fixture統合を進めるためのActiveStorage::FixtureSetActiveStorage::FixtureSet.blobが宣言されるようになった。
Changelogより


つっつきボイス:「お、Active Storageのfixtureサポートが増えた」「ActiveStorage::FixtureSetが追加されてActive Storage向けのfixtureが使えるようになったみたい」「Active Storageのテストでfixtureが添付ファイルも扱えるようになったということですね」「これはありがたい」「今まで添付ファイルってどうやってテストしてたんでしょうね」「使えるなら欲しいヤツ、いい機能だと思います👍

Rails API: `ActiveRecord::FixtureSet`(翻訳)

🔗Rails

🔗 RailsはRuby 3でどのぐらい速くなったか(Ruby Weeklyより)


つっつきボイス:「RubyやRailsのパフォーマンス記事でお馴染みのNoah Gibbsさんの新しい記事です」

Railsアプリに最適なAWS EC2インスタンスタイプとは(翻訳)

「記事を眺めた感じでは、Ruby 3.0にしたときのパフォーマンスの違いは微差というしかなさそう」「誤差の範囲っぽいですね」

「Ruby自体はRuby 2.0か3.0でら3倍速くなったんですよね」「記事下にあるカラフルなグラフがわかりやすそう↓」「お、なるほど」「グラフを見る限りでは、Ruby 2.7から3.0では実質ほぼ変わらないかな」


同記事より

「3.0に上げて遅くならなければいいと思います👍」「そうですよね」「もし上げて遅くなるとアップグレードしない人が出てくるかもしれませんが、Ruby 3.0でRailsが遅くなったわけではないからそこは大丈夫だと思います」「上げて大丈夫ですよ〜」

🔗 tzinfo-data

「記事ではtzinfo-data↓あたりでいろいろ苦労したみたい」「tzinfo-dataはrails newするとデフォルトで入ってきますね」「そういえば皆さんtzinfo-dataってどうしてます?自分は速攻で削除してますけど」「tzinfo-dataはWindowsでRailsを動かすためのものですよね」「そうなんです、Macで動かすとwarningが出てくる」

tzinfo/tzinfo-data - GitHub

「そのwarningを見るたびに、そもそもWindows環境でRailsを動かすことはほぼないよねという気持ちになります」「Windows環境でRailsを動かす人が少しでもいるうちは残るんでしょうね…」

参考: bundle installする際のtzinfo-dataのwarningがウザい - Qiita


「そういえば、この間ついにRuby 3.0でRails動かしてみましたよ」「お、どうでした?」「まだ動かしてヤッタバンザイしただけ😆」「気持ちわかります」「お気持ちお気持ち」「なおwaringやマイグレーション時にいろいろエラーが出ました」

🔗 ReactとRailsの新しいアーキテクチャ(Ruby Weeklyより)


つっつきボイス:「ReactとRailsの共存を進めている開発会社の記事のようです」「この図で見当が付きそう↓」


同記事より

「図を見た限りでは、現在のアーキテクチャ↑ではReact UI ViewでRailsのRESTful APIにアクセスしている: まさに伝統的なRailsアプローチ」

「そして今後目指すアーキテクチャの図↓では、従来のRailsバックエンドとビューもあるけど、React側にAPIライブラリをはさんだうえで、ReactがアクセスするバックエンドをRailsのRESTful APIから徐々にGraphQLに移行しようとしているようですね」「へ〜」「全部移行するのかもしれませんし、RailsのRestAPIも残すのかもしれませんが、いずれにしろRailsを一度引き剥がしてやり直したい感じがしますね」


同記事より

「このようなアーキテクチャ移行は最近よく見かけるやつですね」

🔗 GraphQLよもやま話

「今日ちょうどWebチームミーティングでRailsとGraphQLについての発表があったんですよ」「お、GraphQLやってるんですね」「自分もGraphQLに関わってみた感想としては、ReactとGraphQLの組み合わせはたしかに便利: でも便利であるがゆえに、GraphQLのエンドポイントで行う仕事が肥大化してくるんですよ」「何となくわかります」

「たとえばさっきの図で言うと、federated GraphQLの下に置くマイクロサービスを1個にするのか、それとも複数のマイクロサービスを扱うのかという選択肢があります」「ふむふむ」「そして後者を選ぶと、ファットなRailsモノリスの代わりにファットなfederated GraphQLができる、そういう未来をちょっと感じているところです」「自分もまさにそう思っています」

「具体的には、このfederated GraphQLで何もかもやろうとするとGraphQLのスキーマが巨大になるんですよ」「やっぱり育っちゃうんですね..」

「Railsの場合は、ルーティングにresourcesと1行書くだけでRESTfulなルーティングが使えますが、GraphQLはQueryとMutationがあって、しかもRestfulのCREATEやUPDATEなどもMutationで定義することになります」「ふむふむ」「それをやっていくと、GraphQLのフラットな名前空間に大量のGraphQLメソッドができてくるんですよ」「ありゃ〜」「ネストすることもできるんですが、いずれにしろ、GraphQLのスキーマはちょっと大きすぎじゃないかなと感じているところです」「うーむ」

参考: クエリのクエリとミューテーションの実行 - AWS AppSync
参考: GraphQLのクエリを基礎から整理してみた - Qiita

「そうやってGraphQLスキーマが育ちすぎたら当然エンドポイントを分割しようという話が出てきますけど、今度は分割されたエンドポイント同士のセッション管理をどうするかという問題も出てくるんですよ」「エンドポイントごとのログイン状態のようなstateをフロントエンド側のコードで判断するのは大変そう…」

「そうなったらAWSのAPI Gateway的な方法を使うことになってくるでしょうね: Railsウォッチでも何度か話題にしたEnvoy(ウォッチ20190212)も、そういう流れで出てきたんだろうと思っています」「なるほど」「きれいにやろうと思ったら、さっきの図で言うReactのAPI Libraryの層の下にもうひとつAPI Gateway的な層も必要になってくるんじゃないかと思ったりしますね」「また層が増えるのか〜」「というようなことを最近GraphQLを使いながら思いました」

参考: Amazon API Gateway(規模に応じた API の作成、維持、保護)| AWS
参考: Envoy Proxy - Home


「質問です: 図のfederated GraphQのfederatedは、ここではどういう状態を指すんでしょうか?」「技術用語ではfederationという言葉がよく使われますが↓、ここで使われているfederated GraphQLは複数のマイクロサービスを取りまとめているGraphQL、という程度の意味じゃないかと思います」「なるほど、辞書的には連合とかそういう意味でしたね」

参考: フェデレーション (federation)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210126後編)Google Cloud FunctionsがRubyをサポート、Ruby 3のパターンマッチングでポーカーゲームほか

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

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

Rails公式ニュース

Ruby Weekly


週刊Railsウォッチ(20210202後編)Ruby 3 irbのmeasureコマンド、テストを関数型言語のマインドセットで考えるほか

$
0
0

こんにちは、hachi8833です。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

⚓Ruby

⚓ Ruby 3.0 irbのmeasureコマンド(Ruby Weeklyより)


つっつきボイス:「Ruby 3.0の新機能紹介記事です」「へ〜、irbでmeasureコマンドを実行すると行ごとに時間を測ってくれるのか」「ストップウォッチ的な機能ですね⏱」「たしかに」

# 同記事より
irb(main)> measure
TIME is added
=> nil

irb > sleep 1
processing time: 1.000649s
=> 1

irb > 1
processing time: 0.000025s
=> 1

irb > measure :off
=> nil

参考: library irb (Ruby 3.0.0 リファレンスマニュアル)

「Ruby 3.0の新機能がてんこもりで、この機能に気づかなかった↓」

参考: Ruby 3.0.0 リリース


ruby-lang.orgより

「お、記事によるとmeasureコマンドにstackprof↓を指定するともっと細かく調べられるのか」「irbでこういう機能が使えるようになると、ちょっとした測定が手軽にやれて便利👍」「この辺の機能を使いこなせると強くなった気持ちになれそうですね」

tmm1/stackprof - GitHub

「Ruby 3.0の機能なので新しい環境でしか使えませんけどね」「Ruby 3.0の新機能、がっつり調べないといけないな〜」「パターンマッチングとかもですね」


後で記事のスニペット↓を動かしてstackprofをやってみました。スニペットではさりげなくRuby 3.0のendレスメソッド定義も使っていますね。

# 同記事より
def snippet()= 10_000.times { Date.parse(Date.today.to_s).year != 2020 }

参考: 改めて整理する Ruby 3.0 に実験的に入る予定のエンドレスメソッド定義構文と右代入演算子について - Secret Garden(Instrumental)

以下は素のRuby 3.0.0でも実行できるよう、gem install stackprof activesupportを実行し、irbでrequire 'active_support/core_ext/date'を実行しました。

3.0.0$ ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin19]
3.0.0$ gem install stackprof activesupport
Fetching stackprof-0.2.16.gem
Building native extensions. This could take a while...
Successfully installed stackprof-0.2.16
Building YARD (yri) index for stackprof-0.2.16...
Done installing documentation for stackprof after 0 seconds
Fetching activesupport-6.1.1.gem
Successfully installed activesupport-6.1.1
Building YARD (yri) index for activesupport-6.1.1...
Done installing documentation for activesupport after 1 seconds
2 gems installed
3.0.0$ irb
irb(main):001:0> require 'active_support/core_ext/date'
=> true
irb(main):002:0> def snippet()= 10_000.times { Date.parse(Date.today.to_s).year != 2020 }
=> :snippet
irb(main):003:0> measure :stackprof
STACKPROF is added.
=> nil
irb(main):004:0> snippet
==================================
  Mode: cpu(1000)
  Samples: 34 (0.00% miss rate)
  GC: 2 (5.88%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
        17  (50.0%)          17  (50.0%)     Regexp#match
         6  (17.6%)           6  (17.6%)     MatchData#begin
         3   (8.8%)           3   (8.8%)     Date.today
         3   (8.8%)           3   (8.8%)     String#gsub!
         2   (5.9%)           2   (5.9%)     (sweeping)
         1   (2.9%)           1   (2.9%)     Integer#div
        29  (85.3%)           1   (2.9%)     Date.parse
         1   (2.9%)           1   (2.9%)     String#[]=
        32  (94.1%)           0   (0.0%)     IRB::Context#evaluate
        32  (94.1%)           0   (0.0%)     IRB.init_config
        32  (94.1%)           0   (0.0%)     StackProf.run
        32  (94.1%)           0   (0.0%)     IRB::Irb#signal_status
        32  (94.1%)           0   (0.0%)     RubyLex#each_top_level_statement
        32  (94.1%)           0   (0.0%)     Kernel#loop
        32  (94.1%)           0   (0.0%)     Kernel#catch
        32  (94.1%)           0   (0.0%)     IRB::Irb#run
        32  (94.1%)           0   (0.0%)     IRB.start
        32  (94.1%)           0   (0.0%)     <top (required)>
        32  (94.1%)           0   (0.0%)     Kernel#load
        32  (94.1%)           0   (0.0%)     <main>
        32  (94.1%)           0   (0.0%)     <main>
        32  (94.1%)           0   (0.0%)     IRB::WorkSpace#evaluate
        32  (94.1%)           0   (0.0%)     Kernel#eval
         2   (5.9%)           0   (0.0%)     (garbage collection)
        32  (94.1%)           0   (0.0%)     <main>
        32  (94.1%)           0   (0.0%)     Object#snippet
        32  (94.1%)           0   (0.0%)     Integer#times
        32  (94.1%)           0   (0.0%)     IRB::Irb#eval_input
=> 10000
irb(main):005:0>

⚓ HexaPDFを最適化した話(Ruby Weeklyより)


つっつきボイス:「こちらの記事は、PDF生成に使うHexaPDF gemでTrueTypeフォントを使うと測定結果↓が妙に遅いので、ランタイムプロファイリングとメモリのプロファイリングを実行し、原因を突き止めて修正するまでをやってますね」「これは泥臭そうな作業…」

Time Memory File size
hexapdf 1x 557ms 34.160KiB 452.598
hexapdf 5x 1.891ms 45.244KiB 2.258.904
hexapdf 10x 3.754ms 57.364KiB 4.517.825
hexapdf 1x ttf 634ms 33.044KiB 549.522
hexapdf 5x ttf 2.335ms 48.908KiB 2.687.124
hexapdf 10x ttf 4.693ms 63.568KiB 5.360.947

同記事より

「修正後のベンチマークはこれか↓」

Time Memory File size
hexapdf 1x 572ms 34.680KiB 452.598
hexapdf 5x 1.840ms 45.352KiB 2.258.904
hexapdf 10x 3.504ms 57.464KiB 4.517.827
hexapdf 1x ttf 542ms 33.540KiB 546.390
hexapdf 5x ttf 2.099ms 43.600KiB 2.670.953
hexapdf 10x ttf 4.016ms 63.584KiB 5.328.382

同記事より

「Rubyコードが遅い原因を突き止めて解決するまでの泥臭くてつらい作業をまとめた、いい記事だと思います👍」「こういう作業をやれる人がチームにいるかどうかで、チームの限界性能が変わってきますよね」「たしかに」

「この記事ではHexaPDFの文字glyphを処理する周辺の効率化を行っているみたい」「つくづく、プログラマーはいろんなことを知らないといけないんだなと改めて思いますね」「PDFの仕様を以前読んだことありますけど、あれはホントわからない」「ホントに😆

参考: TrueType - Wikipedia
参考: PDFテクノロジーセンター | Adobe PDFテクノロジーセンター — PDF仕様などが置かれています

⚓ 関数型言語のマインドセットでオブジェクトをテストする(Ruby Weeklyより)


つっつきボイス:「thoughtbotさんの記事を久しぶりにご紹介します」「この記事はタイトルからして、テストの視点をどこに置くかという話のようですね」「意外と短いかも」

「テストに関する記事を書こうとすると、『どういう視点でテストすべきか』『どういう実装方法でテストを実装するか』など考慮すべきことが増えてきて、いわゆる『風呂敷を閉じられない』状態になりがちなのですが、この記事は1つの視点に絞って短くまとめているのが良さそうですね👍

「お、この記事でfunctionalって言ってるのはどうやら関数型言語のことみたい」「あの難しそうなヤツですか」「ということは、記事で言っているside effect(副作用)も関数型言語における副作用を指すようです」「あ、そういうことか」「このあたりの話は、関数型言語をわかっている人ならもうわかってるのかもしれませんね」

参考: 関数型言語 - Wikipedia
参考: 副作用 (プログラム) - Wikipedia

「それにしても関数型言語が出てくるとは」「mindsetとあるのは関数型言語のマインドセットなのか」「そういう視点でテストを書くということでしょうね: テストの粒度とかテストを書くときの視点って、一般的に説明しようとすると抽象度が上がりがちで、わかったような気がするけど結局わからなかったということになりがちなんですが、『関数型言語の副作用のようなものだ』という視点を持ち込むことで、ある種のテストの書き方を理解しやすくしようという意図をこの記事に感じました」「なるほど」「もちろんテストの書き方の視点はひとつではなく、これ以外にもいろいろあります」

「ところで、副作用というとちょっとネガティブなイメージありますよね」「あるある」

参考: 副作用 - Wikipedia

🔗 その他Ruby


つっつきボイス:「RubyのWebサーバーPumaのメンテナをやっているNate Berkopecさんのツイートが気になったので拾ってみました」

puma/puma - GitHub

「スレッドがこのツイートから始まっているのでそれ以前の文脈はわかりませんが、『あなたはActive Supportのpresent?blank?を普段から使いすぎているか』というような話かな?」「自分はそう思いました: present?blank?を乱用するってどういうことなのかが気になりました」

Railsでnil? blank? empty? present?を使いこなそう

「Active Supportのpresent?blank?は、どういう使い方をするかにもよるんですが、かの有名な『PHPの==による比較』↓に少し通じるところがあるんですよ」「あ、PHPですか」「そういえばJavaScriptの=====も意味が違ってたかも」「JavaScriptの==も厳密でない比較ですね」

参考: 条件式の「==」と「===」の意味と違いの3つのポイント | PLUGMIZE(プラグマイズ)
参考: JavaScript 忘れがちな === と == の違い - Qiita

「よく考えてみると、present?blank?って、存在するのは何なのか、空白なのは何なのかが実はファジーじゃないですか: 調べる対象は文字列の空文字("")かもしれないし、数値のゼロ(0)かもしれない」「あ〜!」「nilかもしれないですよね」

present?blank?は元のオブジェクトが何であってもチェックできるのでとても便利なんですが、空文字かどうかを調べるならString#empty?がありますし、数値がゼロかどうかを調べるならNumeric#zero?がありますし、nilかどうかを調べるにはnil?で調べるべきでしょうね」「う、心当たりが😅

「上のツイートは、『本来そうやってチェックすべき箇所をblank?とかで雑にチェックしてる人はまさかいないよね?』ぐらいの意味合いなんだろうなと自分は受け止めました」「なるほど、ちょっと腑に落ちてきました」

「ちょうどNateさんのツイートの続きにこんなのもあった↓、まさにtruthy/falselyしか気にしてない人、つまりtrueっぽければそれでいい、falseっぽければそれで済ませるという人には、present?blank?をそうやって雑に使って欲しくないということなんでしょうね」「Nateさんの気持ちがちょっと見えてきたかも」

「とりあえずのノリでpresent?blank?を使ってくれるなよと」「わかってて使うならいいけど、わかってないうちに使ってくれるなよと」「overuseって言ってるのはそういうことなんでしょうね」

「でも自分は作り捨てのコードなら割と使っちゃいますけどね😆」「😆」「でもpresent?blank?もActive Supportのメソッドなので、たまに素のRubyで使おうとしたら『あ、Active Support入れないと使えないんだった』と思い出したりということもありましたね」

⚓クラウド/コンテナ/インフラ/Serverless

⚓ sudoに脆弱性が見つかる


つっつきボイス:「BPSの社内Slackに貼っていただいた情報です」「上の脆弱性情報記事、今日見てみたら情報が更新されてる: ありがたい!」

「sudoの脆弱性はLinux OSの”ほぼ”すべてが対象なのがキツい」「Dockerコンテナのようにコンテナ化されたLinuxは、コンテナにsudoが入ってなければ影響はありません」

参考: sudo - Wikipedia

「それにしても久しぶりに影響の大きい脆弱性ですね」「だいぶ前にbashで起きた脆弱性以来かも↓」「ありましたね、bashの脆弱性」

参考: 更新:bash の脆弱性対策について(CVE-2014-6271 等):IPA 独立行政法人 情報処理推進機構 — 2014年の情報です

「まだ詳しく見ていないんですが、今回のsudo脆弱性はsudoのコンフィグを変えて対処できる感じではなさそうなんですよ: と思ったら上の脆弱性情報記事にちょうど書いてあった↓」「バイナリをリネームですって」「脆弱なsudo実行バイナリがユーザーから実行可能な場所にある限り起きちゃうんですね…」

CentOS6を使用されている場合には、
sudoを使えないようにする(バイナリをリネームする等)
が、現時点での緩和策では一番確実だと思われます。
同記事より

「おそらく今回の脆弱性は、sudoをアップデートせずに回避するのが難しそうです: 特定の機能に潜む脆弱性ならコンフィグでその機能を無効にすることで基本的に回避できますが、上の脆弱性情報記事を見る限りではsudoをユーザー権限で実行できさえすれば攻撃できてしまうように見えますね」「う〜む」「これはエグい」

「それにしては比較的世の中が静かなよう見えますが、今後どこかのタイミングで急に騒がしくなるかもしれませんね」「情報出てから24時間ぐらいしか経ってないからかなと思ったら、数日経ってるんですね」「sudoの脆弱性は今後も見守っておきたいです」

⚓JavaScript

⚓ mongoose: Node.js向けMongoDBクライアント&ライブラリ

Automattic/mongoose - GitHub


つっつきボイス:「こういうのがあると知人から教わりました」「Node.jsでMongoDBを扱う、こういうのは前からありそうかな」「バージョン5とあるぐらいだから前からあるんでしょうね」「よく見ると★も異様に多い」

参考: The most popular database for modern apps | MongoDB


以下はつっつき後に見つけたツイートです。

⚓言語/ツール/OS/CPU

⚓ 言語は継続


つっつきボイス:「各種プログラミング言語のソースをコレクションですか」「さすが言語を愛する人」「私が手伝っているGoby言語↓も恥ずかしながら開店休業に近い状態です😅

Goby: Rubyライクな言語(2)Goby言語の全貌を一発で理解できる解説スライドを公開しました!


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210201前編)Webpackerのガイドがマージ、RailsはRuby 3でどのぐらい速くなったかほか

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

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

Ruby Weekly

Rails 6.1: whereの関連付けでjoinedテーブルのエイリアス名を参照可能になった(翻訳)

$
0
0

概要

概要原著者の許諾を得て翻訳・公開いたします。

訳注
本記事で言及している#40106のマージコミットにはv6.1.1タグが付けられていますが、2020/08/31の時点でmasterにマージされた#40106は実際にはRails 6.1のコミットリストに含まれており、Rails 6.1 Active RecordのChangelogにも記載されているので、タイトルおよび訳文ではRails 6.1と表記しています。

Rails 6.1: whereの関連付けでjoinedテーブルのエイリアス名を参照可能になった(翻訳)

Rails 6.1で、whereの中でモデル名のエイリアスを指定できる機能が追加されました(#40106)。

たとえば以下のようなEmployeeモデルがあるとします。

class Employee < ActiveRecord::Base
  has_many :subordinates, class_name: "Employee", foreign_key: :manager_id
  belongs_to :manager, class_name: "Employee"
end

管理職が上に付いていない社員のmanager_idnilになります。

変更前

上に付く管理職の名前がSamである社員をすべてフェッチするシナリオで考えてみましょう。

この場合、フェッチ方法は2とおりあります。

Employee.find_by(name: "Sam").subordinates
SELECT "employees".* FROM "employees" WHERE "employees"."name" = $1 LIMIT $2  [["name", "Sam"], ["LIMIT", 1]]
SELECT "employees".* FROM "employees" WHERE "employees"."manager_id" = $1 /* loading for inspect */ LIMIT $2  [["manager_id", "e4f2c753-6057-4952-9947-d1a543b2d1c7"], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<Employee id: "3e584548......
Employee.joins(:manager).where(manager: { name: "Sam" })
ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "manager")
LINE 1: ..._employees"."id" = "employees"."manager_id" WHERE "manager"....

2番目のクエリはmissing FROM-clause entryエラーになります。

ここではmanageremployeesテーブルを参照することを期待していますが、Railsはそのことを認識できませんでした。

変更後

この問題を修正するため、Rails 6.1ではwhereemployeesテーブルへの参照にエイリアス名を使えるようにする修正が加えられました。

Employee.find_by(name: "Sam").subordinates
SELECT "employees".* FROM "employees" WHERE "employees"."name" = $1 LIMIT $2  [["name", "Sam"], ["LIMIT", 1]]
SELECT "employees".* FROM "employees" WHERE "employees"."manager_id" = $1 /* loading for inspect */ LIMIT $2  [["manager_id", "e4f2c753-6057-4952-9947-d1a543b2d1c7"], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<Employee id: "3e584548......

Employee.joins(:manager).where(manager: { name: "Sam" })
SELECT "employees".* FROM "employees" INNER JOIN "employees" manager ON manager."id" = "employees"."manager_id" WHERE "manager"."name" = $1 /* loading for inspect */ LIMIT $2  [["name", "Sam"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Employee id: "3e584548......

このユースケースは、自己参照的なモデルとエイリアス化されたテーブルで多くの場合有用です。

関連記事

Rails 6.1で form_withのデフォルトが「remoteなし」に戻った(翻訳)

Rails 6.1: 関連付けをバックグラウンド削除する「dependent: :destroy_async」(翻訳)

週刊Railsウォッチ(20210208前編)Rails次期リリースがバージョン7に決定、thoughtbotのアプリケーションセキュリティガイドほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今回は以下のコミットリストのChangelog変更から見繕いました。

🔗 Action Cableクライアントの”thundering herd”を防止


つっつきボイス:「thundering herdって何だろう?」

herd n. 家畜[動物]の群れ; 《軽蔑》 (同じような行動などをする)群集.

「ググったら出てきた↓」「へ〜、こういう状態をthundering herdって言うのか!」「マルチスレッドなプログラムで何も考えずにシグナルを使ったときの現象に似てますね: シグナルマスクせずにシグナルを送ると配下の全スレッドがシグナルに応答してしまうので、どれか一つが応答すればよいだけの場合には無駄が大きいんですが、そのようなメッセージバスを共有するモデルに共通する問題に近いかもしれませんね」

参考: WebサーバでのThundering Herdは過去の話? | monolithic kernel

Thundering Herdというのは、ひとつのソケットに対してselectやepollのような通信可能になるのを待つシステムコールを利用して複数のプロセス (またはスレッド) が待機していると、通信可能になったときに本来ならひとつのプロセスだけが起きればいいところを、待機していたすべてのプロセスが起こされてしまうという問題です。この場合、実際に通信できるのはたまたま最も早く通信を開始したプロセスのみで、他のプロセスが起きたことは無駄になってしまいます。
blog.mono0x.netより

参考: Thundering herd problem - Wikipedia

「このプルリクはAction Cableクライアントが対象なので、そこで発生する可能性のあるthuntering herdとやらを修正するためのものなんでしょうね」「あ〜なるほど」「Action Cableで膨大な数のセッションを管理しているときにこれが起きるとかなりのパフォーマンス低下につながる可能性があるので、こういう問題が解決されるのはよいと思います👍

「プルリクのコードを覗いてみるとJavaScriptのコードの追加が多いですね」「Changelogを見ると再接続と書かれているので↓、やはりクライアント側の修正なんでしょうね」

Action Cableクライアントに、サーバー接続が喪失した後のクライアント再接続における”thindering herd”を防止する以下のセーフガードが含まれるようになった。

  • クライアントは、サーバーへの直近のpingの後から最初の再接続試行までの間、staleスレッショルドのランダムな期間でウエイトをかける。
  • 以後の再接続試行では、(対数的バックオフではなく)指数的バックオフを用いるようになった。再接続試行までの遅延時間の増加を当初ゆるやかにするために、デフォルトの指数の基数部は2より小さい値になっている。
  • 再接続の試行と試行の間の遅延時間には、それぞれランダムなジッターが適用される
    Changelogより大意

参考: 仮数部、指数部、基数:意味と計算方法 - 具体例で学ぶ数学

「なるほど、以前も話したランダムなジッター(jitter)を遅延時間に加えているのか(ウォッチ20191216): これはJavaScriptの変更ですが、サーバー側にも影響するものですね」「おぉ?」「たくさんのクライアントが再接続を同時にかけてくるときは、ランダムなジッターを加えてアクセスが同時に集中しないようにするという手法です」「あ〜、思い出してきました」

参考: ジッター - Wikipedia

「ジッターを加えることでクライアントの再接続が成功する可能性も高まりますし、再接続するまでの時間も短縮できますね」「なるほど!」「ジッターがないと、多数のクライアントが同じ時刻に一斉に再接続をかけてきてしまいます」「これは勉強になりました」

「この問題自体はWebSocketの話のようですが、クライアントサーバー形式の通信では普遍的に起きうる現象ですね」「なるほど」「なお、このプルリクにはbackoff timerのアルゴリズム修正も入っているようですね: プルリクのコメントでかなり細かく具体的なケースのやり取りがされているので、興味ある人はコメントも読んでみると面白いと思います」


特に必要はないと思いますが、thundering herdという現象をうまく表せる既存の熟語が見当たらないので、英語ママとしました。

どうやら元ネタはZane Gray作の西部劇小説「Thundering herd」のようで、以下の映像にはBuffalo Stampedeというサブタイトルが付いていました。要するに時代劇ですね。

参考: ゼイン・グレイ - Wikipedia

🔗 Rails 6.2がRails 7になった


つっつきボイス:「@rafaelfrancaさんのコミットです」「え、Rails 6.1の次はもうRails 7になるんですか?」「どんな目的なんでしょうね?」「昨年末にリリースしたHotwireを入れるのかな?🤔

# Gemfile.lock#L30
PATH
  remote: .
  specs:
-   actioncable (6.2.0.alpha)
-     actionpack (= 6.2.0.alpha)
-     activesupport (= 6.2.0.alpha)
+   actioncable (7.0.0.alpha)
+     actionpack (= 7.0.0.alpha)
+     activesupport (= 7.0.0.alpha)
...

速報: Basecampがリリースした「Hotwire」の概要

「6.0から6.1のようにマイナーバージョンが進むときって、基本的には大きく変わらないものだという通念がありますけど、Railsの場合マイナーバージョン番号が更新されるときにも大きな変更が時たま入ってきたりしますよね」「そう思います😆」「考えてみれば、Rails 6.1は6.0と比べてだいぶ変わりましたよね」「自分でも6.1に上げたときに変更点が割と多い印象でしたね、個人的には大きな支障はありませんでしたが」

「変更が大きかったといえば、やはりRails 3.0からRails 3.1のときでしょう」「そうそう!あれはキツかった」「アセットパイプラインがない世界から3.1でいきなりアセットパイプラインのある世界への移動は大きかった」「breaking changeとしてはでかい」

参考: アセットパイプライン - Railsガイド

「もしかするとrequired Ruby versionを変えるから7にするのかな?」「コミットのリリースノートを見ると↓、Rails 7ではRuby 2.7以上が必須、3.0以上が好ましいとなってる」「ホントだ」

  • Ruby 2.7.0+ required, Ruby 3.0+ prefered
    コミットのguides/source/7_0_release_notes.mdより

「そういえば、ちょうど今年1月の銀座Railsで、Ruby 3.0とRails 6.1のバージョニングについて@yahondaさんが話してたのを思い出しました↓」

参考: 【オンライン開催】銀座Rails#29@リンクアンドモチベーション - connpass

「スライドにも、required Ruby versionが上がるのは少なくともRails 5以降はRailsのメジャーバージョンアップのみ、とありますね」「へ〜!」「これを元に推測するなら、メジャーバージョンアップを決めた背景にrequired Ruby versionを変更したかったというのがあるのかもしれませんね」

「ところで、このコミットのコミットメッセージに”big plans”と書かれているんですが↓、具体的な情報は今のところまったく見当たりませんでした」「big planですか」「big planが何なのかは手がかりなしです」

We have big plans for the next version of Rails and that require big versions.
同コミットより

🔗 disallow_raw_sql!で末尾ホワイトスペースを削除するようになった


つっつきボイス:「なるほど、pluckの末尾に\nなどが入っているとエラーになるので、削除して動くようにしたんですね↓」「末尾のホワイトスペースならメソッド側で除去しても大丈夫そう」

# 変更前
User.pluck("first_name, last_name") #=> SELECT first_name, last_name FROM "users"
User.pluck("first_name, last_name\n") #=> ActiveRecord::UnknownAttributeReference
# 変更後
User.pluck("first_name, last_name") #=> SELECT first_name, last_name FROM "users"
User.pluck("first_name, last_name\n") #=> SELECT first_name, last_name\n FROM "users"

参考: ホワイトスペースとは - IT用語辞典 e-Words

「Railsのpluckに改行入り文字列を渡したことってなかったかも」「自分はpluckで複数カラムを指定するときにカンマ区切り文字列ではなく複数のシンボル引数を渡すようにしているので、このケースでハマることはありませんでしたね」

Rails: pluckでメモリを大幅に節約する(翻訳)

🔗 新機能: primary_abstract_class


つっつきボイス:「タイトルにexposeと書かれているのでprivateなメソッドをpublicにしたのかと思ったら、既存のself.abstract_class = trueprimary_abstract_classで明示的にpublicメソッドで書けるようにしたようですね」

参考: [Rails] self.abstract_class = true の意味と挙動 « Codaholic
参考: Active Record で複数のデータベース利用 - Railsガイド

# Railsガイドより
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :primary_replica }
end

self.abstract_class = trueって使ったことなかったかも…」「この書き方は、Railsのマルチプルデータベースなどで使うことがあります: データベースごとにベースクラスを作るけど、そのクラスは直接newさせたくないとき、つまりActive Recordのクラスとして直接使って欲しくないとき」「あ、今理解しました😅

「コードの中でも最終的にself.abstract_class = trueを呼んでいる↓」

      def primary_abstract_class
        if Base.application_record_class && Base.application_record_class != self
          raise ArgumentError, "The `primary_abstract_class` is already set to #{Base.application_record_class}. There can only be one `primary_abstract_class` in an application."
        end

        self.abstract_class = true
        Base.application_record_class = self
      end

「考えてみれば、従来のself.abstract_class = trueのように従来のクラス変数を直接上書きする方式は、Railsガイドに書かれているとはいえやや直接的すぎるという見方もできるので、こうやってメソッドで設定する方がよさそうな気はします」「従来どおりにself.abstract_class = trueを書くこともできそうに見えるけど、メソッドができたなら今後はこれで設定する方がいいのかもしれませんね」


アプリケーションでprimary_abstract_classを設定する方法を提供する。
マルチプルデータベースのRailsアプリケーションで、名前がApplicationRecordでないプライマリ抽象クラスを使っている場合に、特定のクラスをprimary_abstract_classに設定できるようになった。

class PrimaryApplicationRecord
  self.primary_abstract_class
end

アプリケーションが起動すると、自動的にプライマリまたはデータベースコンフィグファイルの最初のデータベースに接続する。マルチプルデータベースでその後connects_toを実行するには、デフォルトのコネクションが ApplicationRecordのコネクションと同じであることを認識する必要がある。しかしアプリケーションによっては ApplicationRecordの名前が異っていることもある。この変更によって、Active Recordが同じデータベースに対してコネクションを複数オープンしてしまうことが防止される。
同Changelogより大意

🔗 Active Storageで定義済みのvariantを使えるようになった

# 同PRより
class User < ActiveRecord::Base
  has_one_attached :avatar, variants: {
    thumb: { resize: "100x100" },
    medium: { resize: "300x300", monochrome: true }
  }
end

class Gallery < ActiveRecord::Base
  has_many_attached :photos, variants: {
    thumb: { resize: "100x100" },
    medium: { resize: "300x300", monochrome: true }
  }
end

<%= image_tag user.avatar.variant(:thumb) %>

つっつきボイス:「Active Storageでようやくvariantを定義して、上のvariant(:thumb)のように名前で呼べるようになったんですね」「variantとは?」「上のコードのthumbmediumのような、Active Storageで扱う画像のサイズの種類のことですね: 100x100などがvariantの設定」「あ、それのことでしたか」

「なお、これはpaperclipやshrineといったgemには普通にある機能です」「そうそう」「Active Storage自体にはvariantの機能が入っていますが(ウォッチ20200114)、この改修が追加されたということは定義済みのvariantを使う機能がこれまでなかったということなんでしょうね」「こうやって他のgemの機能も取り入れてActive Storageがよくなっていくということですね」

thoughtbot/paperclip - GitHub

shrinerb/shrine - GitHub

🔗Rails

🔗 銀座Railsスライド『Active Recordから考える次世代のRuby on Railsの方向性』


つっつきボイス:「上は以前Railsdmで評判を呼んだ『Ruby on Railsの正体と向き合い方』↓の@_yasaichiさんが銀座Rails#29で発表したスライドです」「あれは2年ぐらい前でしたっけ?」「そういえばウォッチでも取り上げたときもとても評判がよかったですね(ウォッチ20190401)」

「前回のスライドは、Ruby on Railsの歴史的経緯を詳しく説明してから、それを踏まえてRailsが今後どう進んでいくのかを考察する内容になっていましたが、今回はフロントエンドも含めて同様の考察を行っています」「おぉ〜!」「Railsのフロントエンド周りの将来像は、Railsをやってる人たちなら誰でも気になるところですね」「これは後で読まなきゃ」「Railsのまとめ方なども含めていろいろ参考になります: RailsをやっていればRailsについて思うことはいろいろあるわけですが、今回のスライドもRailsに入れ込みすぎず突き放しすぎない視点でRailsの現状と将来像を捉えようとしているのがとても参考になります🙏

「なおこの間の銀座Rails29はその後の懇親会がディープな話で盛り上がりましたね」

🔗 1個のファイルでRailsアプリを書く(Ruby Weeklyより)


つっつきボイス:「1個のファイルでRailsアプリを書く…とは?」「1個のファイルでRails書けるかどうかチャレンジするってことかしら?」「routes.rbにlambdaを書いておしまいにするってこと?それだともうRailsではないですよね」「たしかに」

「ああ、Railsのconfig.ruファイルに全部書くということか↓」「あ、理解しました!」「これスクショなのか…」


同記事より

「通常ならapplication.rbやらroutes.rbやらのファイルに分かれているのを、ここではRails::Applicationを継承したクラスを作ってそこに全部書くということらしい」「へ〜!」「スゴいことしますね」「routes.appendは本来routes.rbで行われますけど、そういうのもここに書かれている」

「やろうと思えばRailsでこんなこともできるという記事」「さすがに本番でやろうとは思いませんでした😆」「本当にRailsがシングルファイルアプリになるなら別の意義がありそうですが、この記事のは実用目的ではなさそうかな」

参考: Rails の初期化プロセス - Railsガイド — config.ruについても説明されています

🔗 thoughtbotの「アプリケーションセキュリティガイド」


つっつきボイス:「thoughtbotの記事を追っててこのセキュリティガイドを見つけました」「日付は昨年7月なのか」「それほど長くなさそう」

「へ〜、Personally-Identifying Information (PII)という言葉があるのか: どこまで一般的なものかどうかはわかりませんが、このような個人を特定可能な情報の扱いに注意することは大事ですね」「たしかに」

参考: 個人を特定できる情報 (PII = Personally Identifiable Information) とは何か? - Twilio

「この記事で説明されていることは、セキュリティを考えるうえでどれも当たり前の話ではありますが、こうやってリスト形式で簡潔にまとめられていることで、改めて読んでみると自分の理解が不足している部分を見つけられたりするのがいいと思います👍」「そうですね」「何となくわかっているつもりのことでも、自分がこの記事のように説明できない部分を見つけたら、改めて調べて勉強しないといけない気持ちになれますね」「自分も頑張らなきゃ」

🔗 Google Cloud Functions


つっつきボイス:「この間のウォッチでも報じたGoogle Cloud Functionsのことですね(ウォッチ20210126)」「そうそう、ツイートにもあるようにRubyで使うbundlerのbundle installをGCP側でやってくれるんですよ」「それ優秀じゃないですか!」

参考: Bundler: The best way to manage a Ruby application’s gems

「簡単なRubyコードを動かす分にはGoogle Cloud Functionsの方がAWS Lambdaよりも便利だと思います👍」「お〜」「AWS Lambdaだと、Lambdaレイヤの状態を考慮しないといけないなど、デプロイに多少ノウハウが必要なんですよ」「なるほど」「コンテナでデプロイするなら別ですけど、場合によってはコンテナだとオーバーキル気味になることもあります」

「RubyをサポートするようになったGoogle Cloud Functionsのいいところは、Rubyのソースコードをそのままアップロードして公開できる点だなと思います」

参考: Cloud Functions  |  Google Cloud

参考: AWS Lambda(イベント発生時にコードを実行)| AWS

🔗 その他Rails

つっつきボイス:「4月にオンライン開催予定のRailsConf 2021の登壇者募集のお知らせです」「CFP(Call for proposal: 登壇者募集)の締め切りが今月15日、ってもうすぐなのか」「発表したい人はどうぞ」

「お、募集ページにCFPのstatsが載ってますね↓」「これは?」「何日に何人応募があったかというグラフ」「こういうグラフはたいてい締切日になるとガガっと上昇します😆」「そういうものですよね、昔から」


同サイトより

参考までに、昨年のRailsConf2020のキーノートスピーチ動画を貼っておきます。


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210202後編)Ruby 3 irbのmeasureコマンド、テストを関数型言語のマインドセットで考えるほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20210209後編)Rubyでミニ言語処理系を作る、Kernel#getsの意外な機能、CSSのcontent-visibilityほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 Rubyでミニ言語処理系を作る

sonota88/vm2gol-v1 - GitHub


つっつきボイス:「少し前の記事です」「お〜、Rubyで言語処理系を作ってライフゲームまで動かしたんですね」「VMも自分で作ったのか」

参考: 言語処理系とは

「へ〜、この記事では便利なサポート系の外部ツールを使わずに、Rubyと最小限のgemだけで言語処理を書いてるんですね」「これはいい勉強になりそう」「こういうのは作ってて楽しいですよね」

「小さな言語処理系を一度自分で作ってみるのは大事」「そうそう」「難しすぎることをしてないのもポイントだと思います: リポジトリもシンプルですし、外部ツール群的なものも作っていませんね」「なるほど」

「外部ツールを作り始めると処理系全体がどんどん大きくなってしまいますけど、この記事はVMも純粋なVMにとどめていますし、コンパイラを作らずにコード生成機を直接書いてそこからアセンブリコードを出力しているようですね」「ふ〜む」「グラフィック部分も手作りしているとは面白い!」「外部ツールを作らずに、自分が掌握できる最小限の構成で処理系を作るというのがいいと思いました👍


「ところで、大学のコンピュータサイエンスの授業で使われていたような古典的な教科書に、よくこういう感じのミニ処理系の作り方が載ってましたね: LISPの教科書あたりでそういうのを見た覚えがちょっとあります」「そうそう、スタックマシンを作って動かすような言語処理系はよくこういう構成になりますよね」「オレオレチューリングマシン的なヤツですね🦾

参考: LISP - Wikipedia
参考: スタックマシン - Wikipedia
参考: チューリングマシン - Wikipedia

「自分で言語処理系を作るときは、とにかく最後まで作り切れるかどうかが最大の分かれ目」「そうそう、そこなんですよ」「小さくても言語系を作るのは根性要りますから」「でもそれを乗り越えて作り切れば、得るものは大きい」

「自分なら、ひとりでやるより人数集めて勉強会形式でやるかも」「自分はむしろひとりでやりたいかな」「お、こういうものこそ大学のゼミみたいな場の方がはかどりそうですけど?」「そういう面ももちろんあると思いますけど、こういうものを作るなら他の人の進捗を待たずに自分のペースでやりたい方なので」「それもわかります😆

🔗 method_missingの実用的な使い方3種(Ruby Weeklyより)


つっつきボイス:「Rubyのmethod_missingは、エラーハンドリング、メソッドのdelegation、DSL(Domain Specific Language: ドメイン固有言語)やライブラリで使える、まさにそのとおりですね」

参考: BasicObject#method_missing (Ruby 3.0.0 リファレンスマニュアル)
参考: ドメイン固有言語 - Wikipedia

🔗 method_missingよもやま話

「ところで、最近はRubyでDSLを作るいい教材ってあるのかな?以前はRubyでDSLを作るいい学習サイトがあったんですが、今は閉鎖してしまったんですよ」「ありゃ、残念」「そういう教材では必ずといっていいほどmethod_missingを使うテクニックが紹介されていますね」「『パーフェクトRuby』のような大きな書籍ならカバーされていると思いますが、RubyのDSLでのmethod_missingの使い方を手軽に学べる本もあるといいですよね」

「他の言語をやっていた人がRubyをやってみて驚くことのひとつが、このmethod_missingだろうなと思います」「たしかに!」「Rubyではこんなことができるのか!という一種の感動がありますね」


学習サイトの代わりに、method_missingとDSLについて書かれている英語記事をいくつか見繕いました。

参考: Writing a Domain-Specific Language in Ruby
参考: Metaprogramming: Writing in Ruby with... Ruby | Toptal

🔗 Rubyのgets


つっつきボイス:「意外に短い記事ですね」「Rubyの生のgetsに驚きの挙動ってあったかな?」

# 同記事より
#!/usr/bin/env ruby

puts "What is your name?"
your_name = gets.chomp
puts "Hi, #{your_name}!"

「上のコードを./gets.rb 123のように引数を渡して実行するとエラーになる、これは当然そうなりますよね」「ですよね、引数で渡したものをgetsで取れるというのは聞いたことないかも」「他の言語だとできたりするのかな?」

なお、以下は手元のRuby 3.0.0で上のコードを実行した結果です。

./gets.rb:4:in `gets': No such file or directory @ rb_sysopen - 123 (Errno::ENOENT)
    from ./gets.rb:4:in `gets'
    from ./gets.rb:4:in `<main>'

「Rubyの英語ドキュメント↓を見てみると…え?Kernel#getsにはARGV(Rubyスクリプト実行時に渡す引数の配列)を取る機能があるって書いてある!」「マジですか!?」「これは知らなかった」

参考: gets — Module: Kernel (Ruby 3.0.0)

Returns (and assigns to $_) the next line from the list of files in ARGV (or $*), or from standard input if no files are present on the command line. Returns nil at end of file.
ruby-doc.orgより

# ruby-doc.orgより
ARGV << "testfile"
print while gets

「日本語のドキュメントにも書かれてる↓」「ARGFはARGVをファイルとみなしたオブジェクトとある: ということはgetsにはファイル名を引数で渡せるのか!」「上でエラーになった123は、123という名前のファイルがないからエラーになったということなんですね」「やっと話が見えてきたかも」

参考: Kernel.#gets (Ruby 3.0.0 リファレンスマニュアル)

ARGFから一行読み込んで、それを返します。行の区切りは引数 rs で指定した文字列になります。
rs に nil を指定すると行区切りなしとみなしてファイルの内容をすべて読み込みます。ARGVに複数のファイル名が存在する場合は1度に1ファイルずつ読み込みます。空文字列 “” を指定すると連続する改行を行の区切りとみなします (パラグラフモード)。
読み込んだ文字列は組み込み変数 $_ にもセットされます。
docs.ruby-lang.orgより

「いや〜今までは記事にもあるようにずっと$stdin.getsでARGVを取ってましたけど↓、Kernel#getsでARGVを取れるなんて思いもよらなかった」「たしかに驚きですね」「getsメソッドのこの挙動にはちょっとびっくりしました」

# 同記事より
your_name = $stdin.gets.chomp

参考: argc,argvは何の略 | C言語のTipsとサンプル | C入門 基本情報対策講座のcClip

🔗 Rubyのプリントデバッグを便利にするライブラリ


つっつきボイス:「Matzがリツイートしているのを見かけたので取り上げてみました」「ricecreamというプリントデバッグに便利なgemを作った記事のようですね」

nodai2hITC/ricecream - GitHub

icは引数をそのまま戻り値として返してくれるから、式展開の中で使ったり以下のようにreturnに書いたりできるのが使いやすそう」「他にもいくつか機能があるようですね」

# 同記事より
return "result = #{ic foo.bar}"

「この機能がRuby本体に入ったら便利かも」「元記事にもありますけど、上と同じことはppでもできますね」「ちなみにpprequireしないと使えないんですよ(追記参照)」「Rubyのコンフィグでこういうデバッグ出力をオンオフできると便利かもしれませんね」「こういうツールは比較的作りやすいと思うので、自分で作ってみるのもいいと思います」

参考: library pp (Ruby 3.0.0 リファレンスマニュアル)
参考: class PP - Documentation for Ruby 3.0.0

「ところでデバッグプリントを消し忘れることってありませんか?」「それはコミット前に消さないといけませんよね」「おっしゃるとおり😅

追記(2020/02/10)

ppについて以下のご指摘をいただきました🙇。ありがとうございます!


「元記事を見ると、もともとPythonにicecreamという同じようなプリントデバッグツールがあるらしい↓」「Ruby版のicecreamだからricecreamと名付けたのかもしれませんね」「アイスクリームのアイコンがかわいい!」「sorbet↓のアイコンをちょっと思い出しました」「単にアイスクリームとシャーベットというモチーフが似ているだけだと思いますけどね」

gruns/icecream - GitHub
sorbet/sorbet - GitHub

🔗DB

🔗 Googleのsqlcommenter(Publickeyより)


つっつきボイス:「sqlcommenterはまだ使ったことがありませんが、Railsだと類似のgemが以前からいろいろありますね: ただ、他のフレームワークだとそういうツールがあるとも限らないので、そういうところでは便利なのかもしれないと思いました」

「sqlcommenterは、対応しているデータベースが比較的多いとか、OpenCensusのようなツールと組み合わせて使えたりするらしい」「いわゆるAPM(Application Performance Management)ツールでもそれに近いことができますね」「なるほど」「使いたい人が使うということでよいと思います」

参考: OpenCensus(OpenTelemetry)とは | フューチャー技術ブログ
参考: 【ツール8選】APMツールとは?基本解説やおすすめツールをご紹介! | QEEE

🔗クラウド/コンテナ/インフラ/Serverless

🔗 sudo脆弱性がmacOSにも影響


つっつきボイス:「sudo脆弱性は先週も間話題にしましたね(ウォッチ20210202)」「現時点ではAppleから情報はまだ出てないようです」

「Linuxのsudo脆弱性と似てるというだけで多少違いがありそうな感じ」「日本語記事にはそう出ていますね↓」

参考: sudoコマンドの脆弱性、「macOS」にも影響 - ZDNet Japan

「Linuxと違ってmacOSのバイナリはIntel版でもMach-Oバイナリですし、メモリマップなども違っているので、まったく同じ攻撃方法が使えるとはあまり思えませんが、sudoの脆弱性という視点から見て類似のものがMac版でも見つかった可能性ならありそう」「ふむふむ」「Macのsudoの脆弱性はソースコードレベルではたぶんLinuxのsudo脆弱性と違うだろうという気はしていますが、英語記事を眺めた感じでは、heap overflowにつながる可能性が書かれていたり、どうやらAPIレベルでは同じような結果になるらしい」

参考: Mach-O - Wikipedia
参考: Heap Overflow: Vulnerability and Heap Internals Explained - Infosec Resources

「これはAppleの対応を待つしかないでしょうね」「Windowsを使ってる人は大丈夫なんですね、いいな〜」

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

🔗 CSSのcontent-visibility

参考: content-visibility - CSS: カスケーディングスタイルシート | MDN

参考: CSS Containment - CSS: カスケーディングスタイルシート | MDN


つっつきボイス:「英語記事からの翻訳みたいなので、元記事を開いてみよう↓」

参考: content-visibility: the new CSS property that boosts your rendering performance


web.dev/content-visibilityより

「元記事にcodepenのリンクがありますね↓」「これでやってみましょうか」(一同でしばらく動かしてみる)

See the Pen
Content-visibility Demo: Base (Content Visibility on Grids)
by Una Kravets (@una)
on CodePen.

「Rerunボタンを押すといいのかな?」「動かしてみたけどまだピンとこない…」

「これかな?ちょっとわかりにくいですけど、codepenの表示を下にスクロールすると、途中でページが長くなったのが今スクロールバーに反映されたのが一瞬見えました」「え、今のがそうなんですか?」「Zoom越しだとうまく見えないかも…」「ではもう一度」「あ、ちょっと見えたかも」「下にスクロールすると何度かページの長さが変わっているように見えますね」

「CSSの末尾でcontent-visibility: auto;が指定されているから、この部分に効いてるんでしょうね」「あ、ここですか」

<!- https://codepen.io/una/pen/rNxEWLo より-->

.grid-3,
.grid-2,
.p-group-flex {
  content-visibility: auto;
}

「通常のWebページを開いてそのままにしていれば、JavaScriptでlazy loadingなどを行わない限り、ページが長くてもレンダリングはすべて終わっているものなので、普通ならスクロールしたときにページの縦の長さは変わらないはずですよね」「はい、そうですよね」「今codepenでやったように、レンダリングが終わっているはずのページを下にスクロールすると縦の長さが伸びたということは、ブラウザに表示されているページの中でそれまでビューポート↓に入っていなかった部分、つまりそれまでレンダリングされていなかった部分がレンダリングされたということなんでしょうね」「なるほど!」「ちょっと雰囲気がつかめてきたかも」「元記事を読まずに今試してみた限りでは、たぶんそうなんだろうと推測しました」「弊社のbabaさんのようなCSSに詳しい人に聞いてみたい」

参考: ビューポートの概念 - CSS: カスケーディングスタイルシート | MDN

「どうやらcontent-visibility: auto;は、ビューポートにさしかかるまでレンダリングを遅延するプロパティのようですね: これがlazy renderingのようなものだとすると、lazy loadingとは別物と考えるのがよさそうな気がしました」「なるほど、ちょっと腹落ちしました」「無条件に使うよりも、調べてから使う方がよさそう」「もう少しわかりやすい見せ方があるといいんですけどね」

auto
この要素は、レイアウトの封じ込め、スタイルの封じ込め、およびペイントの封じ込めをオンにします。要素がユーザーに関連していない場合は、その内容もスキップします。非表示とは異なり、スキップされたコンテンツは、ページ内検索、タブオーダーナビゲーションなどのユーザーエージェント機能に対して通常どおり利用可能である必要があり、通常どおりフォーカス可能で選択可能である必要があります。
MDN: content-visiblityより


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210208前編)Rails次期リリースがバージョン7に決定、thoughtbotのアプリケーションセキュリティガイドほか

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

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

Ruby Weekly

Publickey

publickey_banner_captured

速報: Rails 6.1.2がリリースされました

$
0
0

Ruby on Rails 6.1.2が今朝リリースされました。

英語版Changelogをまとめて見るにはGItHubのリリースタグ↓が便利です。v6.1.2タグの日付は日本時間の2021/0210 06:52でした。

詳しくは以下のコミットリストをご覧ください。

🔗 更新の概要

🔗 Changelogに更新が記載されている機能

以下の機能の順序はリリースノートの記載順に従っています。

本記事では、GitHubリリースタグに掲載されているChangelogに対応するプルリクやコミットへのリンクを取り急ぎ貼りました。

🔗 Changelogに更新の記載がない機能

以下はChangelogには更新の記載がありませんが、全体にmasterブランチからmainブランチへの変更やドキュメント更新のような小さな変更は加わっています。

⚓ Action Pack

コントローラのアクション内でthrowしたときに発生するエラーを修正。
Janko Marohnić
リリースノートChangelogより大意


file_fixture_pathが相対パスの場合のfixture_file_uploadのdeprecationメッセージを修正。
Eugene Kenny
リリースノートChangelogより大意

以下は#41067の修正そのものではありませんが、関連の#39086を取り上げた過去エントリです。

⚓ Active Record

sqlite3のタイムスタンプを修正。
Eileen M. Uchitelle
リリースノートChangelogより大意


destroy: :asyncをトランザクショナルになるよう修正
ジョブがキューに入るときにActive Recordのロールバックが発生する可能性がある。このとき、このジョブはデータベース削除がロールバックされていてもキューに入ってしまい問題が生じる。
この修正により、DBトランザクションのコミットが完了した後でのみジョブがキューに入るようになった。
Cory Gwin
リリースノートChangelogより大意


MySQLの接続設定でmalformed packetエラーが発生する問題を修正。
robinroestenburg
リリースノートChangelogより大意


コネクションの仕様が、"url"プロトコルが「jdbc」「http」「https」の場合に"url"キーをアダプタで受け取れるようになった。従来はプレフィックスが「jdbc」の場合にしかActive Recordアダプタに渡されず、その他はアダプタの仕様URLとみなされていた。
Jonathan Bracy
リリースノートChangelogより大意


抽象クラスが複数ある場合のコネクション切り替えの粒度を修正。
Eileen M. Uchitelle
リリースノートChangelogより大意


belongs_to関連付けでfind_byとカスタム主キーを組み合わせたときの問題を修正。
Ryuta Kamizono
リリースノートChangelogより大意


マルチプルデータベースにrails console --sandboxサポートを追加。
alpaca-tc
リリースノートChangelogより大意


whereでポリモーフィック関連付けに空のarrayが渡されたときのエラーを修正。
Ryuta Kamizono
リリースノートChangelogより大意


ApplicationRecordpreventing_writes?を修正。
Eileen M. Uchitelle
リリースノートChangelogより大意


⚓ Active Support

ActiveSupport::Cache::MemCacheStoreaddresses引数で明示的なnilを受け取れるようになった。

config.cache_store = :mem_cache_store, nil

# 上は以下と同等

config.cache_store = :mem_cache_store

# 以下も同等

config.cache_store = :mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211"

# これはDalli gemのフォールバックの振る舞いである

これによって明示的なnilが使えるようになったので、:dalli_storeからの移行で役立つ。
Michael Overmeyer
リリースノートChangelogより大意

petergoldstein/dalli - GitHub


TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

関連記事

速報: Rails 6.1.1がリリースされました

速報: Ruby on Rails 6.1がリリースされました

速報: Ruby 3.0.0がリリースされました

Rails 6.1: 孤立化したレコードのリストを取れる’missing’クエリメソッドが追加(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

以下の関連記事もどうぞ。

Rails 6.1: 孤立化したレコードのリストを取れるmissingクエリメソッドが追加(翻訳)

Rails 6.1に、ActiveRecord内の孤立化したレコード(orphan record)を検索できるmissingというクエリメソッドが追加されました( #34727)。

クリーンアップや一括アップデートなどのために、ActiveRecordモデルに孤立レコードがあるかどうかをチェックするクエリを書くことがよくあります。

以下の3つのモデルで考えてみましょう。

# app/models/manager.rb
# Manager: 担当管理職
class Manager < ApplicationRecord
  has_many :job_listings
end
# app/models/job_listing.rb
# JobListing: 求職リスト
class JobListing < ApplicationRecord
  has_many :job_applications
  belongs_to :manager
end
# app/models/job_application.rb
# JobApplication: 求職者
class JobApplication < ApplicationRecord
  belongs_to :job_listing
end

Rails 6.1より前

担当管理職(manager)を参照できない求職リスト(job listing)をすべて検索するクエリを書いてみましょう。

[1] pry(main)> JobListing.left_joins(:manager).where(managers: {id: nil})
JobListing Load (0.2ms)  SELECT "job_listings".* FROM "job_listings"
LEFT OUTER JOIN "managers" ON "managers"."id" = "job_listings"."manager_id"
WHERE "managers"."id" IS NULL LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Manager id: 3, name: "Jane Doe", created_at: "2020-01-20 14:31:16", updated_at: "2020-01-20 14:31:16">]>

上の例ではleft_joinsを使っていますが、includeseager_loadでも同じことができます。

Rails 6.1以降

Rails 6.1のActiveRecord::QueryMethods::WhereChainクラスにmissingクエリメソッドが追加されました( #34727)。

missingは、外部キーを参照できなくなっているレコードを検出するために、親子モデル間のLEFT OUTER JOINとWHERE句を含むリレーション(ActiveRecord::Relation)を返します。

上の例と同様に、担当管理職を参照できない求職リストをすべて検索してみましょう。

[1] pry(main)> JobListing.where.missing(:manager)
JobListing Load (0.1ms)  SELECT "job_listings".* FROM "job_listings"
LEFT OUTER JOIN "managers" ON "managers"."id" = "job_listings"."manager_id"
WHERE "managers"."id" IS NULL LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Manager id: 3, name: "Jane Doe", created_at: "2020-01-20 14:31:16", updated_at: "2020-01-20 14:31:16">]>
[2] pry(main)>

メソッドにリレーション名を複数渡せば、複数のリレーションを組み合わせることもできます。

たとえば、担当管理職も求職者(job application)も参照できない求職リストを検索してみましょう。

[1] pry(main)> JobListing.where.missing(:manager, :job_applications)
JobListing Load (0.1ms)  SELECT "job_listings".* FROM "job_listings"
LEFT OUTER JOIN "managers" ON "managers"."id" = "job_listings"."manager_id"
LEFT OUTER JOIN "job_applications" ON "job_applications"."job_listing_id" = "job_listings"."id"
WHERE "managers"."id" IS NULL AND "job_applications"."id" IS NULL LIMIT ?  [["LIMIT", 11]]
  => #<ActiveRecord::Relation []>
[2] pry(main)>

上のコード例は、担当管理職を参照できない求職リストがある場合でも、求職リストから求職者を参照できるので、空のリレーション(#<ActiveRecord::Relation []>)を1件返します。

このコードは、担当管理職も求職者も参照できない求職リストを識別します。

関連記事

Rails 6.1: whereの関連付けでjoinedテーブルのエイリアス名を参照可能になった(翻訳)

Rails 6.1: 関連付けのあるレコードを取れる’associated’クエリメソッドが追加(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

以下の関連記事もどうぞ。

Rails 6.1: 関連付けのあるレコードを取れるassociatedクエリメソッドが追加(翻訳)

Rails 6.1で、ActiveRecord::Relation#missingに似たassociatedというクエリメソッドが追加されました(#40696)。missingは孤立化したオブジェクトのリストを取れるActiveRecord::Relationを返すので、孤立化したオブジェクトが存在するかどうかをチェックできます。associatedは関連付けが存在するオブジェクトのリストを取れるActiveRecord::Relationを返すので、関連付けのあるオブジェクトが存在するかどうかをチェックできます。

以下の3つのモデルで考えてみましょう。

# app/models/manager.rb
# Manager: 担当管理職
class Manager < ApplicationRecord
  has_many :job_listings
end
# app/models/job_listing.rb
# JobListing: 求職リスト
class JobListing < ApplicationRecord
  has_many :job_applications
  belongs_to :manager
end
# app/models/job_application.rb
# JobApplication: 求職への応募
class JobApplication < ApplicationRecord
  belongs_to :job_listing
end

Rails 6.1より前

担当管理職を参照できる求職リストをすべて検索するときは、以下のように行っていました。

[1] pry(main)> JobListing.joins(:manager).where.not(managers: {id: nil})
JobListing Load (0.2ms)  SELECT "job_listings".* FROM "job_listings"
INNER JOIN "managers" ON "managers"."id" = "job_listings"."manager_id"
WHERE "managers"."id" IS NOT NULL LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Manager id: 3, name: "Jane Doe", created_at: "2020-01-20 14:31:16", updated_at: "2020-01-20 14:31:16">]>

Rails 6.1以後

Rails 6.1のActiveRecord::QueryMethods::WhereChainクラスにassociatedクエリメソッドが追加されました(#40696)。

上述の例を用いて、担当管理職を参照可能な求職リストをすべて検索してみましょう。

[1] pry(main)> JobListing.where.associated(:manager)
JobListing Load (0.1ms)  SELECT "job_listings".* FROM "job_listings"
INNER JOIN "managers" ON "managers"."id" = "job_listings"."manager_id"
WHERE "managers"."id" IS NOT NULL LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Manager id: 3, name: "Jane Doe", created_at: "2020-01-20 14:31:16", updated_at: "2020-01-20 14:31:16">]>
[2] pry(main)>

associatedは単なるシンタックスシュガーです。理由は、先の例と同様に存在チェックを行うINNER JOIN句とWHERE句を含むリレーション(ActiveRecord::Relation)を返すからです。

associatedにはリレーション名を複数渡すことも可能です。

たとえば、担当管理職と応募のどちらも参照可能な求職リストを検索するには以下のようにします。

[1] pry(main)> JobListing.where.associated(:manager, :job_applications)
JobListing Load (0.1ms)  SELECT "job_listings".* FROM "job_listings"
INNER JOIN "managers" ON "managers"."id" = "job_listings"."manager_id"
INNER JOIN "job_applications" ON "job_applications"."job_listing_id" = "job_listings"."id"
WHERE "managers"."id" IS NOT NULL AND "job_applications"."id" IS NOT NULL LIMIT ?  [["LIMIT", 11]]
  => #<ActiveRecord::Relation []>
[2] pry(main)>

このコード例では、担当管理職を参照可能な求職リストが存在する場合でも空のリレーション(#<ActiveRecord::Relation []>)が返されました。理由は、この求職リストから参照できる応募が存在しなかったからです。

関連記事

Rails 6.1: 孤立化したレコードのリストを取れる’missing’クエリメソッドが追加(翻訳)

Rails 6.1で form_withのデフォルトが「remoteなし」に戻った(翻訳)


週刊Railsウォッチ(20210222)ActiveRecord::Relationの新メソッドload_asyncとexcluding、Active Jobのperform_laterの改善ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsのなど最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

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

🔗 render 'partial'でのインスタンス変数代入が非推奨化された

Railsのパーシャルレンダリングでは、以下のようなインスタンス変数の代入が許されている。

render 'partial', :@name => "Maceo"

上はActionView::Base@nameを”Maceo”に設定する。
ユーザーが定義したインスタンス変数は代入が許されているのみならず、@_assigns@output_buffer@_config@_default_form_builderといったprivateなRails変数の上書きも可能になってしまっている。

この問題は当初Hackeroneに投稿され、@tenderloveがこの挙動をpublicで非推奨化することを決定した。
同PRより大意


つっつきボイス:「え、今までrender 'partial', :@name => "Maceo"みたいな書き方が可能だったの?」「自分もさっきrender 'partial'でインスタンス変数に:が付いているのを見てびっくりしました」「こんな書き方がシンタックスエラーにならなかったとは知らなかった…」「初めて見ました」「こういうふうに書くこと自体思い付かなかった」

🔗 Active Jobのperform_laterでジョブのenqueue失敗を処理できるようになった

perform_laterに、アダプタがジョブのenqueue試行後に実行されるブロックをオプションとして渡せるようになった。このブロックは、enqueueが成功しなかった場合であってもジョブのインスタンスを受け取る。
さらに、ActiveJobアダプタがActiveJob::EnqueueErrorエラーをraiseできるようになった。これはジョブインスタンス内でキャッチされて保存されるので、raiseされたEnqueueErrorをジョブのenqueueを試行するコードがブロックを用いてinspectできるようになる。

MyJob.perform_later do |job|
  unless job.successfully_enqueued?
    if job.enqueue_error&.message == "Redis was unavailable"
      # invoke some code that will retry the job after a delay
    end
  end
end

Daniel Morton
Changelogより大意


つっつきボイス:「perform_laterにブロックを渡してジョブのenqueueに失敗した時の処理を書けるようになったのか」「perform_laterブロックを渡す方法、Active Jobで発生するようになったEnqueueErrorを使う方法、successfully_enqueued
enqueue_errorプロパティを使う方法の3つが実装されたみたい」「テストコードを見ると3とおりともテストしてますね↓」

# activejob/test/cases/queuing_test.rb#42
  test "job is yielded to block after enqueue with successfully_enqueued property set" do
    HelloJob.perform_later "John" do |job|
      assert_equal "John says hello", JobBuffer.last_value
      assert_equal [ "John" ], job.arguments
      assert_equal true, job.successfully_enqueued?
      assert_nil job.enqueue_error
    end
  end

  test "when enqueuing raises an EnqueueError job is yielded to block with error set on job" do
    EnqueueErrorJob.perform_later do |job|
      assert_equal false, job.successfully_enqueued?
      assert_equal ActiveJob::EnqueueError, job.enqueue_error.class
    end
  end
# activejob/test/jobs/enqueue_error_job.rb#3
class EnqueueErrorJob < ActiveJob::Base
  class EnqueueErrorAdapter
    class << self
      def enqueue(*)
        raise ActiveJob::EnqueueError, "There was an error enqueuing the job"
      end

      def enqueue_at(*)
        raise ActiveJob::EnqueueError, "There was an error enqueuing the job"
      end
    end
  end

  self.queue_adapter = EnqueueErrorAdapter

  def perform
    raise "This should never be called"
  end
end

「Changelogのコード例にある”Redis was unavailable”みたいに、enqueueに失敗する可能性は常にあるので、EnqueueErrorは必要ですね👍」「このやり方覚えとこうっと」

「この機能が追加されたということは、これまでperform_laterでジョブのenqueueが失敗したときの公式な処理手段がなかったということなんでしょう」「今まではどうやって処理してたんでしょうね?」「変更前のperform_laterではenqueueを返してはいたけど↓、successfully_enqueuedenqueue_errorなどに相当するものがなかったので、enqueueが成功したかどうかを知る方法がなかったということなのかな?」「この感じだと公式な処理方法はこれまでなかったみたいですね」「従来はsuccessfully_enqueuedenqueue_errorなどに相当するものを自分で書いて処理するしかなかったんだろうと想像できます」

# activejob/lib/active_job/enqueuing.rb#L
      def perform_later(*args)
-       job_or_instantiate(*args).enqueue
+       job = job_or_instantiate(*args)
+       enqueue_result = job.enqueue
+
+       yield job if block_given?
+
+       enqueue_result
      end

🔗 ActiveRecord::Relationの新メソッド2つ: load_asyncexcluding

このメソッドは、スレッドプールから非同期実行されるようにクエリをスケジューリングする。
バックグラウンドスレッドがクエリを実行可能になる前にこの結果にアクセスすると、フォアグラウンドで実行されるようになる。
これは、結果が必要になるより前の実行時間が長いクエリや、独立したクエリを複数実行する必要のあるコントローラで有用。

def index
  @categories = Category.some_complex_scope.load_async
  @posts = Post.some_complex_scope.load_async
end

Jean Boussier
#41372 Changelogより大意


つっつきボイス:「1つ目はActiveRecord::Relation#load_asyncメソッドですか」「コードを見るとRubyのThreadで実装されていますね↓:将来もしかするとThreadの代わりにRubyのRactorを使うようになるのかもしれないとちょっと想像してみました」

# activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L460
      def schedule_query(future_result) # :nodoc:
        @async_executor.post { future_result.execute_or_skip }
+       Thread.pass
      end

参考: class Thread (Ruby 3.0.0 リファレンスマニュアル)
参考: ractor - Documentation for Ruby 3.0.0

「コントローラでlazy loadingする方法だとビューなどで実際に参照されるまで読み込みが開始されないんですが、このload_asyncならビューなどで参照される前に読み込みが開始されるので、期待どおりに動けばブラウザにレスポンスを早く返せるようにできるでしょうね」「お〜」

「ビューをレンダリングしている間にクエリを投げられるという感じでしょうか?」「ビューに限らず使えるでしょうね: たとえば以下のコードのCategory.some_complex_scopeが仮にやや遠くにあるサーバーへのクエリだとすると、ここでeager loadingもせずに普通にfindを使ってクエリを発行すると、結果が戻ってくるまでここでブロックされてしまうという問題がまず考えられます」「ふむふむ」

# Changelogより
def index
  @categories = Category.some_complex_scope.load_async
  @posts = Post.some_complex_scope.load_async
end

「同じ箇所でeager loadingする場合は、必要になった瞬間から読み込みが開始されますが、その場合もコードがブロックされるのは同じです」「たしかに」

「このload_asyncを使えば、そのクエリを実行するためだけのスレッドが新たに生成されるので、純粋な待ち合わせの時間は短縮されるでしょうね」「なるほど、コードがブロックされなくなるということですか」「重たくなりそうなクエリをバックグラウンドのスレッドで扱うというのはなかなかうまい方法だと思います👍」「言われてみると速くなりそうな気がしてきました」「load_asyncだとスレッド生成のコストも少しはかかると思いますが大きくはなさそう」「マルチプルデータベースにもいいかも」

「その代わり、別スレッドでエラーが発生した場合にどうやって拾うかは考えないといけないでしょうね」「あ、その場合どうすればいいんでしょうか?」「エラーそのものは別スレッド側で拾うしかないと思います」

load_asyncしたクエリはトランザクションが効かなくなりそうなので、使い方を間違えるとデッドロックするかも」「それもそうか」「たぶんデッドロックしそうな使い方はしないように、ということなんでしょうね」

参考: デッドロック - Wikipedia

「プルリクを見た感じでは、エラーの場合どうするかについては特に書かれてないのかな?」「お、トランザクションの中でload_asyncした場合のテストが書かれてますよ↓」「あ、ホントだ」

# activerecord/test/cases/relation/load_async_test.rb#55
    def test_load_async_from_transaction
      posts = nil
      Post.transaction do
        Post.where(author_id: 1).update_all(title: "In Transaction")
        posts = Post.where(author_id: 1).load_async
        assert_predicate posts, :scheduled?
        assert_predicate posts, :loaded?
        raise ActiveRecord::Rollback
      end

      assert_not_nil posts
      assert_equal ["In Transaction"], posts.map(&:title).uniq
    end

「上のテストコードを見ると、トランザクションの中で実行したPost.where(author_id: 1).load_asyncは、トランザクションが終わるまで待ってから動き出すようですね」「なるほど、このload_asyncではupdate_allが完了した後の結果が取れるのか」「完了まで待つのはRDBMS自体の機能です: 別スレッドがロックを掴んでいたら、タイムアウトするまでは待つ機能がRDBMSにあります」「なるほど」「いずれにしろトランザクションブロックの中ではload_asyncするかどうかで結果が変わる可能性があるので注意が必要でしょうね」

「たしかこのload_async的な機能は、これまで長らく待ち望まれていたけど実現していなかった機能だったと思います: 複数クエリを呼び出す処理をマルチスレッドで並列実行できるという意味で、その夢を実現する偉大なる一歩なんだろうなと思いました」「たしかに」


# 41439より
Post.excluding(post)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1

Post.excluding(post_one, post_two)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)

# And on associations (also supports collections as above).
post.comments.excluding(comment)
# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2

「2つめのexcludingはプルリクに書かれているとおりPost.where.not(id: post.id)のショートハンドですね↓」「NOT INに相当するのか」「where.notを書かなくていいのがよさそう」「where.notをたくさん書くときにはいいでしょうね」

This is short-hand for Post.where.not(id: post.id) (for a single record) and Post.where.not(id: [post_one.id, post_two.id]) (for a collection).

「その後で@kamipoさんがエイリアスも追加していました↓」「なるほど、excludingwithoutでも書けるんですね」

🔗 データベースアダプタの平均値集計を修正

:averageで呼ばれるActiveRecord::Calculations.calculateActiveRecord::Calculations.averageのエイリアス)が、カラムベースのtype castingを使うようになった。これによって、浮動小数点値(float)のカラムがFloatとして集計され、固定小数点値(decimal)のカラムがBigDecimalとして集計されるようになった。
整数は特殊ケースとして、常にBigDecimalとして扱われる(これは既にそうなっている)。
この変更前は、Railsのデータベースアダプタで平均値集計のときにto_dを呼んでいた。これは今後行われなくなる。この種のマジックに依存していた場合は、独自のActiveRecord::Typeを登録する必要がある(ActiveRecord::Attributes::ClassMethodsのドキュメントを参照)。
Changelogより抜粋


つっつきボイス:「decimal型のカラムをaverageメソッドで集計した際、スキーマ側で定義された型を無視してfloatに変換されて返ってくる問題を修正したということみたい」「テストを見ると、integerは常にBigDecimalで返すべきとなっている↓」「floatはFloatで、decimalはBigDecimalで返すべきということか」

# activerecord/test/cases/calculations_test.rb#50
  def test_should_return_decimal_average_of_integer_field
    value = Account.average(:id)
+
    assert_equal 3.5, value
+   assert_instance_of BigDecimal, value
  end
# activerecord/test/cases/calculations_test.rb#L64
  def test_should_return_float_average_if_db_returns_such
    NumericData.create!(temperature: 37.5)
-
    value = NumericData.average(:temperature)
-   assert_instance_of Float, value
+
    assert_equal 37.5, value
+   assert_instance_of Float, value
+ end
+
+ def test_should_return_decimal_average_if_db_returns_such
+   NumericData.create!(bank_balance: 37.50)
+   value = NumericData.average(:bank_balance)
+
+   assert_equal 37.50, value
+   assert_instance_of BigDecimal, value
  end

「あれ、37.5と37.50って型が違うんですか?」「お?テスト用の定義はスキーマのどこかにあるかな(しばらく探す): あった↓」「temperatureがfloatでbank_balanceがdecimalなのか、なるほど理解しました」「このスキーマ定義の型に応じてtype castingできているかどうかのテストということですね」

# rails/activerecord/test/schema/schema.rb#L651
  create_table :numeric_data, force: true do |t|
    t.decimal :bank_balance, precision: 10, scale: 2
    t.decimal :big_bank_balance, precision: 15, scale: 2
    t.decimal :unscaled_bank_balance, precision: 10
    t.decimal :world_population, precision: 20, scale: 0
    t.decimal :my_house_population, precision: 2, scale: 0
    t.decimal :decimal_number
    t.decimal :decimal_number_with_default, precision: 3, scale: 2, default: 2.78
    t.numeric :numeric_number
    t.float   :temperature
    t.decimal :decimal_number_big_precision, precision: 20
    # Oracle/SQLServer supports precision up to 38
    if current_adapter?(:OracleAdapter, :SQLServerAdapter)
      t.decimal :atoms_in_universe, precision: 38, scale: 0
    else
      t.decimal :atoms_in_universe, precision: 55, scale: 0
    end
  end

「以下がその前にマージされたプルリクですね↓」

🔗Rails

🔗 Railsにマイクロサービスを追加する(Ruby Weeklyより)


つっつきボイス:「Railsにマイクロサービスですか?」「Railsをマイクロサービス化するんじゃなくて?」

# 同記事より
class QuizController < ApplicationController
  # Other controller methods omitted here
  # This is our new service method
  def participants
    response = {}
    response["participants"] = Attempt.all.map { |attempt| attempt.taker }
    render :json => JSON[response]
  end
end

「え、QuizController.action(:participants)でコントローラのアクションを直接呼び出してるのか↓、しかもpumaの引数で.ruファイルを指定してる」「すごい書き方ですね😳

# 同記事より
# This file is used by Rack-based servers to start the application.
require_relative 'config/environment'
run QuizController.action(:participants)
# 同記事より
bundle exec puma -b tcp://0.0.0.0:8080 config_svc_participant.ru

「これってマイクロサービスなんでしょうか?」「自分たちが考えているマイクロサービスとは設計思想が違いますけど、単機能のサービスという意味での『マイクロなサービス』と考えることも一応できるでしょうね」「なるほど、そういう意味ですか」「unbundled seriesと銘打たれている記事だから、いろいろ自由に試しているのかなと想像してみました」


「ところでこの記事、よくみるとEngine Yardのブログなんですね」「Engine YardのWebサイトってもっと赤っぽい色彩だった覚えがあるんですけど、随分青みがかってる」「Engine YardはHerokuと同じぐらい歴史がありますけど、今はRails以外もいろいろサポートするようになっているので、シンボルカラーをRailsを連想する赤から変えたのかもしれませんね」「あ、そうかも」「そういえば、昔の赤色の時代のEngine Yardのステッカーがそこに転がっているパソコンに貼ってありますよ」

🔗 Railsのcycleメソッド(Ruby Weeklyより)


つっつきボイス:「i % 2 == 0を避ける、とは?」

<!-- 同記事より -->
<% @foods.each_with_index do |food, i| %>
  <tr class="<%= i % 2 == 0 ? 'bg-gray-200' : 'bg-gray-100' %>">
    <td><%= food %></td>
  </tr>
<% end %>

「なるほど、以下のようにeach_with_indexの中でcycleビューヘルパーを使うと、上みたいに1行ずつ交互に背景色を変えたりするときにi % 2 == 0 ?で偶数奇数を判定したりせずに書けるのね↓」「へ〜!」「Railsにこんなヘルパーもあるとは」

<!-- 同記事より -->
<% @foods.each_with_index do |food, i| %>
  <tr class="<%= cycle('bg-gray-200', 'bg-gray-100') %>">
    <td><%= food %></td>
  </tr>
<% end %>

odd?even?で書くよりもcycleの方が意味的にも自然かも」「背景色に限らず、ループの中で何かを順繰りに表示したいときに幅広く使えそうですね👍

🔗Ruby

🔗 reductionとは何か、FiberがRubyコンカレンシーの解である理由(Ruby Weeklyより)


つっつきボイス:「お、まだこの記事の図しか見てませんが、こんなふうに↓スレッドを丁寧に図で説明しているのがすごくよさそう」「おぉ!」


同記事より

「Fiberだけじゃなくて最後の方でRactorまで説明してくれている」「こうやって時系列に沿った図になっているのがうれしいですね↓」「マルチスレッド系の解説って、こういう図がないと読んでてつらいんですよ」


同記事より

「歯ごたえありそうだけど、この記事は読む価値がありそうな予感がします👍」「週末に頑張って読んでみようかな」

🔗 M1 MacでRailsアプリを開発するときのコツ(Ruby Weeklyより)


つっつきボイス:「Rails discussionがRuby Weeklyで紹介されていました」「Appleシリコンだ」「Dockerもアップデートされて、ようやくいくつかの機能がM1チップでまともに動くようになったという記事も今日見かけましたね」

参考: Apple M1チップ対応のDocker Desktop、同梱のKubernetesも実行可能に - Publickey

「次に買うMacBookはM1チップにしようかな」「たぶん今後はM1チップのしか買えなくなると思いますよ」「え、そうなんですか?」「AppleがIntelへのCPUの新規発注を絞り込んでいるらしいという話もあるので、おそらく現行のIntel Macはいったんディスコンになる流れなんじゃないかなと予想しています」「あ〜なるほど」「最近Intelがこういう広告↓を打っているのが話題になってますけど、そのあたりも理由じゃないかと言われていますね」

参考: インテル、「MacにできないことがPCにできる」キャンペーンを展開 - Engadget 日本版


「ところで、最近一部で話題になったcpu-monkey.comのベンチマーク↓は、スペック情報は参考になる部分もあるんですけど、シングルコアでM1とM1Xの差がほとんどないとか、自分も含めてこのベンチマークの信憑性にはちょっと疑問があるんですよ」「あ、そうなんですか」

参考: Apple M1X vs. Apple M1 - Benchmark and Specs

「ここに載ってたスペックを見ると、M1Xにはメモリが32GB搭載できるらしいので次に買ってみてもいいかなと思うんですが、M1XのTDP(Thermal Design Power)が35Wもあるのでバッテリーの持ちという面では優位性がちょっと下がるかもと思っているところです」「それでも32GB積めたらいいですよね」「今の自分の使い方だと16GBではもうやっていけないレベルです」「今のMacBookは最大16GBですもんね」(以下延々)

参考: TDP - CPU の選び方


今回は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210209後編)Rubyでミニ言語処理系を作る、Kernel#getsの意外な機能、CSSのcontent-visibilityほか

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

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

Rails公式ニュース

Ruby Weekly

日本語エラーチェックサイトenno.jpを作った理由

$
0
0

こんにちは、hachi8833です。はっと気がつくと、私が運営している日本語エラーチェックサイト「enno.jp」を開始してからもう8年が経過していましたので、たまには振り返ってみたいと思います。


enno.jpより

使い方はあきれるほど単純です。チェックしたい文章をフィールドに貼り付けてボタンを押すだけ。

enno.jpは、自分で作って自分で使い、自分で常にパターンを追加/更新しています。いわゆるドッグフーディングというヤツです。当然この記事もenno.jpでチェック・修正しました。

参考: ドッグフーディングとは:意味/解説 - シマウマ用語集

今年1月に突然enno.jpがバズッて自分でもびっくりしました。広めていただきありがとうございます🙇

enno.jpをAIまたは機械学習ベースと一部で思われているようですが、実際ははるかに単純で、正規表現で記述したエラーパターンを大量に登録し、常にメンテを繰り返しているだけです。

アナリティクスを見てみた

以下は、今年2/16時点のGoogleアナリティクスです。1つ目は8年間のページビュー数の移り変わりです。

2つ目は8年間の目標完了数の移り変わりです。

上のツイートのおかげでバズッた今年1月だけスパイクがありますが、基本的に非常にじわじわと増え続けていることがわかります。

作った理由

enno.jpは実を言うと、2013年のBPSでの面接で出されたトライアル課題用に作ったRailsアプリです。

その年の5月頃、今ではめちゃくちゃ有名になった「Ruby on Rails チュートリアル」の最初の英日翻訳(自分が翻訳したのは量的には半分程度)と翻訳ディレクションを手掛け、その勢いでこのenno.jpを作りました。

手元でGitの履歴を参照してみると開始当時はRails 4.0.0、Ruby 2.0.0-p247でしたが、現在はRails 5.2.4、Ruby 2.7.2です。作り始めた日が2013/08/12、ひとまずリリースしたときが2013/09/12だったので、作成開始後ちょうど1か月でリリースしたことになります。

enno.jpは、Webアプリとしては極めて素朴な部類に入りますので、コード上見るべき点はほぼありません。強いて言うなら正規表現によるエラー検出結果をビューに反映する部分ですが、そこは公開するつもりはありません。

ただ、当時enno.jpを作った動機だけははっきりしています。「自分の欲しいエラーチェックツールがどこにもなかった」からです。

参考: へうげもの - Wikipedia–「己の欲しいものは己で作るしかない」というセリフが印象的でした

一般的な文章チェックツール

世の中には、昔からこの種の文章チェックツールがいろいろあります。近年だとtextlintが有名かと思います。

textlint/textlint - GitHub

さらに、textlint-jaにはJTF(日本翻訳連盟)公式のルールセットなど、さまざまなルールが掲載されています。

textlint-ja/textlint-rule-preset-JTF-style - GitHub


その他には、青山学院大学の「Tomarigi」も有名かと思います。私も使っていたことがありました。

最近だと、vscode用の「テキスト校正くん」もありますね。

参考: テキスト校正くん - Visual Studio Marketplace

既存ツールを使わなかった理由

端的に言うと、textlintなど既存ツールのインターフェイスは自分の欲しいものではなかったからです。

enno.jpの構想

自分がずっと欲しかった文章エラーチェックツールの仕様は、以下のようなものです。

  • 文章入力フィールドと、チェック結果の表示が横にきれいに並んでいること
  • 一度送信した文章が、結果表示と同時に入力フィールドにも自動で再入力されること
  • 長い文章を入力しても、入力フィールドとチェック結果の表示がずれないこと
  • 誤りのある箇所をハイライトしてくれること
  • ハイライト部分にマウスオーバーするとツールチップで説明が表示されること
  • さらにその説明の中に参考URLがあり、クリック可能であること
  • 誤りのある箇所をハイライトするだけでなく、誤りのリストも出力してくれること
  • 文章の種類を限定せず、どんな日本語文でもチェックできること

それを実現したかったからこそ、enno.jpを作りました。enno.jpの中身はこまごまと変わっていますが、それらのユーザーインターフェイスは以下のように最初から実現されています。


enno.jpより

セキュリティとプライバシー配慮について

流し込んだテキストは保存していない

enno.jpではマッチしたエラーのカウントを取っているだけで、流し込んだテキストは保存せずにすべて捨てています。

Railsのログ出力で以下のようにフィルタをかけているので、流し込んだテキストはログでもマスキングされます。

# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [:password]
Rails.application.config.filter_parameters += [:typotext]

後にRailsのログ出力レベルを:errorに変えたので、通常の利用で流し込まれるテキストのログはそもそも出力されなくなりました。

# config/environments/production.rb
  config.log_level = :error

参考: 2.2 ログの出力レベル Rails アプリケーションのデバッグ - Railsガイド

ユーザーが流し込んだテキストを保存しないのは、セキュリティとプライバシー配慮のためです。
データベースに保存しているのは、エラーパターンごとのヒットカウントだけです。今見てみると、多いもので3979844件というパターンもありました。

送信前にダイアログボックスを表示する

enno.jpでは、上のフィルタ処理に加えて、チェックするテキストをブラウザから送信する前に以下のダイアログを表示し、ユーザーに注意を喚起しています。


enno.jpより


次回は「enno.jpであえて搭載しなかった機能」について書きたいと思います。

関連記事

はじめての正規表現とベストプラクティス1: 基本となる8つの正規表現

週刊Railsウォッチ(20210301前編)Rails 6.1.3がリリース、Active Supportのbefore?とafter?、link_to_unless_currentほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

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

🔗 ActionController::Live::Buffer#writelnが追加

DHH自らによるプルリクです。

# 同PRより
send_stream(filename: "subscribers.csv") do |stream|
  stream.writeln "email_address,updated_at"

  @subscribers.find_each do |subscriber|
    stream.writeln [ subscriber.email_address, subscriber.updated_at ].join(",")
  end
end

つっつきボイス:「末尾で改行されるwritelnができたんですね」「名前がどことなくJavaなどのprintln風味かも」「Javaやったことないです…」「JavaやってたのもJava 5の頃なのであまり思い出せませんが」「私も同じぐらいJava忘れてますね😆

参考: Javaで文字列を出力する:print(), println() | UX MILK

writelnend_with?を使って末尾の改行が重複しないように処理してくれている↓」「これは賢い👍

# actionpack/lib/action_controller/metal/live.rb#166
      # Same as +write+ but automatically include a newline at the end of the string.
      def writeln(string)
        write string.end_with?("\n") ? string : "#{string}\n"
      end

🔗 to_strが使えるオブジェクトもredirect_toに渡せるようになった

Addressable::URIのように)#to_strが使えるものなら何でもredirect_toのlocationとして渡せるようにした。
ojab
Changelogより大意


つっつきボイス:「Addressable::URIって何だろうと思ったら、どうやら外部のgemらしい↓」

sporkmonger/addressable - GitHub

to_strって普段使わないけど、これってto_sとどう違うんだっけ?」「to_strto_sの違いをググったらTechRacho記事が出てきた↓」「自分も同じ記事を見てます😆」「to_sto_iみたいな短い変換メソッドは明示的な変換、to_strto_intみたいな名前の長い変換メソッドは暗黙的な変換、だそうです」「う〜んまだ違いがピンとこない」

Rubyの明示的/暗黙的な型変換についてのメモ(翻訳)

「記事を見ると、文字列を式展開しないで直接"string" + otherと結合すると、otherto_strが呼ばれるのか」「へ〜、otherで呼ばれるのはto_sじゃないんですね!」「式展開ならto_sが呼ばれます↓」

Rubyでの文字列連結に「#+」ではなく式展開「#{}」を使うべき理由

「Rubyのドキュメントも見ると、to_strは『文字列が使われるすべての場面で代置可能』『それ自体が文字列とみなせるもの』のときにだけ定義する、と書かれてる」「to_strが定義されているということは、文字列とみなしてよいものということなのかな」

参考: Object#to_str (Ruby 3.0.0 リファレンスマニュアル)

オブジェクトの String への暗黙の変換が必要なときに内部で呼ばれます。デフォルトでは定義されていません。
説明のためここに記載してありますが、このメソッドは実際には Object クラスには定義されていません。必要に応じてサブクラスで定義すべきものです。
このメソッドを定義する条件は、

  • 文字列が使われるすべての場面で代置可能であるような、
  • 文字列そのものとみなせるようなもの

という厳しいものになっています。
docs.ruby-lang.orgより

to_strは定義されているとは限らないけど、to_s↓はObjectに実装されているからすべてのオブジェクトに対して使えますね」

参考: Object#to_s (Ruby 3.0.0 リファレンスマニュアル)

「元コードは、when節のRegexp#===比較が暗黙型変換(to_str)で評価されるのにwhen節内でoptionsをそのまま返してしまっていたため、_compute_redirect_to_locationの戻り値がStringにならないケースがあったのを、#41390では明示的にoptions.to_strすることで戻り値がStringであることを保証するようにしたんですね↓」

# actionpack/lib/action_controller/metal/redirecting.rb#101
    def _compute_redirect_to_location(request, options) #:nodoc:
      case options
...
      when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
-       options
+       options.to_str
      when String
        request.protocol + request.host_with_port + options
      when Proc
        _compute_redirect_to_location request, instance_eval(&options)
      else
        url_for(options)
      end.delete("\0\r\n")
    end

🔗 fixtureのhas_many :throughでタイムスタンプを設定するようになった

has_many :through関連付けのfixtureがjoin tableでタイムスタンプを設定するようになった。
以下のfixtureがあるとする。

### monkeys.yml
george:
  name: George the Monkey
  fruits: apple
### fruits.yml
apple:
  name: apple

このjoin table (fruit_monkeys)がcreated_atupdated_atを含む場合、fixtureの読み込み時に展開されるようになった。従来はこれらのカラムをrequireするとクラッシュし、しない場合はnullのままになった。
Alex Ghiculescu
同Changelogより大意


つっつきボイス:「fixtureで中間テーブルにタイムスタンプ定義がある場合にcreated_atupdated_atが自動設定されずにnullになっていたのが、このプルリクで修正されたようです」「お〜」「修正されたのはactive_record_fixture_set/のfixtureですね↓」「中間テーブルにタイムスタンプを付けることはありうるので、修正されてよかった」

# activerecord/lib/active_record/fixture_set/table_row.rb#L37
+       def timestamp_column_names
+         ModelMetadata.new(@association.through_reflection.klass).timestamp_column_names
+       end
      end
...

        def add_join_records(association)
          # This is the case when the join table has no fixtures file
          if (targets = @row.delete(association.name.to_s))
            table_name  = association.join_table
            column_type = association.primary_key_type
            lhs_key     = association.lhs_key
            rhs_key     = association.rhs_key

            targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
            joins   = targets.map do |target|
              { lhs_key => @row[model_metadata.primary_key_name],
                rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
-             join = { lhs_key => @row[model_metadata.primary_key_name],
-                      rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
+             association.timestamp_column_names.each do |col|
+               join[col] = @now
+             end
+             join
+           end
+           @table_rows.tables[table_name].concat(joins)
          end

Rails API: `ActiveRecord::FixtureSet`(翻訳)

🔗 ActiveSupport::CurrentAttributesのキーワード引数を修正

最近のRubyで(キーワード引数周りが)変更された後にキーワード引数を引き続き使いたい人向けのシンプルな改良。
respond_to?変更は、RSpecでパーシャルのダブル(double)をチェックするときのCurrentAttributesのスタブ化の問題を解決するのが目的。
キーワード引数が動かないという前提を置く理由がないので、ドキュメントは変更していない。
同PRより大意


つっつきボイス:「CurrentAttributesmethod_missingでキーワード引数を中継できるように**kwargsが追加されてますね↓」

# activesupport/lib/active_support/current_attributes.rb#L158
-       def method_missing(name, *args, &block)
+       def method_missing(name, *args, **kwargs, &block)
          # Caches the method definition as a singleton method of the receiver.
          #
          # By letting #delegate handle it, we avoid an enclosure that'll capture args.
          singleton_class.delegate name, to: :instance

-         send(name, *args, &block)
+         send(name, *args, **kwargs, &block)
+       end
+
+       def respond_to_missing?(name, _)
+         super || instance.respond_to?(name)
+       end

「今は、受け取った引数をすべて受け取ってdelegateできるようにするには、上のようにmethod_missing(name, *args, **kwargs, &block)と書くのか」「へ〜、今はこうなんですね」「これはnameが必ず存在する前提ですが、そういうのがない場合は*args, **kwargs, &blockと書けばすべての引数を受け取れるんでしょうね」

🔗 ドキュメント更新: redirect_toの危険な利用法について


つっつきボイス:「APIドキュメントとガイドの更新です↓」「redirect_toにユーザー入力をそのまま渡すのは一般に危険、たしかに」「これはもうおっしゃるとおりとしか言いようがない」「そんなことをする人がいるんだろうかと思いますが、たぶんいたから明示的にドキュメントとガイドにも書くことにしたんでしょうね」

# guides/source/security.md#L345
-If it is at the end of the URL it will hardly be noticed and redirects the user to the attacker.com host. A simple countermeasure would be to _include only the expected parameters in a legacy action_ (again a permitted list approach, as opposed to removing unexpected parameters). _And if you redirect to a URL, check it with a permitted list or a regular expression_.
+If it is at the end of the URL it will hardly be noticed and redirects the user to the `attacker.com` host. As a general rule, passing user input directly into `redirect_to` is considered dangerous. A simple countermeasure would be to _include only the expected parameters in a legacy action_ (again a permitted list approach, as opposed to removing unexpected parameters). _And if you redirect to a URL, check it with a permitted list or a regular expression_.

🔗Rails

🔗 @kamipoさん記事より


つっつきボイス:「MySQL 8.0のクライアントでMySQL 5.7のサーバーに接続、これやっちゃってました😅」「記事を見た感じではMySQLの接続のハンドシェイクでの問題なので、接続した後でSET NAMESコマンドを渡して設定する分には大丈夫そうですね: ちょうど記事にも書いてあった↓」「お〜」

一応、接続後にSET NAMES utf8mb4すればサーバー側のutf8mb4のdefault collationが設定されるが、最悪のケースをカバーするために適切に設定してるひとには必要ない処理が増えて損をすることになるのでなんとか回避したい気持ちがあるけど、現状はそういう感じ。
同記事より

「元々この41403↓で上がってたissueを解決しようとしていたんですね」

「そうそう、記事にもあるように、MySQL 8.0.1からデフォルトのコレーションがutf8mb4_general_ciからutf8mb4_0900_ai_ciに変わっているんですよ↓: 後者はMySQL 5.7の頃にはなかったから認識されなくて、MySQL 5.7サーバーのデフォルトのコレーションにフォールバックしてたのか」「え、そこ変わっちゃってたんですか!」「MySQL 5.7サーバーには新しいutf8mb4_0900_ai_ciid:255がないから、MySQL 8.0クライアントからこれを渡しても認識しようがありませんね」「このことを知らなかったら原因を見つけるのは難しそう…」

ここで表題の “MySQL 8.0のクライアントでMySQL 5.7のサーバーに接続するとcharsetが設定されないかもしれない” についてなんですが、MySQL 8.0.1からutf8mb4のdefault collationがutf8mb4_general_ci (id: 45)からutf8mb4_0900_ai_ci (id: 255)に変更されたため、MySQL 8.0のクライアントがuff8mb4でサーバーに接続するとid: 255のcs_numberを送るけどMySQL 5.7はid: 255のcs_numberを知らないのでサーバー側のデフォルトの設定が採用されるという仕組み。
同記事より

🔗 AS句で作ったカラムにDBの型情報はない


つっつきボイス:「永和システムマネジメントさんのブログです」「『AS句で作ったカラムにDBの型情報はない』、そういうカラムはスキーマに定義がありませんのでそのとおりですね」

「ただ、これはActive Recordがどこまでよしなにマジックを効かせてくれるかという流れを何となくでも把握していないと、すぐには見当がつかないと思います」「あ、そういうことですか」「Active Recordが内部的にSHOW FIELDS(MySQLの場合)を使ってスキーマの型情報を取得していることを理解していれば、AS句で取ったカラムに型情報がないことは推測できると思います」「ふむふむ」「SHOW FIELDSはテーブルに対して実行するものなんですが、テーブルがなければSHOW FIELDSを実行できないので、AS句でSELECTした結果に対してはSHOW FIELDSできません」「なるほど」

「もしやりたければ、VIEW(データベースビュー)↓を作ればやれますよ: VIEWならSHOW FIELDSが使えるので」「あ〜、なるほど」

RDBMSのVIEWを使ってRailsのデータアクセスをいい感じにする【銀座Rails#10】

参考: ビュー (データベース) - Wikipedia

「記事にも、スキーマにないカラムの型はデフォルトでActiveModel::Type::Valueになると書かれていますね↓」

その過程で、AS 句で作ったカラムに型情報がつかない理由もわかります。 スキーマにないカラムはデフォルトで ActiveModel::Type::Value 型になるから、です。
同記事より

「今つっつきの場にいませんが、たしかBPS Webチームのkazz氏が、AS句で組み立てたクエリに何らかの方法で型ヒントを渡してカラムの型を認識させるというのをやっていた覚えがありますので、何か方法はあると思います」「お〜、やれそうなんですね」「でなければVIEWを使うか、さもなければAS句を使わないことでしょうね」

「Active Recordがあまりにいろんなことをよしなにやってくれるので、その内部構造に興味を持ってない人はこういうところでハマるでしょうね」「ですね、自分はハマる自信あります😅

🔗 Dateの比較で<>を混同しないようにする


つっつきボイス:「Boring Railsっていう名前が面白い」「Date同士を比較するときには<とか>よりもActive Supportのbefore?after?の方が便利だよという話のようですね」「この辺は自分もよく不安になるので動かして確かめてます」

# 同記事
start_date = Date.new(2019, 3, 31)
end_date = Date.new(2019, 4, 1)

start_date.before? end_date
#=> true
end_date.after? start_date
#=> true

start_date = Date.new(2020, 8, 11)
end_date = Date.new(2018, 8, 11)

start_date.before? end_date
#=> false

「英語圏的にはbefore?after?の方がわかりやすいのかな?」「記事のコード例だと、start_dateが2019, 3, 31で、end_dateが2019, 4, 1だと、start_date.before? end_dateはtrueになる」「えっ、falseになるのかなという気がしましたけど?」「パッと見に逆かと思っちゃいますよね: 先行するstart_dateが主語で、それに対してend_datebefore?と読まないといけないんでしょうね」「あ、それでちょっとわかってきたかも」「いつものようなオブジェクト指向的な語順で読まずに、普通の関数のような語順で読むとよさそう」「before?after?はRails 6で追加されたんですね↓」

参考: Rails 6 adds before? and after? to Date and Time | BigBinary Blog


「そういえば、このboringrails.comの他の記事にも反応を見つけました↓」「link_to_unless_currentって見たことなかった」

「なるほど、この図↓のようにWebページのメニューなどで現在のページだけリンクを生成しないで、それ以外のリンクを有効にできるメソッドなのか」「あ、それちょっと便利かも😋」「こんなメソッドあるの知らなかった〜」


同記事より

参考: link_to_unless_current — ActionView::Helpers::UrlHelper

🔗 Rails 6.1.3がリリース


つっつきボイス:「お、Rails 6.1.3が出たんですね」「リリース情報見落としててRuby Weeklyの見出しで知りました🙇」「今回の更新は少なそう」


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210222)ActiveRecord::Relationの新メソッドload_asyncとexcluding、Active Jobのperform_laterの改善ほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20210303後編)Bundlerのセキュリティ修正、Rubyのガベージコレクション記事、Rubyが2/24に誕生日ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

訂正(2021-03-04)

本記事タイトルの「Ruby24歳に」および本文に誤りがある旨ご指摘をいただき、本記事タイトルおよび本文を訂正いたしました。
関係者の皆様にご迷惑をおかけしたことをお詫び申し上げます(hachi8833)🙇。

🔗Ruby

🔗 Bundlerのセキュリティ修正(Ruby Weeklyより)


つっつきボイス:「Bundlerサイトのブログです」「Bundlerをよりセキュアに、ですか」

「なるほど、複数のgemサーバーを使っている状況でgemの名前がコンフリクトする場合に、以下のようにどのサーバーから取ってくるかを明示的に指定できるようになったんですね」「おぉ?」「たとえばmy-private-gemがrubygems.orgにもmy-private-serverにも置かれている場合、従来だとどちらからフェッチするかを指定できなかったのが、以下のようにsourceブロックで明示的に囲んで取得元を指定できるようになった」「あ、そういうことですか」

# Gemfile

source "https://rubygems.org"

source "https://my-private-server" do
  gem "my-private-gem"
end

「記事で引用されている”dependency confusion”↓という問題が従来のBundlerにもあったんですね」

参考: Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies | by Alex Birsan | Feb, 2021 | Medium

「上のdependency confusion記事は他の言語の話ですが、この図↓のnpm dependenciesの記述みたいに、自分が直接取得元を指定していない間接的なdependency confusionについては、たしかに従来のBundlerのgem依存関係でも起きる可能性がありますね: Bundlerがこういう問題に対処可能になったのはいいことだと思います👍」


Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companiesより


「お、同じ記事のこの図が、この問題を使った攻撃の手口を端的に表してますね: 複数リポジトリを使う場合、同じ名前でバージョン番号が大きいライブラリをリポジトリに置かれてしまうとそちらを参照してしまうことがあります」

🔗 必要がない限りインスタンス変数をattr_readerで公開するのは止めよう


つっつきボイス:「記事冒頭のコード例↓の2つ目でattr_reader: nameを書いている理由は、末尾の"#{name.upcase}!!!"name@を付けたくないからということらしい」「リファクタリングがしやすくなるからとかタイポに強くなるからとか理由が書かれていますね」

# 同記事より: 元のバージョン
class User
  def initialize(name)
    @name = name
  end

  def loud_name
    "#{@name.upcase}!!!"
  end
end
# 同記事より: attr_readerバージョン
class User
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def loud_name
    "#{name.upcase}!!!"
  end
end

「でもそのためにattr_reader@nameを不必要にpublicにしてしまうのはよくない、やるならせめて以下のようなprivateなattr_readerにしましょうと: たしかに」「言われてみればそうですね」「著者としてはそれもあまり好きでないようですね」

# 同記事より: private化バージョン
class User
  def initialize(name)
    @name = name
  end

  def loud_name
    "#{name.upcase}!!!"
  end

  private

  attr_reader :name
end

「ところで、name@を付けたくない理由が他にあるとすれば、@nameだと変更できてしまうからでしょうね」「あ、たしかに」「attr_readerにすれば=で代入できなくなるので、その部分の安全度は上がります」「うっかり=で代入しても大丈夫になりますね」「あまり意識的に使ったことはありませんが、こんなふうにattr_readerをprivateにするテクニックは以前どこかで見たことがある気がします」


後で探すと、attr_readerを用いてインスタンス変数を隠蔽する方法はSandi Metz『オブジェクト指向設計実践ガイド(“Practical Object-Oriented Design in Ruby”)』に書かれていると以下の記事で知りました。私が持っている同書日本語PDF版(初版第1刷)で確認するとp46〜47にありました。以下の記事には、attr_readerprivateにする方法についても紹介されています。

参考: Rubyのインスタンス変数の直接参照について - 雑草SEの備忘録

🔗 Rubyのガベージコレクションを深掘りする(Ruby Weeklyより)


つっつきボイス:「Rubyのガベージコレクション解説記事です」「Rubyで使われているというtri-color mark and sweepってどこかで聞いたかも」


同記事より

後で探すとWikipediaにTri-color markingの項がありました↓。

参考: Tri-color marking — Tracing garbage collection - Wikipedia

「Rubyのガベージコレクション周りについてはRubyKaigiでよく発表されているので、そのあたりを探してみるといいかも」「そういえばAaron PattersonさんやNate BerkopecなどもRubyのガベージコレクション記事執筆や発表をよく行っていますね↓」

Rubyのヒープをビジュアル表示する(翻訳)

Ruby: mallocでマルチスレッドプログラムのメモリが倍増する理由(翻訳)

🔗 その他Ruby

つっつきボイス:「Rubyの誕生日って1993年の2/24なのか」「2/24って昨日じゃないですか(注: つっつき時点)」「Rubyという名前が付けられた日が誕生日なんですね」「Ruby作者自らが発信した情報で知ることができるのはいいですね」

「そういえば昨年JavaScriptも25歳になったらしいですね↓」「Rubyが1歳、いや2歳若い!」(訂正: 「Rubyは2/24で28歳」です。訂正し、お詫び申し上げます)

🔗クラウド/コンテナ/インフラ/Serverless

🔗 AWS CodeDeploy


つっつきボイス:「今日Webチーム内発表のお題になっていたことでCodeDeployを知りました」「AWSのCodeDeployって見たことなかったんですけど使われているんですか?」「お、普通に使われていますよ」

「AWS CodeDeployのいいところは、ALB配下で複数台冗長化している際にシステム全体を正常にゼロダウンタイムでデプロイするための機能が統合されている点ですね: ALBに設定されたリクエストタイムアウト値に合わせた待ち時間でインスタンス切り離しを行い、healthcheckに合わせた設定でサービスインするという、当たり前だけどロールバックなどを考慮すると地味にめんどくさい機能がAWSサービスとしてまるっと統合されている良さがあります」「そういうのを自動でやってくれるんですか」「そういう機能がビルトインされています: たぶんですけどAuto ScalingでオートスケールするものならCodeDeployを使うのが便利だと思いますよ」「へ〜」

参考: CodeDeploy と Elastic Load Balancing の統合 - AWS CodeDeploy
参考: Integrating CodeDeploy with Amazon EC2 Auto Scaling - AWS CodeDeploy

「デプロイではインスタンスをALBからいきなり切り離したらダメなんですよ: まずリクエストの流入を止める、次に処理中のリクエストがあるかもしれないのでタイムアウトまで待つ、そしてトラフィックが完全に来なくなったことを確認してからインスタンスを切り離してデプロイして、終わったらつなぎ直す、もし途中で失敗したらロールバックする」「ふむふむ」「そうした一連の処理をCodeDeployはひととおりやってくれるんですよ」「そこまでやってくれるんですね」「今度やってみようかな」


「ちなみにBPSの自分のWebチームでは、AWSに全部まとめたいときなどにAWS CodeDeployが結構使われています」「お〜、そうでしたか」「数で言うとたぶんGitHub ActionsよりAWS CodeDeployの方が多いかも」「そんなに!」

GitLab自社運用のための注意点とノウハウ(2018/06版)

「自分はCIをGitHub Actionsに乗せようかなと思っているところなんですよ」「GitHubでやっているならGitHub Actionsがいいでしょうね: BPSの場合はGitLabを使っているのでプロジェクトによってはGitLab CIを使うことがよくあります」

参考: GitLabの継続的インテグレーションと継続的デリバリー | GitLab.JP


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210301前編)Rails 6.1.3がリリース、Active Supportのbefore?とafter?、link_to_unless_currentほか

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

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

Ruby Weekly

DB Weekly

db_weekly_banner

Rails 6.1: 数値バリデーションを範囲(..)で指定できるinオプションが追加(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

以下の関連記事もどうぞ。

Rails 6.1: 数値バリデーションを範囲(..)で指定できるinオプションが追加(翻訳)

numericalityバリデーションヘルパーは、特定の範囲に収まるべき数値をバリデーションするための:greater_than_or_equal_to:less_than_or_equal_to、あるいは :greater_than:less_thanといったオプションを提供しています。オプション名を見ればどのようなバリデーションを期待しているかがすぐわかるので、理解がはかどります。

でも、コードのシンプルさも表現力も損なわずに入力量を節約できる方法があれば素晴らしいと思いませんか?

最近のRailsの更新ではこの点が改善され、バリデーションしたい値の範囲を指定できる in: オプションが使えるようになりました(#41022)。

Rubyでは、以下のような範囲指定を日常的に用います。

range = (1..5)
range.each { |n| puts n }        # => 1, 2, 3, 4, 5

たとえば、プロフィールの詳細に入力するユーザーの年齢を18〜65歳の間で入力してもらいたいとします。

変更前

従来だと以下のような書き方になったでしょう。

class User < ApplicationRecord
  validates :age, numericality: { greater_than_or_equal_to: 18, less_than_or_equal_to: 65 }
end

変更後

in:オプションを使うことで、同じことを以下のように書けるようになりました。

class User < ApplicationRecord
  validates :age, numericality: { in: 18..65 }
end

ageの値が1865の範囲に収まらないUserを追加しようとすると、以下のようになります。

User.create!(name: "Chris", age: 14)

# => ActiveRecord::RecordInvalid (Validation failed: Age must be greater than or equal to 18)

関連記事

Rails 6.1: 関連付けのあるレコードを取れる’associated’クエリメソッドが追加(翻訳)

Rails 6.1: whereの関連付けでjoinedテーブルのエイリアス名を参照可能になった(翻訳)

Viewing all 1386 articles
Browse latest View live