Quantcast
Viewing all 1836 articles
Browse latest View live

Railsのdefault_scopeは使うな、絶対(翻訳)

Image may be NSFW.
Clik here to view.

概要

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


  • 2017/10/31: 初版公開
  • 2021/11/04: 更新

Railsのdefault_scopeは使うな、絶対(翻訳)

あるモデル全体にスコープを適用したい場合、default_scopeが利用できます。詳しくはRailsガイド: Active Recordクエリインターフェイス 14.スコープ(日本語)かRails APIドキュメントをご覧ください。


投稿を非表示にできる機能を持つブログシステムを書き始めるときを考えてみます。

次のように書かないこと

default_scopeを使う:

# app/models/post.rb
class Post < ActiveRecord::Base
  default_scope { where(hidden: false) }
end

次のように書くこと

明示的にスコープを指定する:

# app/models/post.rb
class Post < ActiveRecord::Base
  scope, :published -> { where(hidden: false) }
end

これで次のように書けます。

Post.published

default_scopeを使うべきでない理由

理由は2つあります。どちらも後になってコードが混乱したりバグつぶしに明け暮れたりすることを避けるのが目的です。

default_scopeを追加すると、モデルの初期化が影響を受けます。上の例で言うと、開発者が期待するかどうかにかかわらずPost.newするとデフォルトでhidden = falseになってしまいます。

いったん定義されたdefault_scopeを使わないようにするのはつらい作業です。default_scopeが不要な場面で削除するには、unscopedしたスコープ(!)を使わなければならず、しかも適用されていた関連付けなどの条件はすべて削除されてしまいます。

: Post.first.comments.unscopedとすると、Postの最初のコメントだけではなく、データベース内のすべてのコメントを返します。

default_scopeよりも明快な解決法は、名前付きスコープを明示的に使うことです。default_scopeを使えばバグつぶしに何時間も費やすことになるでしょう。default_scopeは使わないでください。

default_scopeを使ってもよさそうな場面はありますか?

どうかこればかりは私を信じてください。使えばきっと痛い目にあいます。

関連記事

よくある?Rails失敗談 default_scope編

論理削除用paranoia gemがあるRailsプロジェクトで物理削除する方法

The post Railsのdefault_scopeは使うな、絶対(翻訳) first appeared on TechRacho.


週刊Railsウォッチ: Rails 7がRuby 3.1のClass#descendantsに対応、GitHub Issue風ファイルアップローダほか(20211115前編)

Image may be NSFW.
Clik here to view.

こんにちは、hachi8833です。つっつきの後でこの番組見ました↓。

週刊Railsウォッチについて

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

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

お知らせ

来週の週刊Railsウォッチは祝日のためお休みをいただきますImage may be NSFW.
Clik here to view.
🙇

Image may be NSFW.
Clik here to view.
🔗
Rails: 先週の改修(Rails公式ニュースより)

更新情報にまた追い抜かれ中…ハロウィンエディションの残りと次の更新情報から見繕いました。

Image may be NSFW.
Clik here to view.
🔗
uuid_namespaceのパラメータが未定義文字列の場合のUUID生成を修正

Digest::UUIDで定義されているのと異なるnamespace IDの場合のDigest::UUID.uuid_from_hashの振る舞いを修正した。
新しい振る舞いは、コンフィグのconfig.active_support.use_rfc4122_namespaced_uuidsオプションをtrueに設定すると有効になる(新規アプリではこれがデフォルト)。
アップグレードしたアプリでは古い振る舞いがデフォルトになり、Digest::UUIDでnamespace IDとして定義されている定数と異なる場合に毎回deprecation warningを出力する。
Alex Robbin, Erich Soares Machado, Eugene Kenny
同Changelogより


つっつきボイス:「Digest::UUIDってあるんですね」「UUIDの名前空間とは…?ググってみるとPythonライブラリのドキュメントが出てきた↓」「これは知らなかった」「コードのDNS_NAMESPACEURL_NAMESPACEなどもこのドキュメントに載っているので、UUIDには名前空間の仕様もあるということなのか、へ〜!」

参考: uuid --- RFC 4122 に基づくUUID オブジェクト — Python 3.10.0b2 ドキュメント

# activesupport/lib/active_support/core_ext/digest/uuid.rb#58
+   def self.pack_uuid_namespace(namespace)
+     if [DNS_NAMESPACE, OID_NAMESPACE, URL_NAMESPACE, X500_NAMESPACE].include?(namespace)
+       namespace
+     elsif use_rfc4122_namespaced_uuids == true
+       match_data = namespace.match(/\A(\h{8})-(\h{4})-(\h{4})-(\h{4})-(\h{4})(\h{8})\z/)
+
+       raise ArgumentError, "Only UUIDs are valid namespace identifiers" unless match_data.present?
+
+       match_data.captures.map { |s| s.to_i(16) }.pack("NnnnnN")
+     else
+       ActiveSupport::Deprecation.warn <<~WARNING.squish
+         Providing a namespace ID that is not one of the constants defined on Digest::UUID generates an incorrect UUID value according to RFC 4122.
+         To enable the correct behavior, set the Rails.application.config.active_support.use_rfc4122_namespaced_uuids configuration option to true.
+       WARNING
+
+       namespace
+     end
+   end

参考: Rails API Digest::UUID

Image may be NSFW.
Clik here to view.
🔗
Ruby 3.1のClass#descendantsに対応

Ref: https://bugs.ruby-lang.org/issues/14394
Ref: ruby/ruby#4974
ObjectSpaceをイテレーションする必要のないClass#descendantsのネイティブ実装がRuby 3.1に入る公算が高いので、可能ならこれを使うべき。
Rubyのプルリクがマージされるのを待ってからこちらをマージするつもり。DescendantsTrackerのほとんどをスキップする必要もあるが、検出方法がまだわからないので、利用可能かどうかをRUBY_VERSIONでチェックする方法で回避した。

# activesupport/lib/active_support/core_ext/class/subclasses.rb#L17
  def descendants
    ObjectSpace.each_object(singleton_class).reject do |k|
      k.singleton_class? || k == self
    end
- end
+ end unless method_defined?(:descendants) # RUBY_VERSION >= "3.1"

つっつきボイス:「ActiveSupport::DescendantsTrackerにあるdescendantsメソッドが、Ruby 3.1にネイティブのClass#descendantsが入ったのを機にRubyのバージョンを見て切り替えるようにしたんですね」「Active SupportにあるメソッドがこうやってRuby本家に取り入れられることはときどきありますよね」「descendantsもとうとう出世メソッドになった」「出世おめでとうございますImage may be NSFW.
Clik here to view.
🎉

Image may be NSFW.
Clik here to view.
🔗
テストごとにExecutor#wrapを呼び出すオプションが追加

  • Rails.application.executorフックがすべてのテストの前後で呼ばれるようになった。
    これにより、リクエストやジョブのローカルなステートがリセットされるのを適切にシミュレートするようになり、テストとテストの間でステートが漏れるのを防止できる。
    ただし、test環境で実行されるexecutorフックはリエントラントであることが求められる。
    Jean Boussier
    同Changelogより

つっつきボイス:「変更が割と多いかも」「テスト間でステートが漏れ出さないようにする改修: active_support.executor_around_test_case = trueというコンフィグで設定できるのね」

Image may be NSFW.
Clik here to view.
🔗
date_selectヘルパーにday_formatが追加

:year_formatオプションと同様の:day_formatオプションをdate_selectに追加。
ロケールをja(日本語)にしたRailsアプリケーションとrails-i18nではdate_selectで以下が表示される。
Image may be NSFW.
Clik here to view.

日本では2021-10-29という日付を2021年10月29日と表示するのが普通(年はyear、月はmonth、日はdayの意味)。
しかし現在のdate_selectにはdayの書式をカスタマイズするオプションがないのでday_formatを追加した。

form.date_select :published_date, year_format: ->(year) { "#{year}年" }, day_format: ->(day) { "#{day}日" }

これにより、上のように書くと以下が出力される。
Image may be NSFW.
Clik here to view.

form.date_select :published_date, order: %i[day month year], day_format: ->(day) { day.ordinalize }

また、上のように書くと以下のように出力できる。
Image may be NSFW.
Clik here to view.

同PRより


つっつきボイス:「お〜なるほど、今までyear_formatは指定できたけどday_formatはできなかったのをできるようにしたんですね」「最近RailsでAPIとバッチを書くことが多くてビューを書く機会があまりなかったけど、今までできなかったのか〜」「month_formatがないと思ったらmonth_format_stringがあった」

Image may be NSFW.
Clik here to view.
🔗
delegated_typeaccepts_nested_attributes_forをサポート

このプルリクは、delegated_typeaccepts_nested_attributes_forのサポートを追加する。これで以下のようなメソッドを書かずにレコードを手軽に作成や更新できるようになる。

class Entry < ApplicationRecord
  delegated_type :entryable, types: %w[ Message Comment ]

  def self.create_with_comment(content, creator: Current.user)
    create! entryable: Comment.new(content: content), creator: creator
  end
end

accepts_nested_attributes_forが使えると以下のように書ける。

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

params = { entry: { entryable_type: 'Comment', entryable_attributes: { content: 'Smiling' } } }
entry = Entry.create(params[:entry])

作った理由
accepts_nested_attributes_forによるネステッドフォームは非常に強力。これはDelegated Typeでも使えるようにするために足りなかった最後のピースとなる。
疑問
これはポリモーフィックなbelongs_toリレーションシップなので、他にどんなテストがあるとよいだろうか?(TestNestedAttributesOnABelongsToAssociationで既にテストされているので)
同PRより


つっつきボイス:「Delegated Typeといえば以前話題になりましたね(ウォッチ20200601)」「Delegated Typeでaccepts_nested_attributes_forが使えるようになったのね: 自分はaccepts_nested_attributes_forは使わないけど」「このメソッドで苦しんだ人を割とよく見かける印象あります」「Rails wayなフォームですべてできるならいいんですが、そこからはみ出したときにちょっとね…」

参考 Railsのaccepts_nested_attributes_forについて解説してみた。 | 目指せ、スーパーエンジニア

「そういえばDelegated TypeのAPIドキュメント↓を翻訳したので近々公開しようと思います」

Image may be NSFW.
Clik here to view.
🔗
Rails

Image may be NSFW.
Clik here to view.
🔗
StimulusとActive StorageでGitHub Issue風のファイルアップローダを作る(Ruby Weeklyより)


つっつきボイス:「これはたしかにGitHub issue風↓」「こんなふうに作れるんですね、いいな〜」

Image may be NSFW.
Clik here to view.

同記事より

「今日ちょうど社内勉強会で話題になりましたけど、Active Storageはダイレクトアップロードにも対応していますね」「そうそう、意外に知られてないのかも」「サーバーに負担をかけずにクラウドにアップロードできるダイレクトアップロード機能はあってうれしい機能ですね: 要するにでかいファイルをサーバーで受け取りたくない」「ほんとに」

参考: 9 ダイレクトアップロード — Active Storage の概要 - Railsガイド

Image may be NSFW.
Clik here to view.
🔗
minitest-spec-rails(Ruby Weeklyより)

Image may be NSFW.
Clik here to view.
metaskills/minitest-spec-rails - GitHub

何だか懐かしい風味のロゴと東洋風フォントですね↓。

Image may be NSFW.
Clik here to view.

同リポジトリより


つっつきボイス:「minitestでspecが使いたいのかな?」「普通にminitestで書けばよさそうですけどね」

「お、ActiveSupport::TestCaseを使いながらMiniTest::Spec::DSLにあるitでも書けるのね↓」「なるほど」「ActiveSupport::TestCaseだけどRSpec風にitでも書きたい人向けという感じかな」

# 同リポジトリより
require 'test_helper'
class UserTest < ActiveSupport::TestCase
  let(:user_ken)   { User.create! :email => 'ken@metaskills.net' }
  it 'works' do
    user_ken.must_be_instance_of User
  end
end

ActiveSupport::TestCaseを継承しないとテスト用拡張モジュールを使えないようなgemは、実は割とあるんですよ」「あ〜」「ズバリRailsがそうだったりします」「そういえばRailsフレームワークのテストコードはRSpecじゃないんですよね」「minitestですね」

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

Image may be NSFW.
Clik here to view.
🔗
Railsコアメンバーによるプルリクレビュー実況動画


つっつきボイス:「ruby-jp SlackのEnglishチャンネルで見かけた、RailsコアメンバーのKasperさんがRailsフレームワークへのプルリクをレビューする様子の実況中継です」「お〜、RubyKaigiなどでやりそうな企画ですね: ヒアリングの練習がてら見てみるといいかも」

Image may be NSFW.
Clik here to view.
🔗
その他Rails


つっつきボイス:「そうそう、@koicさんが11/19(金)の銀座Rails#39の招待講演枠に登壇されます: まだ一般の発表枠はあるので皆さんぜひお気軽に応募お願いします↓」


前編は以上です。

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

週刊Railsウォッチ: JSON.parseの機能、Opal 1.3、async gem、Linuxコマンドチートシートほか(20211110後編)

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

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

Rails公式ニュース

Image may be NSFW.
Clik here to view.

Ruby Weekly

Image may be NSFW.
Clik here to view.

The post 週刊Railsウォッチ: Rails 7がRuby 3.1のClass#descendantsに対応、GitHub Issue風ファイルアップローダほか(20211115前編) first appeared on TechRacho.

週刊Railsウォッチ: Ruby Struct入門、書籍『進化的アーキテクチャ』、AWS Web問題集ほか(20211116後編)

Image may be NSFW.
Clik here to view.

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

お知らせ

来週の週刊Railsウォッチは祝日のためお休みをいただきます。

Image may be NSFW.
Clik here to view.
🔗
Ruby

Image may be NSFW.
Clik here to view.
🔗
Ruby 3.1-preview1とYJIT

YJITを手元で少し試してみました。

Ruby 3.1.0-preview1がリリースされました


つっつきボイス:「手元で3.1.0-preview1をインストールしてごく簡単にYJITを試してみた感じでは、特定の機能というより全般に速くなっている印象でした」「わーい、preview1ぼくも入れてみよう〜」「リリースノートをざっと見た限りでは、breaking changeという言葉は見当たらなかったので安心度は高いかなと思いました」「たしかにありませんでしたね」

「YJITはベンチマークでも強いと思いますが、アプリなどでも速くなるでしょうね」「optcarrot以外でも速くなるといいな〜」「optcarrotはRubyのベンチマークによく使われるファミコンエミュレータですね↓」

Image may be NSFW.
Clik here to view.
mame/optcarrot - GitHub

Image may be NSFW.
Clik here to view.
🔗
RubyでGo処理系を書く


つっつきボイス:「Evil Martiansの記事、とても面白かった」「Matzもリツイートしていましたね」「mattnさんがツイートで見事に記事を要約してくれていました」「Rubyはこういうこともできてしまうのがホント面白い」


「そういえばこの記事のアイキャッチ画像が何かに似ているって言ってませんでした?」「そうそう、ジュラシック・パークのオープニングにあった、琥珀の中に蚊が閉じ込められている映像みたいだなと思って」「懐かしい〜、GopherくんがRubyの中に封印されている感じですね」「その蚊が吸った恐竜の血液からDNAを取り出して恐竜を現代に再生するんでしたっけ」

参考: ジュラシック・パーク - Wikipedia

Image may be NSFW.
Clik here to view.
🔗
Ruby Struct入門(Ruby Weeklyより)


つっつきボイス:「記事ではrefinementsまで登場してる」「RubyのStructは、いわゆるPORO(Plain Old Ruby Object)ですね: OpenStructは登場してから多少変わってきたようですが、RubyのStructはそれほど変わっていないかな」「StructはMarshalのdumprestoreも問題なくできたはず」

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

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

「RubyのStructは弊社にも好きな人がいて、よくStructでValue Objectを作ったりしてます: 自分はハッシュでValue Objectを作ることが多いですが」「ふむふむ」

「ただ、RubyのStructはループ内でインスタンスをたくさん生成したりすると遅くなる傾向があるんですよ」「そういえばJeremy Evansさんの『Polished Ruby Programming』↓にも、RubyのStructはメモリ消費が少なめだけどアクセサメソッドが遅いとありました」「そうそう、Structは大量に生成するにはまだあまり向いていないかなと思っていますが、Marchal.dumpされていたStructをMarshal.loadとかするならたぶん速い」

『Polished Ruby Programming』(Jeremy Evans著)を読みました

同書では「Structはもっと注目されてもよい」「イミュータブルなStructが欲しいという意見もあるがまだRubyには入っていない」という記述もありました。探してみると#16769immutable: trueが提案されていましたがrejectされていました。

Image may be NSFW.
Clik here to view.
🔗
chruby: Rubyのバージョンを変更するツール

Image may be NSFW.
Clik here to view.
postmodern/chruby - GitHub


つっつきボイス:「Ruby 3.1.0-preview1で以下のyjit-bench↓を動かそうと思って調べていたら、このchrubyに依存していたことで知りました」「チェンジルビーって読むんですか」「rbenvのオルタナのようですね」「サイズが小さい(100行以下)とはスゴい」

Image may be NSFW.
Clik here to view.
Shopify/yjit-bench - GitHub

「chrubyちょっと入れてみたんですが、既存のrbenvとどう折り合いをつけたものかよくわからなかったのでいったん外しました」「自分はもうDocker環境で作業するのが当たり前になってきているので、Rubyのバージョン切り替えのことは最近あまり気にしていませんね」「そういえば自分もそうだった」「考える時間がもったいないので、迷ったら即Dockerにしてます」

Image may be NSFW.
Clik here to view.
🔗
benchmark_driver

Image may be NSFW.
Clik here to view.
benchmark-driver/benchmark-driver - GitHub


つっつきボイス:「benchmark-driverはだいぶ前にウォッチで見た気がする」「当時ウォッチで取り上げたときは(ウォッチ20189330)日本語記事がなかったんですが、YJITのベンチマークを手元でやろうと思って調べていたらk0kubunさんがQiitaに書いていた記事をたまたま見つけました」

「こんなふうにRubyのバージョンを複数指定してベンチマークを実行できるのね↓」「これでYJITを試してみました」「便利そう」

# 同記事より: 複数のRubyバージョン
# require 'benchmark/driver'

Benchmark.driver do |x|
  x.rbenv '2.0.0', '2.4.2'
  x.prelude %{
    class Array
      alias_method :blank?, :empty?
    end
    array = []
  }
  x.report 'Array#empty?', %{ array.empty? }
  x.report 'Array#blank?', %{ array.blank? }
  x.compare!
end

「お、ベンチマーク設定をYAMLファイルを書ける↓」「CIでベンチマークを回すのによさそうImage may be NSFW.
Clik here to view.
👍

# 同記事より: yamlコンフィグ
prelude: |
  class Array
    alias_method :blank?, :empty?
  end
  array = []
benchmark:
  Array#empty?: array.empty?
  Array#blank?: array.blank?

「なお以下はRuby Weeklyで紹介されていました」「Rubyのメモリプロファイリングができるベンチマークソフトですね」

Image may be NSFW.
Clik here to view.
michaelherold/benchmark-memory - GitHub

Image may be NSFW.
Clik here to view.
🔗
その他Ruby

つっつきボイス:「パッチを投げたいgemがあるのでRails/OSSパッチ会出席したいなと思っているんですが、なかなか機会がなくて」「従来はIdobataが使われていたんですが、半月ぐらい前にDiscordに移っていました」「Discordは使っている人多いですよね」


つっつきボイス:「メソッドのオーバーロード機能はC++などにありますね: 同じメソッド名でもパラメータの型や個数や順序が違うと別メソッドとして呼べるもので、そういう言語ではメソッド名とパラメータリストと型をまとめてメソッドシグネチャと呼んでたりします」「言語によっては戻り型も含まれますね」「そうそう、それも含めてメソッドの仕様になりますね」

参考: メソッドのシグネチャ(signature)とメソッドの構文(syntax)の違い - いっしきまさひこBLOG

「Rubyにオーバーロード機能はないので、既存のものと同じメソッド名を書くとオーバーライドされる仕様: その分メソッド名がかぶらないよう気にしないといけませんが」

Image may be NSFW.
Clik here to view.
🔗
クラウド/コンテナ/インフラ/Serverless

Image may be NSFW.
Clik here to view.
🔗
AWS Lambdaによる進化的アーキテクチャの構築


つっつきボイス:「はてブでバズっていた記事です」「最近よく見かけるヘキサゴナルアーキテクチャにLambdaを絡めた設計の話ですね」「最近のAWSはこういう技術記事のようなメディア展開にも力を入れていますね」

参考: ヘキサゴナルアーキテクチャ(Hexagonal architecture翻訳) | blog.tai2.net

「モノリスだとちょっとデータを取り出して調べたりというのは簡単なんですが、ヘキサゴナルアーキテクチャになると複数の業務を横断したデータをお互いに紐付けたまま取り出す(RDBのJOINなど)のが簡単にできなくなって、ベストプラクティスに従うと周辺サービスを購入してそれ経由でデータにアクセスすることになったりしがちなんですよ」「そうそう、ありがちです」「出費かさむんですね」「レイヤー化された設計はたしかにきれいなんですが、気がつくと追加費用が増えていたりしオーバーキルになったりしがち」

「もちろん大規模システムは最初からきちんと設計するべきですが、何をするにも大げさになりがちなので、モノリスの取り回しの良さが捨てがたいと思うときもあります」「それわかります」

「その一方で、モノリスは開発メンバーが異動したときの影響が大きいので、それを考えるとヘキサゴナルアーキテクチャのように設計を統一して担当範囲を絞っておくことも理にかなっていると思います」「たしかに」「メンバー異動のコストをヘキサゴナルアーキテクチャで先払いしていると思えばいいのかなという気もします」「う〜む考えさせられます」「ヘキサゴナルアーキテクチャは費用も大きいですし、簡単に結論を出せる話ではありませんね」

Image may be NSFW.
Clik here to view.
🔗
書籍『進化的アーキテクチャ』

「記事の中で『進化的アーキテクチャの構築』というリンクがあるのでクリックしてみたらBuilding Evolutionary Architecturesという本が出てきました」「オライリーでも日本語版出ていて自分も持ってます↓」

Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

「目次を見た限りでは、クラウドサービスのカタログというよりも、コンピュータサイエンスの基礎のようにアーキテクチャをみっちり学ぶ本のようですね」「そうそう、コンピュータサイエンス基礎という感じで、最初はモノリスだったのがだんだん大きくなってサーバーレスになって…みたいなことが解説されてました」「一般性が高くてなかなかよさそうな本Image may be NSFW.
Clik here to view.
👍
: 買ってみよう」

Image may be NSFW.
Clik here to view.
🔗
aws.koiwaclub.comのAWS Web問題集


つっつきボイス:「BPS社内SlackでAWS受験対策おすすめとして貼っていただいたURLです」「自分は2016年頃にこのサイトに登録してます: おすすめImage may be NSFW.
Clik here to view.
👍

「まず当時から問題数がとても豊富: AWS公式の問題集より問題数が多くて、しかもAWS公式みたいに過去問が見られなくなることもない」「お〜」「無料のサンプル問題集だけでもかなり量があります(問題がどのぐらい更新されているかはわかりませんが)」「自分もこれで勉強して受けてみようかな」

「試験ってどのぐらい難しいんでしょうか?」「AWSのソリューションアーキテクトは、普段AWSのダッシュボードを操作している程度だとそれなりに対策が必要だと思いますが、顧客向けの提案書を書くときにAWSのSLAやサービスの制限事項や拡張性に関するドキュメントを詳しく読む機会が多ければ、それほど苦労しなくても取れると思いますよ」「お〜、ちょっと頑張って取ってみようという気持ちになりました」「とりあえずフリープランの問題集を解いて感触を確かめておくといいと思います」

参考: AWS サービスレベルアグリーメント — SLA

Image may be NSFW.
Clik here to view.
🔗
CSS/HTML/フロントエンド/テスト/デザイン

Image may be NSFW.
Clik here to view.
🔗
HTTPのQUERYメソッド


つっつきボイス:「はてブで見つけた記事です」「お、これ読みました: GETで付けられないボディをQUERYだと付けられるんでしたっけ」「GETはボディが付けられないのでたくさん渡そうとするとURLパラメータを使うことになりますが、URLの長さに制限があるのと、URLパラメータがサーバーログに残るのが嬉しくないんですよ」「お〜」「記事にもあるように、QUERYならURLエンコードも不要でURL長さ制限にもかからないとありますね: 使えると嬉しいかもImage may be NSFW.
Clik here to view.
👍

Image may be NSFW.
Clik here to view.
🔗
言語/ツール/OS/CPU

GitHub Copilotのベータ申し込みしていたのがいつの間にか有効になっていたので、VSCodeでJSとRubyのAI補完を初めて体験できました。GitHub Spacesも待ってます。

Image may be NSFW.
Clik here to view.
github/copilot-docs - GitHub

Image may be NSFW.
Clik here to view.
🔗
シェル考古学


つっつきボイス:「どちらもとても長い記事でしたが読み応えあったので貼ってみました」「考古学だけあってものすごい量ですね」「ここまで詳しく書いているとは」「著者紹介にShellSpecというのもあった↓」「シェルスクリプトの単体テストフレームワークなんですね: カバレッジまでできるとは」

Image may be NSFW.
Clik here to view.
shellspec/shellspec - GitHub

「正しいシェルスクリプトを自信を持って書ける人」「貴重な情報ありがたいですImage may be NSFW.
Clik here to view.
🙏
」「シェル芸の元締めとして知られるこの方のことも思い出しました↓」

Image may be NSFW.
Clik here to view.
🔗
OpenID Connect

つっつきボイス:「OpenID Connect(OIDC)がサポートされたというのはどのあたりが嬉しい感じでしょうか?」「OIDCはサーバーにAPIキーとして置かなくていいことに加えて、トークンの更新がプロトコルレベルでサポートされているのが嬉しい点かなと思います」「お〜」

「一度発行されたAPIキーを期限切れさせるにはAPIを発行したサーバー側が対応すればできますが、APIキーを使う側が自分自身のAPIキーを更新する仕組みは一般的にはないんですよ」「ふむふむ」「なのでAPIキーが漏洩したら大変」

「OIDCなら仕様の中にAPIキーの更新処理も含まれていてプログラムで更新できるので、その部分についての安心度は高いでしょうね」「なるほど」


後編は以上です。

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

週刊Railsウォッチ: Rails 7がRuby 3.1のClass#descendantsに対応、GitHub Issue風ファイルアップローダほか(20211115前編)

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

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

Ruby Weekly

Image may be NSFW.
Clik here to view.

Publickey

Image may be NSFW.
Clik here to view.
publickey_banner_captured

The post 週刊Railsウォッチ: Ruby Struct入門、書籍『進化的アーキテクチャ』、AWS Web問題集ほか(20211116後編) first appeared on TechRacho.

Rails: ActiveRecord::DelegatedType APIドキュメント(翻訳)

Image may be NSFW.
Clik here to view.

概要

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

ActiveRecord::DelegatedTypeはRails 6.1以降で利用できます。delegated typeは英ママとしました。

週刊Railsウォッチ20200601 新機能: Active Recordにdelegated_typeが追加もどうぞ。

また、Rails 7ではaccepts_nested_attributes_forもサポートされます。

週刊Railsウォッチ20211115前編 delegated_typeaccepts_nested_attributes_forをサポート

Rails: ActiveRecord::DelegatedType APIドキュメント(翻訳)

Delegated typeについて

Class階層は、さまざまな方法でリレーショナルデータベースのテーブルとマッピングできます。たとえば、スーパークラスが属性を持たない純粋抽象クラスや、階層のあらゆるレベルの属性を1個のテーブルで表現するSTI(Single Table Inheritance: 単一テーブル継承)がActive Recordで提供されています。どちらの手法にも使い所がありますが、どちらにも欠点があります。

純粋抽象クラスの問題は、すべての具象サブクラスが、そのサブクラス自身が持つテーブル内で共有される属性をすべて永続化しなければならないことです(これはクラステーブル継承とも呼ばれます)。そのため、クラス階層にまたがるクエリが難しくなります。たとえば、以下のクラス階層があるとします。

Entry < ApplicationRecord
Message < Entry
Comment < Entry

このとき、MessageモデルのレコードとCommentモデルのレコードを両方使うフィードを、ページネーションしやすい形で表示するにはどうすればよいでしょうか?これはできません。メッセージにはmessagesテーブルがあり、コメントにはcommentsテーブルがあります。両方のテーブルから一度にデータを取り出して、しかも一貫したOFFSET/LIMITスキームを使うのは無理です。

ページネーションの問題はSTIを使えば回避できますが、その代わりすべてのサブクラスにあるすべての属性を巨大な1個のテーブルに保存することを強いられます。属性がテーブルごとにどれほど異なっていようと、そうするしかありません。メッセージにsubject属性があり、コメントにはsubject属性がなくても、結局コメントにもsubject属性が入ってきてしまいます。つまり、STIはサブクラス同士やサブクラスの属性同士のばらつきがほとんどない場合にベストです。

しかし、第3の「delegated type」という方法があります。このアプローチでは、「スーパークラス」が独自のテーブルで表現する具象クラスになっていて、すべての「サブクラス」で共有される属性はすべてスーパークラス独自のテーブルに保存されます。そして、各サブクラスはその実装特有の属性を保存するテーブルを個別に持ちます。これはDjangoフレームワークでマルチテーブル継承と呼ばれているものと似ていますが、このアプローチでは実際の継承ではなく、委譲を用いて階層を形成しつつ責務を共有します。

delegated typeを用いたEntry、Message、Commentのコード例を見てみましょう。

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

module Entryable
  extend ActiveSupport::Concern

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

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

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

ご覧のように、MessageモデルもCommentモデルも単独では成立しません。両方のクラスにある重要なメタデータは、「スーパークラス」であるEntryモデルに存在します。しかしEntryモデルは、特にクエリ機能の面では絶対的に独立しています。これで、以下のような操作を簡単に行なえるようになります。

Account.find(1).entries.order(created_at: :desc).limit(50)

これはまさに、コメントとメッセージをまとめて表示したいときに欲しくなるものです。エントリそのものはdelegated typeとして以下のように簡単にレンダリングできます。

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

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

「スーパークラス」であるEntryモデルは、メッセージとコメントの両方に適用するすべての共有ロジックを置く場所としても申し分ありません。以下を想像してみてください。

class Entry < ApplicationRecord
  include Eventable, Forwardable, Redeliverable
end

これで、ForwardsControllerRedeliverableControllerのような、どちらもエントリ上で動かすものに使うコントローラを持てるようになり、メッセージとコメントの両方で機能を共有できるようになります。

新規レコードを作成する

delegated typeを用いる新規レコードを作成する場合は、以下のようにdelegator(委任側)とdelegatee(受任側)を同時に作成してください。

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

さらに複雑なコンポジションが必要な場合や、依存のバリデーションを実行する必要がある場合は、factoryメソッドかfactoryクラスを構築して複雑なニーズを扱います。これは以下のようにシンプルにできます。

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

さらに委譲を追加する

このdelegated typeは、背後のクラスが何と呼ばれるかという質問に答えるだけの存在であってはいけません。これはほとんどの場合アンチパターンになります。この階層を構築する理由は、ポリモーフィズムを利用するためです。以下に簡単なコード例を示します。

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

class Message < ApplicationRecord
  def title
    subject
  end
end

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

これで、大量のエントリをリストしてEntry#titleを呼び出したときに、ポリモーフィズムが答えを出してくれます。


publicインスタンスメソッド

delegated_type(role, types:, **options)

渡されたroleの型をtypes内のクラス参照に委譲するクラスとして定義します。これにより、そのroleへのポリモーフィックなbelongs_toリレーションシップが作成され、delegated typeの便利メソッドがそこにすべて追加されます。

class Entry < ApplicationRecord
  delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
end

Entry#entryable_class # => +Message+ または +Comment+
Entry#entryable_name  # => "message" または "comment"
Entry.messages        # => Entry.where(entryable_type: "Message")
Entry#message?        # => entryable_type == "Message"の場合はtrue
Entry#message         # => entryable_type == "Message"の場合はmessageレコードを返し、それ以外はnilを返す
Entry#message_id      # => entryable_type == "Message"の場合はentryable_id,を返し、それ以外はnilを返す
Entry.comments        # => Entry.where(entryable_type: "Comment")
Entry#comment?        # => entryable_type == "Comment"の場合はtrue
Entry#comment         # => entryable_type == "Comment"の場合はcommentレコードを返し、それ以外はnilを返す
Entry#comment_id      # => entryable_type == "Comment"の場合はentryable_idを返し、それ以外はnilを返す

以下のように名前空間付きの型も宣言できます。

class Entry < ApplicationRecord
  delegated_type :entryable, types: %w[ Message Comment Access::NoticeMessage ], dependent: :destroy
end

Entry.access_notice_messages
entry.access_notice_message
entry.access_notice_message?
オプション

optionsは、belongs_to呼び出しに直接渡されるので、dependentなどはここで宣言します。以下のオプションを含めることで、delegated typeの便利なメソッドの振る舞いを特殊化できます。

  • :foreign_key

便利メソッドで用いる外部キーを指定します。デフォルトでは、_idをサフィックスしたものがroleに渡されると推測します。つまり、delegated_type :entryable, types: %w[ Message Comment ]という関連付けを定義しているクラスでは、デフォルトで:foreign_keyに”entryable_id”が使われます。

  • :primary_key

便利メソッドで用いられる、関連付けされたオブジェクトの主キーを返すメソッドを指定します。これはデフォルトではidになります。

オプションの利用例は以下のとおりです。

class Entry < ApplicationRecord
  delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
end

Entry#message_uuid      # => entryable_type == "Message"の場合はentryable_uuidを返し、それ以外はnilを返す
Entry#comment_uuid      # => entryable_type == "Comment"の場合はentryable_uuidを返し、それ以外はnilを返す

関連記事

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

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

The post Rails: ActiveRecord::DelegatedType APIドキュメント(翻訳) first appeared on TechRacho.

Rais 7のbyebugがruby/debugに置き換わる(翻訳)

Image may be NSFW.
Clik here to view.

概要

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

Rais 7のbyebugがruby/debugに置き換わる(翻訳)

皆さんもご存知のように、デバッガはプログラマがコードを手軽にデバッグするための主要なツールです。Rails 7の最新の変更で、従来のbyebugruby/debugに置き換えられます(#43187)。

byebugは2014年からRailsプログラマーのアプリケーションデバッグを支えてきました(当時のプルリクは#14646で参照できます)。Rails 7からは、ruby/debugのがデフォルトのデバッガになります。

ruby/debugはRuby 3.1に同梱されることになっている新しいデバッガで、Ruby 3.1の標準ライブラリになる予定です。Rails 7にruby/debugが追加されることで、Rails 7とRubyの新しい機能が手を組むことになります。

デバッグにbyebugdebugを使った例をそれぞれ見ていきましょう。

byebugを使う場合

  class TestController < ApplicationController
    def index
      name = "Sam"
      age = 23
      byebug
      place = "Boston"
    end
  end

  # Result

  [1, 8] in /Users/sam/test_app/app/controllers/test_controller.rb
    1: class TestController < ApplicationController
    2:   def index
    3:     name = "Sam"
    4:     age = 23
    5:     byebug
  => 6:    place = "Boston"
    7:   end
    8: end
  (byebug) name
  "Sam"

ruby/debugを使う場合

  class TestController < ApplicationController
    def index
      name = "Sam"
      age = 23
      binding.break
      place = "Boston"
    end
  end

  # Result

  [1, 8] in ~/test_app/app/controllers/test_controller.rb
      1| class TestController < ApplicationController
      2|    def index
      3|       name = "Sam"
      4|       age = 23
  =>   5|       binding.break
      6|       place = "Boston"
      7|    end
      9| end
  =>#0  TestController#index at ~/test_app/app/controllers/test_controller.rb:5
    #1  ActionController::BasicImplicitRender#send_action(method="index", args=[])
  (rdbg) name     # ruby
  "Sam"
  (rdbg) continue # command

ruby/debugのコマンドや機能について詳しくは、ruby/debugリポジトリをどうぞ。

Image may be NSFW.
Clik here to view.
ruby/debug - GitHub

関連記事

ruby/debugのChrome Devtools連携をRailsで動かす

Rubyの新しいデバッガの機能を先行紹介(翻訳)

The post Rais 7のbyebugがruby/debugに置き換わる(翻訳) first appeared on TechRacho.

Ruby 3.1: ES6風のハッシュリテラル省略記法が追加(翻訳)

Image may be NSFW.
Clik here to view.

概要

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

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

Ruby 3.1: ES6風のハッシュリテラル省略記法が追加(翻訳)

Ruby 3.1に、ECMAScript 6オブジェクトのショートハンド構文に似たハッシュリテラルのショートハンド構文が追加されました。(c60dbcd)。

まず、ECMAScript 6のハッシュリテラル値省略がどのようなものかを見てみましょう。

const a = 1;
const b = 2;
const c = {a, b};
console.log(c);

//=> {"a":1,"b":2}

このショートハンド構文の主なメリットは、プロパティとして渡される変数名と同じキー名を持つハッシュを定義したい場合に、キー名を渡すだけでショートハンド構文を利用できることです。

変更前

a = 1
b = 2
c = {a: a, b: b}
#=> {:a=>1, :b=>2}

c = {"a": a, "b": b}
#=> {:a=>1, :b=>2}

上の例では、キー名と値が同じであっても、いちいち値を書かなければなりません。

変更後

a = 1
b = 2
c = {a:, b:}
#=> {:a=>1, :b=>2}

c = {"a":, "b":}
#=> Raises SyntaxError

上の例では、ハッシュに値を書かずにキー名だけを書いています。これで、キー名と同じ名前を持つ値を識別できます。

ただしご覧のとおり、キーを文字列で渡すとエラーになります。

関連記事

Ruby 3.1にArray#intersect?メソッドが追加(翻訳)

The post Ruby 3.1: ES6風のハッシュリテラル省略記法が追加(翻訳) first appeared on TechRacho.

Ruby 3.0.3/2.7.5/2.6.9セキュリティ修正がリリースされました

Image may be NSFW.
Clik here to view.

Ruby 3.0.3/2.7.5/2.6.9セキュリティ修正がリリースされました。

リリースノート

詳しくは上記リリース情報をご覧ください。

注釈

Ruby 2.6.x系は現在セキュリティメンテナンスフェーズにつき、重大なセキュリティ問題の修正のみが行われます。詳しくはRuby 2.6.9 リリースノートをご覧ください。

Image may be NSFW.
Clik here to view.
🔗
修正の概要

Ruby本体に含まれる「default gem」↓の脆弱性が3件修正されました。

参考: standard librariesとdefault gemsとbundled gemsの違い - esm アジャイル事業部 開発者ブログ

基本的には当該gemを更新するかRubyバージョンを更新することで修正可能です。

CVE-2021-41817: Regular Expression Denial of Service Vulnerability of Date Parsing Methods

CVE-2021-41817はdate gemのDate.parseなどのメソッド内部で使われている正規表現の脆弱性に関連します。詳しくは上記リンク先の情報をご覧ください。

Image may be NSFW.
Clik here to view.
ruby/date - GitHub

  • 影響を受けるdate gemバージョン
    • date gem 3.2.0以前
    • date gem 3.1.1以前(Ruby 3.0.3以前のRuby 3.0系)
    • date gem 3.0.1以前(Ruby 2.7.5以前のRuby 2.7系)
    • date gem 2.0.0以前(Ruby 2.6.9以前のRuby 2.6系)
  • gemが修正されたRubyバージョン: 3.0.3/2.7.5/2.6.9
  • 修正されたdate gemバージョン
    • date gem 3.2.1以降
      • 本記事公開時点ではdate gem 3.2.2です。
    • date gem 3.1.2以降
      • 本記事公開時点ではdate gem 3.1.3です。
    • date gem 3.0.2以降
      • 本記事公開時点ではdate gem 3.0.3です。
    • date gem 2.0.1以降
      • 本記事公開時点ではdate gem 2.0.2です。

CVE-2021-41816: Buffer Overrun in CGI.escape_html

CVE-2021-41816はcgi gemのCGI.escape_htmlメソッドで見つかった正規表現の脆弱性に関連します。詳しくは上記リンク先の情報をご覧ください。

Image may be NSFW.
Clik here to view.
ruby/cgi - GitHub

  • 影響を受けるRubyバージョン: Ruby 2.7以降
  • 影響を受けるcgi gemバージョン
    • cgi gem 0.3.0以前
    • cgi gem 0.2.0以前(Ruby 3.0.3以前のRuby 3.0系)
    • cgi gem 0.1.0以前(Ruby 2.7.5以前のRuby 2.7系)
  • gemが修正されたRubyバージョン: 3.0.3/2.7.5
  • 修正されたcgi gemバージョン
    • cgi gem 0.3.1以降
    • cgi gem 0.2.1以降
    • cgi gem 0.1.1以降

CVE-2021-41819: Cookie Prefix Spoofing in CGI::Cookie.parse

CVE-2021-41819はcgi gemのCGI::Cookie.parseメソッドで見つかった脆弱性に関連します。詳しくは上記リンク先の情報をご覧ください。

Image may be NSFW.
Clik here to view.
ruby/cgi - GitHub

  • 影響を受けるRubyバージョン: Ruby 3.0、Ruby 2.7、Ruby 2.6.8以前
    • ただしRuby 2.6.8以前はgem update cgiでは修正できません。Ruby を2.6.9以降にアップデートする必要があります。
  • 影響を受けるcgi gemバージョン
    • cgi gem 0.3.0以前
    • cgi gem 0.2.0以前(Ruby 3.0.3以前のRuby 3.0系)
    • cgi gem 0.1.0以前(Ruby 2.7.5以前のRuby 2.7系)
  • gemが修正されたRubyバージョン: 3.0.3/2.7.5/2.6.9
  • 修正されたcgi gemバージョン
    • cgi gem 0.3.1以降
    • cgi gem 0.2.1以降
    • cgi gem 0.1.1以降

参考: ruby-buildとDocker Hub

rbenvで使われるruby-buildでは、既にRuby 3.0.3/2.7.5/2.6.9が利用可能になっています。

Image may be NSFW.
Clik here to view.
rbenv/ruby-build - GitHub

Docker Hubでも各種ディストリビューション向けのRuby 3.0.3/2.7.5/2.6.9が利用可能になっています。

関連記事

Ruby 3.0.2/2.7.4/2.6.8セキュリティ修正がリリースされました

The post Ruby 3.0.3/2.7.5/2.6.9セキュリティ修正がリリースされました first appeared on TechRacho.

binstubをしっかり理解する: RubyGems、rbenv、bundlerの挙動(翻訳+解説)

Image may be NSFW.
Clik here to view.

こんにちは、hachi8833です。

Rails関連の記事でbinstubについてちょくちょく見かける割に、ちゃんとした理解がつい後回しになっていたので、rbenvのwikiにあるUnderstanding binstubs をこの機会にさっと訳してみました。原文の最終更新は2015年11月です。なお、訳文では原則「binstub」と単数形で表記しています。

さらに、訳文の技術チェックを行っていただいた弊社Webチーム部長のmorimorihogeさんによる解説を翻訳の後に追加いたしましたので、合わせてご覧ください。

binstubを理解する(翻訳)

概要

Mislav Marohnić氏の許諾を得て翻訳・公開します。


  • 2016/08/24: 初版公開
  • 2021/11/22: 更新

binstubとは、実行可能ファイルのラッパースクリプトです。ここで言う実行可能ファイルは「バイナリ」を指すこともありますが、コンパイルされたバイナリでなくても構いません(訳注:シェルスクリプト等も対象です)。binstubの目的は、その実行可能ファイルを呼び出す前に環境を整えることです。

Rubyでよく使われるのは、何らかの実行可能ファイルを含むgemをインストールしたときに、RubyGemsによって生成されるbinstubです。しかし、binstubの記述にはどんな言語でも使えるので、多くの開発者がしばしばbinstubを自作しています。

RubyGems

gem install rspec-coreを実行したときの動作を詳しく見てみましょう。RSpecの最終的な実行可能ファイルは、gemの中にある./exe/rspecです 。gemをインストールすると、RubyGemsによって次の実行可能ファイルが提供されます。

  • <ruby-prefix>/bin/rspec(RubyGemsが生成するbinstub)
  • <ruby-prefix>/lib/ruby/gems/1.9.1/gems/rspec-core-XX.YY/exe/rspec(本来の実行可能ファイル)

2番目の実行可能ファイルのラッパーです(訳注: このラッパーのことをbinstubと呼びます)。RubyGemsはこのbinstubをruby-prefix>/binに配置します。このディレクトリは$PATHに設定済みであることが前提です(設定はrvmやrbenvなどのRubyバージョンマネージャの仕事です)。

番目のファイル(本来の実行可能ファイル)がRubyGemsによってインストールされるディレクトリは、$PATHには含まれていません。仮に本来の実行可能ファイルが$PATHにインストールされたとしても、Rubyプロジェクト内の実行可能ファイルは、たいてい適切なセットアップ処理なしに直接実行されることを意図していないため、安全に実行できないと考えておく方がよいでしょう。少なくとも、実行可能ファイルのプロジェクトのソースファイルをrequireするために、$RUBYOPTを設定する必要があるでしょう。

こうして生成されたbinstubファイル<ruby-prefix>/bin/rspecは、次のような短いRubyスクリプトです(やや簡略化してあります)。

#!/usr/bin/env ruby
require 'rubygems'

# Prepares the $LOAD_PATH by adding to it lib directories of the gem and
# its dependencies:
gem 'rspec-core'

# Loads the original executable
load Gem.bin_path('rspec-core', 'rspec')

RubyGemsのbinstubの目的は、gemの種類を問わず、本来の実行可能ファイルを呼び出す前に$LOAD_PATHを準備することです。

rbenv

rbenv は、$PATH変数に独自の「shim」ディレクトリを追加します。Rubyに関連する全実行可能ファイルのbinstubは、このshimディレクトリの下に配置されます。rubyのbinstubコマンドやgemのbinstubコマンドはもちろん、システムにインストールされているRubyのバージョンごとに、RubyGems binstubがこのshimディレクトリに置かれます。

コマンドラインでrspecを呼び出すと、次の順に呼び出しが発生します。

  1. $RBENV_ROOT/shims/rspec(rbenv shim)
  2. $RBENV_ROOT/versions/1.9.3-pXXX/bin/rspec (RubyGems binstub)
  3. $RBENV_ROOT/versions/1.9.3-pXXX/lib/ruby/gems/1.9.1/gems/rspec-core-XX.YY/exe/rspec (本来の実行可能ファイル)

rbenvのshimファイルは、次のような短いRubyスクリプトです(やや簡略化してあります)。

#!/usr/bin/env bash
export RBENV_ROOT="$HOME/.rbenv"
exec rbenv exec "$(basename "$0")" "$@"

rbenvのshimファイルの目的は、Ruby実行可能ファイルへのすべての呼び出しがrbenv execを経由するようにし、指定したバージョンのRubyで正しく実行されるようにすることです。

プロジェクト固有のbinstub

rspecを(訳注: bundle exec <コマンド>を付けずに)プロジェクトディレクトリ内で実行すると、rbenvが適切なバージョンのRubyをプロジェクトの設定どおりに選択してくれます。しかしここで注意しないといけないのは、プロジェクト内でrspecを実行した場合、正しいバージョンのRSpecが有効になるかどうかまでは保証されないという点です。実際、システムが古いバージョンのRSpecに依存していても、RubyGemsは単に最新バージョンのRSpecを有効にします。プロジェクトの取り扱いという観点から見ると、望ましくない動作です。

この問題を解決するために、bundle exec <コマンド>が必要なのです。このコマンドを使うことで、正しい依存関係が有効になり、一貫したRuby実行環境を利用できるようになります。とは言うものの、いちいちbundle execを入力するのがだるいのも確かです。

Bundlerによって生成されるbinstub

Bundlerを使って、プロジェクトにbundleされた実行可能ファイルのbinstubをインストールできます。

bundleされたすべてのgemのbinstubを一括生成するには、次のコマンドを実行します。

bundle install --binstubs

gemをひとつ指定してbinstubを生成するには、次のコマンドを実行します(この方法をおすすめします)。

bundle binstubs rake
bundle binstubs rspec-core

プロジェクトのバージョンコントロールにあるbinstubを一度チェックしてみてください。プロジェクトの他のメンバーにとってbinstubがどのように役に立っているかがわかるでしょう。

たとえば、Bundlerがプロジェクト用に生成した./bin/rspecは次のようになります(簡略化してあります)。

#!/usr/bin/env ruby
require 'rubygems'
# Prepares the $LOAD_PATH by adding to it lib directories of all gems in the
# project's bundle:
require 'bundler/setup'
load Gem.bin_path('rspec-core', 'rspec')

これにより、プロジェクトディレクトリでbin/rspecと入力するだけでRSpecを実行できます。

※プロジェクト自体がgemである場合(つまりgemを書いている場合)、bundle install --binstubs exeのようなコマンドを実行して、bin/以外のディレクトリを使うようにする必要があります。gemのリポジトリにうっかりbin/rspecを登録したりすると、開発者がgemをインストールしたときにrspecコマンドを上書きして壊してしまいます。

プロジェクト固有のbinstubをPATHに追加する

プロジェクトのbinstubは、慣例としてプロジェクトローカルのbin/ディレクトリに配置されるので、このディレクトリへの相対パスをシェルの$PATHに追加しておけば、bin/を付けずにプロジェクト固有のrspecを実行できるようになります。

export PATH="./bin:$PATH"

ただしこのシェル設定は、共有ホストのように他のユーザーが書き込み権限を持つシステムに設定するとセキュリティ上のリスク を生じます。セキュリティを高めるために、次のようにカレントプロジェクトのbin/ディレクトリだけを$PATHに追加することができます。

export PATH="$PWD/bin:$PATH"
hash -r 2>/dev/null || true

こちらのほうがよりセキュアですが、その代わりグローバルに一度だけ設定すればよいというわけではなく、プロジェクトを切り替えるたびにこのコマンドを実行する必要があります。

direnv もご覧ください。

binstubを手動で作成する

ここまで読んでいただければ、binstubの目的はもちろんのこと、binstubが単なるスクリプトであることと、どんな言語でも記述できることもご理解いただけたと思います。それでは、プロジェクトや自分のローカル開発環境に合わせてbinstubを自作できるかどうか、検討してみましょう。

たとえば、Railsアプリケーションのコンテキストで、次のようなbinstubを手書きして./bin/unicornに置くことで、Unicornを実行できるようになります。

#!/usr/bin/env ruby
require_relative '../config/boot'
load Gem.bin_path('unicorn', 'unicorn')

bin/unicornを使うことで、アプリケーションで使うRubyのバージョンやGemfileの依存関係と完全に同じ環境でUnicornを実行できます。

これは、/<パス>/app/current/bin/unicornのように、プロジェクトディレクトリの外からbinstubを呼び出す場合でも同様です。

「Understanding binstubs」の解説(morimorihoge

エンジニア視点での翻訳内容確認を担当しましたmorimorihoge です。本記事に関するコメントです。

仕事でRails開発をしていると複数バージョンのRubyやgemを渡り歩くことが多く、binstubの仕組みを知ることは思わぬ地雷を踏まないためには有用かと思います。それなりにRails開発の経験がある人からも「コマンド実行してみたんだけどうまくいかない」といった相談を受けて、よくよく聞いてみるとbundle execの付与漏れであることがあるので、binstubまわりをきちんと理解している人は実は意外に少ないのでは?と思いました。

また、良くやられている開発者向けhackとしては、.bashrcや.zshrcあたりにalias be='bundle exec'という記述をしておくと、bundle exec railsbe railsと省略して書けて便利です。bundle execはあまりにもしょっちゅう叩くのでalias化すると良いですね。

元記事では、最後にオレオレbinstubを./bin以下に作る話が出ていますが、本番環境ではbinstubをどこに置くのか?という問題といえば、以前Capistrano の仕様変更にハマったことがありました。それまではリリースディレクトリ以下のbinはshared/binにシンボリックリンクされていたのが、特定のバージョンからシンボリックリンクではなくリポジトリのbinディレクトリを配置するのがデフォルトになっていたのでした。

また、以前には./binをgitignoreしていたプロジェクトもあったので、rails runnerがsshログインして実行しようとしても動かない問題などが現場で発生したこともありましたね。

さらに、Rails 4.1以降ではコマンド実行にSpringも絡んでくるので、もしbinstub周りでおかしいことがあった場合にはSpring周りも原因として疑ってみる方が何とかできる可能性が高まると思います。

関連記事

chefからansibleに乗り換えた5つの理由

The post binstubをしっかり理解する: RubyGems、rbenv、bundlerの挙動(翻訳+解説) first appeared on TechRacho.


週刊Railsウォッチ: フォームヘルパーの改修、Railsの監査ログgem比較、DHHとimport-mapほか(20211129前編)

Image may be NSFW.
Clik here to view.

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

Image may be NSFW.
Clik here to view.
🔗
Rails: 先週の改修(Rails公式ニュースより)

以下の公式情報から見繕いました。

Image may be NSFW.
Clik here to view.
🔗
フォームヘルパーでurl: falseaction: falseオプションが指定可能になった

以下を用いて<form>要素のレンダリングをaction属性なしで行えるようになった。

  • form_with url: falseまたはform_with ..., html: { action: false }
  • form_for ..., url: falseまたはform_for ..., html: { action: false }
  • form_tag falseまたはform_tag ..., action: false
  • button_to "...", falseまたはbutton_to(false) { ... }

Sean Doyle
同Changelogより


つっつきボイス:「久しぶりにform_*系ヘルパーに改修が入ったようです」「この改修ではRailsのフォームヘルパーでurl: falseaction: falseを明示的に指定することでアクションなしを指定できるようになったんですね: 現在のRailsのデフォルトではaction属性が必ず付与されるようになっていますが、HTMLの仕様ではaction属性なしでフォームを生成すると現在のURLが生成されて自分自身へのアクションを参照するようになっています」「フォームを自分自身に投げるならaction属性を書かなくてもいいんですね」

参考: HTML Standard — 4.10.21.3 Form submission algorithm html.spec.whatwg.org

  1. If action is the empty string, let action be the URL of the form document.
    html.spec.whatwg.orgより

「今までは以下のように<form method="get">をアクションなしで作りたくてもform_withform_forなどではデフォルトでurl_for({})が使われるので作る方法がなかったということみたい」「たしかに、今まではform_withform_forを使うと必ずaction="/postsみたいなアクションが<form>タグに入ってたので、アクションなしのフォームもありとは知りませんでした」「この機能を自分で使う状況はあまり思いつかないけど、HTMLの仕様で許されていることができないとRailsの自由度が下がってしまうことになるので、できる方がいいでしょうねImage may be NSFW.
Clik here to view.
👍

<form method="get">
  <button name="sort" value="desc">Most to least</button>
  <button name="sort" value="asc">Least to most</button>
</form>

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

Image may be NSFW.
Clik here to view.
🔗
button_toauthenticity_token:オプションをサポート

form_withform_forの呼び出しでauthenticity_token:オプションを渡せるようになった。

button_to "Create", Post.new, authenticity_token: false
  # => <form class="button_to" method="post" action="/posts"><button type="submit">Create</button></form>

button_to "Create", Post.new, authenticity_token: true
  # => <form class="button_to" method="post" action="/posts"><button type="submit">Create</button><input type="hidden" name="form_token" value="abc123..." autocomplete="off" /></form>

button_to "Create", Post.new, authenticity_token: "secret"
  # => <form class="button_to" method="post" action="/posts"><button type="submit">Create</button><input type="hidden" name="form_token" value="secret" autocomplete="off" /></form>

同PRより


つっつきボイス:「authenticity_token:オプションでtruefalseを渡したり、任意の文字列を渡したりできるようになった」「trueや文字列の場合は<input type="hidden">の中にトークンが埋められるんですね」「このオプションがbutton_toヘルパーで使えるようになったので、フォームの中に含まれていない単体のボタンでもトークンが使えるようになる: これを使いたくなる気持ちはちょっとわかるかも」

Image may be NSFW.
Clik here to view.
🔗
field_nameビューヘルパーが追加

field_nameビューヘルパーを導入する。これはFormBuilder#field_nameに相当する。

form_for @post do |f|
  f.field_tag :tag, name: f.field_name(:tag, multiple: true)
  # => <input type="text" name="post[tag][]">
end

Sean Doyle
同PRより


つっつきボイス:「今回はフォーム周りの改修が多いですね」「お〜、フォームのフィールドでネステッドな連想配列形式のフィールド名を指定する公式の方法がついにできたImage may be NSFW.
Clik here to view.
🎉
(シンボル渡しだとtag[hoge][]のようにできなかった)」「しかもmultiple: trueにも対応しているのが偉い!」

# 同PRより
text_field_tag :post, :title, name: field_name(:post, :title, :subtitle)
  # => <input type="text" name="post[title][subtitle]">

text_field_tag :post, :tag, name: field_name(:post, :tag, multiple: true)
  # => <input type="text" name="post[tag][]">

form_for @post do |f|
  f.field_tag :tag, name: f.field_name(:tag, multiple: true)
  # => <input type="text" name="post[tag][]">
end

「今までは以下のような書き方↓しかなかったのがイケてないな〜と思いながら使っていましたけど、改修後はようやくname: field_name(:post, :title, :subtitle)のようなRubyらしい方法でフィールド名を指定できるようになった」「今回のフォーム周りのプルリクを投げてくれたSean Doyleさんは自分たちがビューで欲しいものをわかってくれている感じで嬉しいです」「覚えてたら使おうっと」

# api.rubyonrails.orgより
text_field_tag 'name'
# => <input id="name" name="name" type="text" />

text_field_tag 'query', 'Enter your search query here'
# => <input id="query" name="query" type="text" value="Enter your search query here" />

text_field_tag 'search', nil, placeholder: 'Enter search term...'
# => <input id="search" name="search" placeholder="Enter search term..." type="text" />

text_field_tag 'request', nil, class: 'special_input'
# => <input class="special_input" id="request" name="request" type="text" />

text_field_tag 'address', '', size: 75
# => <input id="address" name="address" size="75" type="text" value="" />

text_field_tag 'zip', nil, maxlength: 5
# => <input id="zip" maxlength="5" name="zip" type="text" />

text_field_tag 'payment_amount', '$0.00', disabled: true
# => <input disabled="disabled" id="payment_amount" name="payment_amount" type="text" value="$0.00" />

text_field_tag 'ip', '0.0.0.0', maxlength: 15, size: 20, class: "ip-input"
# => <input class="ip-input" id="ip" maxlength="15" name="ip" size="20" type="text" value="0.0.0.0" />

Image may be NSFW.
Clik here to view.
🔗
Rails標準のエラーレポートインターフェイスを追加

修正: #43472

このレポーターはExecutorにあるが、このRailsモジュールはもっと便利なRails.errorというショートカットを提供している。

使いやすさのため、2個のブロックをベースとする専用メソッドを公開している。

handleは、エラーを飲み込んでサブスクライバに転送する。

Rails.error.handle do
  1 + '1' # raises TypeError
end
1 + 1 # これは実行される

recordは、エラーをサブスクライバに転送するが、コールスタックを巻き戻して継続させる。

Rails.error.record do
  1 + '1' # raises TypeError
end
1 + 1 # ここは実行されない

ブロックベースのAPIに合わない場合は、低レベルのreportメソッドを使える。

Rails.error.report(error, handled: true / false)

インターフェース
このプルリクではRails.errorのみを導入したが、後で「ローカル」エラーレポーターを導入してctive SupportやActive Recordなどのgemでもエラーをレポートできるようにしたい。現在のRails.loggerと少し似た感じで動くだろう。

例: ActiveSupport.errorはデフォルトではエラーをログ出力するだけのエラーレポーターだが、RailtieでこれをRails.errorに置き換える。


つっつきボイス:「Rails.errorというショートハンドでエラーレポートできるようにしたということのようですね」「ActiveSupport:: ErrorReporterが追加されてる」

「考えてみればログ出力とエラーレポートは別に指定できる方がいいですね: 今までは自分でraiseしてrescue_fromするとか、Slack通知などを各自が実装しますけど、こういうふうにRails公式のインターフェイスでエラーレポートを使えると便利でしょうね」「Rails.errorだとraiseとかを書かなくてよくなりそうですね」「そうそう」

「このActiveSupport::ErrorReporterと機能が近いのはActiveSupport::Instrumentationですが、前者はエラーレポートに特化したインターフェースを持ち、後者は汎用的なpub/subで、subscriberがいなければスルーされるという点で使い分けが想定されてる感じかな」

参考: Active Support の Instrumentation 機能 - Railsガイド

「このインターフェイスを使って、airbrakeやsentryやrollbarといったエラーレポートツールも今後共通化できそうかなと思いました: それぞれの*-reporterみたいなgemやSlack-reporterライブラリなどをリリースしたりして」「そうなるといいですね」

Image may be NSFW.
Clik here to view.
airbrake/airbrake-ruby - GitHub

Image may be NSFW.
Clik here to view.
getsentry/sentry-ruby - GitHub

Image may be NSFW.
Clik here to view.
rollbar/rollbar-gem - GitHub

Image may be NSFW.
Clik here to view.
🔗
jbuilderのコレクションレンダリングが高速化


つっつきボイス:「おぉ、コレクションのレンダリングが高速化されるのは嬉しいImage may be NSFW.
Clik here to view.
🎉
」「cached: trueをオンにするとキャッシュが効くのね」「アプリケーションの性質次第では速くなりそう」

「でもこれよく見たらjbuilder gemの改修ですね」「jbuilder使ってない…」「私も…」

Image may be NSFW.
Clik here to view.
🔗
jb gemは優秀

「自分は最近jbuilderの代わりにこのjbというgemを使ってます↓」「amatsudaさんのgemだ」

Image may be NSFW.
Clik here to view.
amatsuda/jb - GitHub

# amatsuda/jbより
# app/views/messages/show.json.jb

json = {
  content: format_content(@message.content),
  created_at: @message.created_at,
  updated_at: @message.updated_at,
  author: {
    name: @message.creator.name.familiar,
    email_address: @message.creator.email_address_with_name,
    url: url_for(@message.creator, format: :json)
  }
}

if current_user.admin?
  json[:visitors] = calculate_visitors(@message)
end

json[:comments] = @message.comments.map do |comment|
  {
    content: comment.content,
    created_at: comment.created_at
  }
end

json[:attachments] = @message.attachments.map do |attachment|
  {
    filename: attachment.filename,
    url: url_for(attachment)
  }
end

json

「jbだとRubyっぽく書けるのが嬉しいんですよImage may be NSFW.
Clik here to view.
😂
」「わかります、jbuilderの書き方はDSL的ですよね」「それそれ、jbuilderで思ったとおりのJSONやXMLを出力しようと思ったらDSLの書き方を覚えないといけないんですよ」「Response.jsonとかxmlを組み立てていて細部の挙動が思うようにならないと、ERBで書く方がましという気持ちになったりしますよね」

参考: Response.json\() - Web API | MDN

「jbだとRubyで書いたとおりにJSONが出力されるのでホント楽」「ちょっと大きいけど、active_model_serializers gemも比較的そういう感じで書けるところが好き」「キャッシュみたいなものはDSLに任せたいけど、JSONみたいなものはRubyらしいシンプルな方法でビルドしたい気持ちです」

Image may be NSFW.
Clik here to view.
rails-api/active_model_serializers - GitHub

Image may be NSFW.
Clik here to view.
🔗
Rails

Image may be NSFW.
Clik here to view.
🔗
rails_multisite: Discourseから切り出されたマルチテナントgem(Ruby Weeklyより)

Image may be NSFW.
Clik here to view.
discourse/rails_multisite - GitHub


つっつきボイス:「マルチテナント系のgemはいろいろありますけど、これはDBも含めて完全に切り分けるマルチサイトがやれるライブラリのようですね」

# 同リポジトリより
mlp:
  adapter: postgresql
  database: discourse_mlp
  username: discourse_mlp
  password: applejack
  host: dbhost
  pool: 5
  timeout: 5000
  host_names:
    - discourse.equestria.com
    - discourse.equestria.internal

drwho:
  adapter: postgresql
  database: discourse_who
  username: discourse_who
  password: "Up the time stream without a TARDIS"
  host: dbhost
  pool: 5
  timeout: 5000
  host_names:
    - discuss.tardis.gallifrey

「そこまで分けるなら別アプリにするかなとも思いますが、Discourseでそういう需要があるのは何となく想像できる」「いずれ別アプリに分けたいというリクエストが来るかもしれませんね」「レンタルサーバーでよくある共有サーバープランから専用サーバープランに移行するような感じでアプリを切り離せるんじゃないかな」

Image may be NSFW.
Clik here to view.
🔗
Railsの監査ログgemを比較する(RubyFlowより)


つっつきボイス:「監査ログ機能のgemを比較する記事だそうです」「知っているのや知らないのや、いろんなのがありますね」「記事の末尾で使い分けが書かれていました」「自分はpaper_trailを使ってます」「監査ログ機能は常に何らかの形で求められる機能なので、こういうふうに定期的にまとめてくれるのはいいですねImage may be NSFW.
Clik here to view.
👍

Image may be NSFW.
Clik here to view.
paper-trail-gem/paper_trail - GitHub

Image may be NSFW.
Clik here to view.
collectiveidea/audited - GitHub

Image may be NSFW.
Clik here to view.
chaps-io/public_activity - GitHub

Image may be NSFW.
Clik here to view.
palkan/logidze - GitHub

Image may be NSFW.
Clik here to view.
rails-engine/audit-log - GitHub

Image may be NSFW.
Clik here to view.
🔗
ログの置き場所

「ただ、監査ログについてはなるべくCloudWatch↓のような外部サービスにおまかせしたい気持ちがあります」「たしかに」

参考: Amazon CloudWatch(リソースとアプリケーションの監視と管理)| AWS

「後でログを調べたりするだけならSQLクエリでさっと取り出せるのは一見便利ですが、アプリのデータベースに監査ログを置くとものすごく量が増える可能性があるのと、作業者がセンシティブなデータを不用意にSELECT *してしまう可能性があるんですよ」「そうそう、データベースのインスタンスは別にしておきたいです」「直近のログだけならまだしも、少なくとも永続化するログを同じデータベースに置くのは避けたい」「古くなったログをexpireするのも忘れないようにしないと」

「ちなみにPostgreSQLやBigTableだと自動的にテーブルをパーティショニングするオプションがありますけどね」「MySQL派ですけど、ぽすぐれにそんな機能もあるんですか」「ググってみるとMySQLにもありますね」

参考: PostgreSQLドキュメント 5.11. テーブルのパーティショニング
参考: BigTable - Wikipedia
参考: MySQL :: MySQL 5.6 リファレンスマニュアル :: 19.6 パーティショニングの制約と制限

「なおlogidzeは翻訳記事↓でも取り上げたgemですが、ウクライナ語なので読み方をいつも忘れてしまいますImage may be NSFW.
Clik here to view.
😅

Rails: Logidze gemでActive Record背後のPostgreSQL DB更新をトラッキング(翻訳)

Image may be NSFW.
Clik here to view.
🔗
Dry-monadsで「Railway指向プログラミング」設計(RubyFlowより)


つっつきボイス:「以前取り上げたRailway指向プログラミング(ROP)の図↓がこの記事に出てきたので拾ってみました(ウォッチ20200302)」

Image may be NSFW.
Clik here to view.

Image may be NSFW.
Clik here to view.

同記事より

「コードを見た感じでは特に変わったことをしているわけではなさそうかな↓」

# 同記事より
def deliver_car(year, model, color, city)
  yield check_year(year)
  yield check_model(model)
  yield check_city(city)
  yield check_color(color)

  Success("A #{color} #{year} Toyota #{model} will be delivered to #{city}")
end

def check_year(year)
  year < 2000 ? Failure("We have no cars manufactured in year #{year}") : Success('Cars of this year are available')
end

def check_model(model)
  @available_models.include?(model) ? Success('Model available') : Failure('The model requested is unavailable')
end
def check_color(color)
  @available_colors.include?(color) ? Success('This color is available') : Failure("Color #{color} is unavailable")
end

def check_city(city)
  @nearby_cities.include?(city) ? Success("Car deliverable to #{city}") : Failure('Apologies, we cannot deliver to this city')
end

「Railway指向とは?」「まさに上の図のようにステップごとにポイント切り替え的に処理を進めるという考え方ですね: ここでは失敗部分の処理を共通化するのに使っているように見える」「おぉ?」「よくあるオブジェクト指向的な設計だと、失敗ごとに別々の例外を投げることでエラーオブジェクトも別々になったりしますけど、この記事では失敗時はすべてFailureに流れる、つまり図で言うと赤い線路に流れることで共通化するということなんでしょうね」「あ、そういうことですか」

「この場合失敗の理由に応じたエラーオブジェクトは取れなくなりますが、処理はシンプルになりますね: 分岐がたくさんあって、失敗の理由に興味がない場合は、こういう設計にすることもあるでしょうね」「なるほど」「あくまで設計思想のひとつです」

「この記事はdry-monadsを使ってモナドっぽく書いているらしい」「dry-rbシリーズは地味にラインナップが増えていますね」「dry-rbシリーズはなかなかいいライブラリなので、この調子で広まって今後公式にも反映されたりしたらいいですよね」

Image may be NSFW.
Clik here to view.
dry-rb/dry-monads - GitHub

Image may be NSFW.
Clik here to view.
🔗
エンジニア3年目の人に向けて


つっつきボイス:「koicさんの記事にもあるように、Railsエンジニア3年目ぐらいの人を対象にした題材で銀座Rails#39に登壇いただきました: とてもいい話Image may be NSFW.
Clik here to view.
❤
」「お〜、スライドを見た感じでもよき話なのが伝わってきます」「最初の頃はタスクを拾うだけで一杯になりますけど、3年目ぐらいになってくるともっといい設計にすることに目が向くようになりますよね」

「koicさんの話を聞いていて、こういう話は時代が移っても比較的変わりにくいなと思いましたね」「たしかに10年前もこういうことしていたなという感じはありますね」「技術の移り変わりは激しいけど、変わりにくい部分はやはりあると改めて実感します」

Image may be NSFW.
Clik here to view.
🔗
DHHの『JavaScriptのバンドルとトランスパイルが不要なモダンWebアプリ』

つっつきボイス:「いい記事Image may be NSFW.
Clik here to view.
👍
」「ruby-jp Slackで『DHHが日本語で記事書いたのかと思った』と話題になってました」「ないないImage may be NSFW.
Clik here to view.
😆

「DHHがimport-mapの普及に力を入れているのを見ていると、import-mapがすべてのブラウザで使えるようになって、Rails以外のところでもフロントエンジニアがimport-mapを使いまくる世界を目指しているのかなと思いましたね」「あ、そうかも」

「caniuse.comで言うと、一日も早くすべてのブラウザでimport-mapが緑色になって欲しいと願っているんじゃないかなと思っています↓」「なるほど、今はChromeとEdgeあたりは緑か」「今のEdgeのレンダリングエンジンはChromeと同じなので緑なのは当然ですが」「FirefoxとSafari、頑張ってくれ〜」

Image may be NSFW.
Clik here to view.
Data on support for the import-maps feature across the major browsers from caniuse.com

参考: Import maps | Can I use... Support tables for HTML5, CSS3, etc

「DHHの記事によるとes-modules-shims↓を使えば今のFirefoxやSafariでもimport-mapのほとんどの機能を使えるらしい」

Image may be NSFW.
Clik here to view.
guybedford/es-module-shims - GitHub

「FirefoxとSafariでもimport-mapが使えるようになったらproductionで安心して使えますよね」「たぶんそれがDHHの目指す世界だと思っています」

Rails 7: import-map-rails gem README(翻訳)


前編は以上です。

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

週刊Railsウォッチ: Ruby Struct入門、書籍『進化的アーキテクチャ』、AWS Web問題集ほか(20211116後編)

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

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

Rails公式ニュース

Image may be NSFW.
Clik here to view.

Ruby Weekly

Image may be NSFW.
Clik here to view.

RubyFlow

Image may be NSFW.
Clik here to view.
160928_1638_XvIP4h

The post 週刊Railsウォッチ: フォームヘルパーの改修、Railsの監査ログgem比較、DHHとimport-mapほか(20211129前編) first appeared on TechRacho.

Rails: Value Objectで「基本データ型への執着」と戦う(翻訳)

Image may be NSFW.
Clik here to view.

概要

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

週刊Railsウォッチ20210510『Value Objectをクラスで定義してプリミティブな値と戦う』もどうぞ。

参考: Primitive-obsession(基本データ型への執着) - Qiita

Rails: Value Objectで「基本データ型への執着」と戦う(翻訳)

前回のRead Modelの記事では別のお題を取り上げましたが、今回はRead Modelそのものに焦点を当てることにし、それ以外については今後の別記事に回したいと思います。ただ、この実装で1つ気に入らない点は、スコアの集計にプリミティブ型(基本データ型)を使っていることです。

Projection

def calculate_scores(test_id, participant_id)
  RailsEventStore::Projection
    .from_stream(stream_name(test_id, participant_id))
    .init(-> { Hash.new { |scores, skill_id| scores[skill_id] = { score: 0, number_of_scores: 0 } })
    .when(
      SurveyExecution::AnswerRegistered,
      ->(state, event) do
        skill_id = event.data.fetch(:skill_id)
        state[skill_id][:score] += event.data.fetch(:score)
        state[skill_id][:number_of_scores] += 1
      end
    )
    .run(Rails.configuration.event_store)
    .reduce({}) do |scores, (skill_id, values)|
      scores[skill_id] = values[:score] / values[:n]
      scores
    end
end

指定のスキル範囲でスコアを集計することで、平均値などをカウントできるようになります。ご想像のとおり、このコード例は簡略化したものであり、オリジナルのコードはもっと複雑です。

よりよい方法はある

もっとマシな方法はないものでしょうか?そう、Value Objectを導入することです。コードを見ていく前に、Value Objectの正しい定義を固めておく必要があります。私はEric Evansが『Domain-Driven Design: Tackling Complexity in the Heart of Software』で述べているValue Objectの特徴が気に入っています。

  • ドメイン内にあるものを測定、定量化、または記述する。
  • イミュータブルのまま維持可能。
  • 互いに関連する属性をひとつにまとめることで概念上の全体をモデリングする。
  • 測定や記述が変更された場合に完全に入れ替え可能。
  • 「値の等価性」によって他と比較可能
  • 「副作用のない振る舞い」を持つ、自身のコラボレーターを提供する

Value Objectの最もよくある例は、PriceMonetaryValueBigDecimal型と通貨を表すString型の組み合わせ)でしょう。それでは以下のように少し変えてみましょう。

class AnswerScore
  def initialize(skill_id, score)
    @skill_id = skill_id
    @score = BigDecimal(score.to_s)
  end

  attr_reader :skill_id, :score

  def ==(other)
    other.class === self &&
      other.hash == hash
  end

  alias eql? ==

  def hash
    [skill_id, score].join.hash
  end
end

これで、2つのAnswerScoreの違いを独自の==eql?hashメソッドで値ごとに比較可能になりました。

irb(main):069:0> AnswerScore.new(123, 0) == AnswerScore.new(123, 0)
#=> true
irb(main):070:0> AnswerScore.new(123, 0) == AnswerScore.new(123, 1)
#=> false
irb(main):071:0> AnswerScore.new(123, 0) == BigDecimal("0")
#=> false
irb(main):072:0> AnswerScore.new(123, 0) == AnswerScore.new(456, 0)
#=> false

eql?演算子は==のエイリアスになっているので、同じ結果を得られます。

Value Objectを2つ追加する

なるほど、2つのオブジェクトは比較できますが、次はどうすればよいのでしょうか?ここにはidもありますが、これをEntityにするのはよくないでしょうか?そのとおり、Entityにすべきではありません。このidは、さまざまなスキルのスコアを区別するのに用いるものです。スキルが異なるスコア同士を足してもあまり意味がありませんよね?ドルやポンドといった通貨を区別せずに足したらどうなるかを考えればおわかりでしょう。

次はこのオブジェクトに+演算子を実装しましょう。

class AnswerScore
  def initialize(skill_id, score)
    @skill_id = skill_id
    @score = BigDecimal(score.to_s)
  end

  attr_reader :skill_id, :score

  def +(other)
    raise ArgumentError unless self.class === other
    raise ArgumentError if self.skill_id != other.skill_id

    score + other.score
  end

  def ==(other)
    other.class === self &&
      other.hash == hash
  end

  alias eql? ==

  def hash
    [skill_id, score].join.hash
  end
end

これで、スコアに間違ったものを足すことは不可能になりました。

# 同じスキルで異なるスコアを足す場合
irb(main):123:0> AnswerScore.new(123, 0) + AnswerScore.new(123, 1)
=> 0.1e1

# オブジェクトが異なる場合
irb(main):124:0> AnswerScore.new(123, 0) + 5
Traceback (most recent call last):
        5: from /Users/fidel/.rbenv/versions/2.7.3/bin/irb:23:in `<main>'
        4: from /Users/fidel/.rbenv/versions/2.7.3/bin/irb:23:in `load'
        3: from /Users/fidel/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        2: from (irb):124
        1: from (irb):107:in `+'
ArgumentError (ArgumentError)

# 異なるスキルのスコアを足す場合
irb(main):126:0> AnswerScore.new(123, 0) + AnswerScore.new(456, 1)
Traceback (most recent call last):
        5: from /Users/fidel/.rbenv/versions/2.7.3/bin/irb:23:in `<main>'
        4: from /Users/fidel/.rbenv/versions/2.7.3/bin/irb:23:in `load'
        3: from /Users/fidel/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        2: from (irb):124
        1: from (irb):107:in `+'
ArgumentError (ArgumentError)

うまくいきました。しかし返されるのがBigDecimalではうれしくないので、AnswerScoreオブジェクトをもっと足してProjectionを整頓し、シンプルにしたいと思います。

def calculate_scores(test_id, participant_id)
  RailsEventStore::Projection
    .from_stream(stream_name(test_id, participant_id))
    .init(-> { NullScore.new( })
    .when(
      SurveyExecution::AnswerRegistered,
      ->(state, event) do
        state += AnswerScore.new(
          skill_id: event.data.fetch(:skill_id),
          score: event.data.fetch(:score)
        )
      end
    )
    .run(Rails.configuration.event_store)
    .reduce(&:+)
    .average_score
end

この時点ではNullScoreがないので動きません。NullScoreを実装しましょう。

class NullScore
   def +(other)
    raise ArgumentError unless AnswerScore === other

    other
  end

  def ==(other)
    NullScore === other
  end

  alias eql? ==

  def hash
    'NullScore'.hash
  end
end

NillScoreを追加すると、本物のValue Objectが初めて返るようになりました。Projectionでこの振る舞いを得るためには、AnswerScoreの内部をハックするよりもここを出発点とする方が優れています。

イミュータブルにする

話をAnswerScoreに戻します。AnswerScoreから返して欲しいのは、生のBigDecimal値ではなくValue Objectです。2つのスコアを足したものはもうスコアとは呼べないので、おそらくScoreSumを返すべきです。

class AnswerScore
  def initialize(skill_id, score)
    @skill_id = skill_id
    @score = BigDecimal(score.to_s)
  end

  attr_reader :skill_id, :score

  def +(other)
    raise ArgumentError unless self.class === other
    raise ArgumentError if self.skill_id != other.skill_id

    ScoreSum.new(skill_id: skill_id, sum: score + other.score, n: 2)
  end

  def average_score
    score.round(2)
  end

  def ==(other)
    other.class === self &&
      other.hash == hash
  end

  alias eql? ==

  def hash
    [skill_id, score].join.hash
  end
end

class ScoreSum
  def initialize(skill_id:, sum:, n:)
    @skill_id = skill_id
    @sum = BigDecimal(sum.to_s)
    @n = Integer(n)
  end

  attr_reader :skill_id, :sum, :n

  def +(other)
    raise ArgumentError unless AnswerScore === other
    raise ArgumentError if self.skill_id != other.skill_id

    ScoreSum.new(sum: sum + other.score, skill_id: skill_id, n: n+1)
  end

  def average_score
    (score / n).round(2)
  end

  def ==(other)
    other.class === self &&
      other.hash == hash
  end

  alias eql? ==

  def hash
    [skill_id, sum, n].join.hash
  end
end

動作を確かめてみましょう。

irb(main):254:0> AnswerScore.new(123, 0) + AnswerScore.new(123, 1)
#=> #<ScoreSum:0x00000001137b3770 @skill_id=123, @sum=0.1e1, @n=2>
irb(main):255:0> AnswerScore.new(123, 0) + AnswerScore.new(123, 1) + AnswerScor
e.new(123, 1)
#=> #<ScoreSum:0x0000000112030a30 @skill_id=123, @sum=0.2e1, @n=3>
irb(main):256:0> [AnswerScore.new(123, 0), AnswerScore.new(123, 1), AnswerScore
.new(123, 1)].reduce(&:+)
#=> #<ScoreSum:0x00000001137a8938 @skill_id=123, @sum=0.2e1, @n=3>

これで以下の結果が得られました。

  • オブジェクトがイミュータブルになり、何らかの操作を行うたびに新しいオブジェクトが返されるようになった
  • 概念がくっきりと示されるようになった
  • AnswerScoreScoreSumに固有の振る舞いをもたせられるようになった: たとえばスコアのaverage_scoreは単なるスコアのままですが、スコアのScoreSumは合計を要素の個数で割ったものになります。

残念なお知らせ

私たちのProjectionが機能しなくなりました。理由は、弊社のRails Event Storeフレームワークの現在の実装ではできないからです。初期の実装では、ステートをHashで保存し、その同じインスタンスを改変していたので動いたのですImage may be NSFW.
Clik here to view.
😱

しかし光明が見えた

WeDontDoThatHere = Class.new(StandardError)

def calculate_scores(test_id, participant_id)
  Rails
    .configuration
    .event_store
    .read
    .stream(stream_name(test_id, participant_id))
    .map do |event|
      case event.event_type
      when 'SurveyExecution::AnswerRegistered'
        AnswerScore.new(
          skill_id: event.data.fetch(:skill_id),
          score: event.data.fetch(:score)
        )
      else
        raise WeDontDoThatHere
      end
  end
  .reduce(&:+)
  .average_score
end

やっていることは先ほどと同じですが、今度はマジックも少な目になりました、少なくとも私の目には。それとともにNullScoreは無用の長物となり、mapreduceを使うようになりましたとさ。

お知らせ

ARKADEMY.DEVに参加してArkencyのトップクラス教育プログラムコースにアクセスしましょう!「Railsアーキテクトマスタークラス」「アンチ”IF”コース」「忙しいプログラマーのためのブログ執筆コース」「Async Remoteコース」「TDD動画クラス」「ドメイン駆動Rails動画コース」以外にもさまざまなコースが新設中です。

関連記事

Rails: Value Objectを検討してみよう(翻訳)

The post Rails: Value Objectで「基本データ型への執着」と戦う(翻訳) first appeared on TechRacho.

週刊Railsウォッチ: Railsで「Read Model」を使う、Ruby Prize 2021受賞者決定、pru gemほか(20211201後編)

Image may be NSFW.
Clik here to view.

こんにちは、hachi8833です。TechRachoのアドベントカレンダー2021が始まる季節になりました。

週刊Railsウォッチについて

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

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

Image may be NSFW.
Clik here to view.
🔗
Rails

Image may be NSFW.
Clik here to view.
🔗
Read Model


つっつきボイス:「TechRacho翻訳記事でお世話になっているArkencyさんの記事です」「ざっと見たところ、複雑なJOINをするようなデータを毎回Active Recordで取り出したりする代わりに、よく使うデータをRead Modelというもので共通化している感じかな」「広義のFacadeパターンのような趣ですね: 自分はこういうふうに複雑なものをうまく共通化するのは割と好きImage may be NSFW.
Clik here to view.
❤
」「そうそう、よく使うクエリ結果を取り出すFacadeみたいな感じですね」「それにRead Modelという呼び方を与えたということでしょうね」

# 同記事より
ApplicationRecord.transaction do
  booking = Booking.create!(params)
  CalendarReadModel.handle_booking_created(booking)
end

「ちなみに自分はそういうときにデータベースVIEWを使いたい方です」「例の記事で推している方法ですね↓」

RDBMSのVIEWを使ってRailsのデータアクセスをいい感じにする【銀座Rails#10】

「上のRead Modelみたいなもので仮にActiveRecord::Relationを不用意に返してしまうと、他の開発者がすぐそこにスコープを追加し始めて壊れたりするんですよ」「そうそう、とてもありがちですよね」

ActiveRecord::Relationを返す共通化モデルの中でJOIN済みだったものを、知らずにJOINしてしまって結果がおかしくなるというのはありがちな事故」「そうそう、そもそもJOINしなくていいように作ったはずのものだったのに、それに気が付かずにJOINされてしまうという」

「書いた自分すら後でそういうことをやりかねないので、安全と安心のためにもデータベースVIEWを作るのがいいよと各所で普及に努めてます: データベースVIEWにしておけば、たとえばunscopeされても問題が生じなくなるのが嬉しい」「ですよね」

参考: unscopeActiveRecord::QueryMethods

「ORDER BYとかWHEREで絞り込むぐらいはRead Model的なもので共通化してもいいと思うんですけど、そのモデルで何をやってよくて何をやってはいけないかを厳密に定義するのは結構悩ましい問題ではある」「そういうのを考えるとデータベースVIEWにするのがいいという結論に落ち着きそうかな」

「記事のRead Modelは、ActiveRecord::Relationを返さないようにすれば何も気にしないで使ってもよいものですねImage may be NSFW.
Clik here to view.
👍
: その代わり返す結果が巨大だとメモリを大量に消費しますが」「そうそう」「もっともCQRS(コマンドクエリ責務分離)↓の文脈ではActiveRecord::Relationを返すことはしなさそうですけどね」

Railsドメイン設計: イベントソーシングでCQRS Read Modelが基本的に必要な理由(翻訳)

Image may be NSFW.
Clik here to view.
🔗
HanamiとRailsの違いがわかる図


つっつきボイス:「久しぶりのHanami記事ですね」「Hanamiの公式ブログ記事にあったRailsとHanamiのアーキテクチャ比較図がよかったので取り上げてみました」「なるほど、RailsでRoutesから直接コントローラのアクションを呼ぶとか、そこからビューやヘルパーを呼び出すとかはおなじみの部分」「HanamiはモデルのところがPersistanceになってる」「こういう比較の図があるといいですね: Railsを知るにもよさそうImage may be NSFW.
Clik here to view.
👍

Image may be NSFW.
Clik here to view.

Image may be NSFW.
Clik here to view.

Image may be NSFW.
Clik here to view.

Image may be NSFW.
Clik here to view.

同記事より

「以前Hanamiを少しやってみたことがあるんですが、RailsだったらこうなのにHanamiは何でこうなってないんだろうみたいなところで悩みました」「もう自分たちはすっかりRailsに慣れているから最初はそうなるでしょうね」「当時こういう図があったらな〜」「自分たちがRailsという圧倒的に複雑なフレームワークに慣れてきた分、よりシンプルなHanamiを理解するのに時間がかかるというのはありそう」「それはあるかもです」

Image may be NSFW.
Clik here to view.
🔗
Rails Tutorial(英語版)の更新


つっつきボイス:「この間自分が英語版Rails Tutorial(以下Rails Tutorial)の更新を確認したときに、N+1クエリ問題とsessino replay攻撃とsession fixation攻撃への対応がRails Tutorialに反映されたことを知りました: 上はRails Tutorialの更新部分から参照されていた2本の記事です」「お〜なるほど」「それぞれの更新に関連する演習問題もいくつか追加されていました」「よく大学の教科書で『第x章のy節にあるzを修正せよ』というような解答なしの演習問題が付いていたりしますけど、ちょうどそんな感じですね」

なお、上記2記事の中でもRails Tutorialが対応済みであることについて言及しています。

「Railsを学ぶ人はまずRailsチュートリアルを最後まで走り抜けて一度ちゃんと動かすことが大事ですね: N+1クエリ問題はその後で学んでも大丈夫Image may be NSFW.
Clik here to view.
👍
」「そうですね」

「2本目の記事はsession replay攻撃に関するもので、Rails Tutorialでもログインとログアウトの前後にsession_resetを追加するように更新されていました」「そうそう、基本的な処置ですね: session_resetを追加するのはRailsガイドにも載っています↓」

参考: Rails セキュリティガイド - Railsガイド

上述の変更内容は今後Railsチュートリアルにも反映される予定です。

「Railsチュートリアルは教育目的なのであえて認証機能を手作りしていますけど、最初からDeviseを使うとマジックが増えて初学者につらくなってしまうからでしょうね」「チュートリアルでいきなりDevise使ったらマジックオブマジックになっちゃいそうImage may be NSFW.
Clik here to view.
😆
」「ちなみにRailsチュートリアルの本文では認証ライブラリとしてDeviseを推しています」

Image may be NSFW.
Clik here to view.
heartcombo/devise - GitHub

Image may be NSFW.
Clik here to view.
🔗
Ruby

Image may be NSFW.
Clik here to view.
🔗
Ruby 3.1のMatchData\#matchMatchData\#match_length


つっつきボイス:「なるほど、MatchDataオブジェクトのmatchでキャプチャグループを取り出したりmatch_lengthでキャプチャ文字列の長さが取れるようになったのね」「Ruby 3.1は新機能を控えめにするということですが、こういう機能も入るんですね」

# 同記事より
result = /\$(?<dollars>\d+)\.(?<cents>\d+)/.match("$1.95")
#=> #<MatchData "$1.95" dollars:"1" cents:"95">

result.match(0)
#=> "$1.95"

result.match(1)
#=> "1"

result.match(:dollars)
#=> "1"

result.match(:cents)
#=> "95"

result.match_length(:cents)
#=> 2

Image may be NSFW.
Clik here to view.
🔗
Primitive.mandatory_only?でRuby組み込みメソッドを高速化


つっつきボイス:「ruby-jp Slackでたまたま見かけたんですが、Rubyの組み込みメソッドで上のような改修をやってくれる方を気長に募集しているそうです」「どれどれ、RubyのPrimitive.hogeなどを使っている組み込みメソッドで、キーワード引数のすべてにデフォルト値がある場合に、そのメソッドがキーワード引数なしで呼ばれると少し遅くなる、へ〜」「iSeqレベルの話なので細かそう」「以下のようにPrimitive.mandatory_only?を使って変更することで、キーワード引数なしで呼び出しても遅くならないようにできるのね: 小さな改修でも呼ばれる回数が多ければ改善は大きそう」「貢献するチャンスですね」

# 2a3d5d6より
# timev.rb#L270
  def self.at(time, subsec = false, unit = :microsecond, in: nil)
-   Primitive.time_s_at(time, subsec, unit, Primitive.arg!(:in))
+   if Primitive.mandatory_only?
+     Primitive.time_s_at1(time)
+   else
+     Primitive.time_s_at(time, subsec, unit, Primitive.arg!(:in))
+   end
  end

後で気づきましたが、以下のコミットログに詳しく書かれています。

Image may be NSFW.
Clik here to view.
🔗
Rubyコンパイラのリスト(Ruby Weeklyより)


つっつきボイス:「以下の記事↓を翻訳させていただいたShopifyのChris Seatonさんが、Rubyのコンパイラにどんなものがあるかを調べてみたそうです」「いろんなコンパイラやJITがあるな〜」「@tendeloveさんのTenderJITもありますね」「YJITはリストの下の方に載ってる」「こういうサーベイをやれるところがShopityの強さですね」

Rubyオブジェクトの未来をつくる「シェイプ」とは(翻訳)

Image may be NSFW.
Clik here to view.
🔗
pru: コマンドラインでRubyを手軽にパイプでつなぐ(Ruby Weeklyより)

Image may be NSFW.
Clik here to view.
grosser/pru - GitHub


つっつきボイス:「これは?」「以下みたいにpruを呼び出すことでRubyをsedやawk的なフィルタとして手軽に使えるようにするそうです」「お〜なるほど、Rubyを-eオプションで使おうとすろとPerlのperl peよりコマンドラインが長くなりがちなので、なかなか便利そうImage may be NSFW.
Clik here to view.
👍
」「pru -i Gemfile 'sub /ruby/, "foo"'perl -i的なインライン置換もできるんですね」

# 同リポジトリより
# count letters of each line
ls -1 | pru size

# select lines longer than five letters
ls -1 | pru 'size > 5'

# 2nd to last character
ls -1 | pru self[2..-1]

「このpruのシングルバイナリ版が欲しいですね: gem installできない環境でも使えるようになるので」「それ欲しいです」「例のBusyBox↓にpruもで入ってくれたらとても嬉しい」「BusyBoxはよく使うコマンドがシングルバイナリになっているんでしたね」

参考: BusyBox - Wikipedia

Image may be NSFW.
Clik here to view.
🔗
その他Ruby

つっつきボイス:「今年のRuby PrizeはYJITでRubyに貢献したShopifyのMaximeさんでしたね」「YJITをここまで作り上げたMaximeさんとShopifyの人たちはホント凄い」「MaximeさんがPhD論文で提案したBasic Block Versioning(BBV)をRubyのYJITに応用したそうです」

YJIT: CRuby向けの新しいJITコンパイラを構築する(翻訳)


「チェリー本の改訂第2版がいよいよ先行発売開始Image may be NSFW.
Clik here to view.
🎉

Image may be NSFW.
Clik here to view.
🔗
クラウド/コンテナ/インフラ/Serverless

Image may be NSFW.
Clik here to view.
🔗
厚労省の「偽装請負」に関する疑義応答集の解説(Publickeyより)


つっつきボイス:「Agile Japan 2021でIPA専門委員の弁護士が厚労省の関係疑義応答集を解説してくれたのを記事にしたものですね: 自分の理解が概ね合っていたことがこの記事で確認できました」

参考: Agile Japan 2021 | The Heart of Agile

「そういえばBPSは客先常駐を基本的に行っていませんけど関連はあるんでしょうか?」「発注元が受注先の指揮系統を通さずに受注先の作業者に直接指示を出すことが違法になるので、常駐やリモートの有無にかかわらず関連はあります」「なるほど」「発注元が受注先の作業者に直接指示を出すなら派遣の形にする必要があります(発注前に作業者を面談・試験したりするのは派遣でも違法です)」

「いわゆる準委任型の開発を受ける側はこういう点に気をつける必要があるんですが、受注側が守りに入りすぎると今度はアジャイル開発のよさが失われてしまって、たとえばアジャイルでよくあるスタンダップミーティングすらできなくなってしまったりします」「あ〜たしかに」

参考: アジャイルソフトウェア開発 - Wikipedia

「話そのものは新しくはありませんが、これまでグレーになりがちだった部分についてIPAの弁護士が事例込みで詳しく解説してくれたのはありがたい」「ちゃんと読んでおこうっと」


後編は以上です。

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

週刊Railsウォッチ: フォームヘルパーの改修、Railsの監査ログgem比較、DHHとimport-mapほか(20211129前編)

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

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

Ruby Weekly

Image may be NSFW.
Clik here to view.

Publickey

Image may be NSFW.
Clik here to view.
publickey_banner_captured

The post 週刊Railsウォッチ: Railsで「Read Model」を使う、Ruby Prize 2021受賞者決定、pru gemほか(20211201後編) first appeared on TechRacho.

Go格言集に込められた詩情とダブルミーニング

Image may be NSFW.
Clik here to view.

Go言語には、かなり初期から「Go Proverbs」と呼ばれるGoの格言集があります。

同サイトに記載されている格言は、2015年のRob Pikeの講演動画にもリンクされています。

日本語訳はいくらでもネットに落ちているので探せばすぐに見つかるでしょう。

銀座Rails#38の発表「TechRacho翻訳記事の裏舞台」の中でこのGo Proverbsについて少し触れたのですが、惜しくも途中で時間切れとなってしまったのでここで供養したいと思います。

Go格言集というタイトル

根拠はありませんが、Go Proverbsというタイトルはきっと「囲碁の格言集」に引っかけているのだと私は信じています。

参考: 囲碁格言集

私は囲碁はわからないので、麻雀の「早いリーチは七対子」や「単騎は西で待て地獄待ち」「下家のポンは親孝行」みたいな格言や、「下手の中飛車、へぼの棒銀」「桂馬の高飛び歩の餌食」みたいな将棋の格言ぐらいしか知りません。

参考: 麻雀格言特集!プロの見解つき! | 麻雀豆腐
参考: 将棋の格言 - Wikipedia

第1の格言

Go Proverbsの第1の格言は以下です。

Don’t communicate by sharing memory, share memory by communicating.
Go Proverbsより

技術的な格言としては「共有メモリで通信するな」「メモリは通信で共有せよ」などとなるでしょう。メモリ共有をソフトウェアで行うのは難易度が高く、複雑になったときに競合周りで事故りやすいというのは昔からよく言われているので、それを戒める意味もあるのでしょう。これなら全然普通の訳文です。

参考: 共有メモリ - Wikipedia

しかし、Go Proverbsのサブタイトルは「シンプルで詩情にあふれ、含蓄がある」とあります。

Simple, Poetic, Pithy
Go Proverbsより

しかも考案者のRob Pikeは動画の中で「この格言は繰り返しも詩的にキマったし含蓄もある」というようなことを話しています。つまり少なくともここには詩情が込められているはずです。

それを踏まえて、以下の裏訳をこしらえてみました。

思い出を分かち合うことで通じ合うのではない」「通じ合うことで思い出を分かち合うのだ

あるいは

思い出を分かち合うことで互いの心が通うのではない」「互いの心を通わせることで思い出を分かち合うのだ

memoryには「思い出」という意味もあるのでした。知らなくても何の問題もありませんが、どこかで話したくて仕方がなかったので、ここで供養したいと思います。

第10・第11の格言

第10と第11の格言

Cgo must always be guarded with build tags.
Cgo is not Go.
Go Proverbsより

動画では、Rob Pikeがcgoを「シーゴ」と発音しています(シージーオーではないんですね)。これは少なくとも動画の中ではC言語の神(C God)にかけていると推測しました。動画中でも「cgoを呼び出せばその結果は神のみぞ知る(God only knows)」とダジャレをかましています。詩情まではあまり手が回ってない感あります。

参考: cgo command - cmd/cgo - pkg.go.dev

ついでながら、当時のRob Pikeは「自分はテスト以外では絶対cgoを使いません」とも話していますね。

第5・第15の格言

第5と第15の格言は以下です。

Make the zero value useful.
Errors are values.
Go Proverbsより

Go言語の基本型にはゼロ値(zero value)という概念があります。

参考: ゼロ値を使おう #golang - Qiita

また、Go言語は例外処理を用いず、関数が複数の値を返せるようになっていて(多値返し)、エラーを明示的に返すのが基本です。

参考: Goエラーハンドリング戦略

このvalueという言葉ですが、技術用語の「値」という意味の他に「価値」「値打ち」という意味もあります。

つまり第5の格言は、「ゼロ値を活用せよ」という本来の意味に加えて、「価値のないものを役立てよ」「無価値を活用せよ」という引っかけになっているのだと推測しました。

第15の格言も、「エラーとは値である」(これはGoのエラーは例外処理やタプルのようなものではなく、単なる値でしかないのだという含みもあると思っています)という本来の意味の他に、「エラーとは価値だ」「エラーには価値がある」という引っかけにもなっているのだと思っています。


話はそれますが、フィル・コリンズが昔出した最初のソロアルバム「夜の囁き」の英語タイトルが「Face Value」でした。これは辞書的には「額面価格」という意味ですが、アルバムジャケットにフィル・コリンズの顔が大写しになっているあたりからして、文字通り「顔の値打ち」にもかけているのかなと当時思った覚えがあります。

参考: 夜の囁き - Wikipedia

第19の格言

これだけ動画がありません。

Don’t panic.
Go Proverbsより

Goにはpanicというキーワードがありますが、この格言はpanicを通常のエラー処理フローに使ってくれるなという意図です。

参考: Go言語のエラーハンドリングについて ~panic編~ - Qiita

これは一番わかりやすいですね。「Don’t panic.」は「うろたえるな!」というイディオムでもあります。映画やアニメの悪役が追い詰められたときのセリフで頻繁に使われます。

その他

動画の他の格言では「格言っぽくキマらなかったけど、いいんだよこれで」みたいなお茶目な発言もありますね。格言すべてが詩的というわけではないことが伺えます。それでもこういう遊び心は大好きです。

英語だと「Think Different」みたいなキャッチコピーならともかく、格言を短くキメるのは大変そうです。中国語は「1シラブル=1語」ですし、日本語にも四字熟語の形でかなり浸透していることもあって、中国語の格言は「好牌先打」的に短くキメやすいように自分には見えます。どちらにしろ創作するのは簡単ではないでしょうけど。

参考: 短い英語の名言・格言集。覚えやすく心に残る言葉! | 癒しツアー
参考: 「使えるとかっこいい!」中国語の名言・ことわざ30選 | courage-blog

また最後の方では「格言に矛盾があるかもしれないけどいいんです、人生は矛盾に満ちています」という感じで締めています。Rob Pikeの動画は終始この調子で笑いを取りまくっていて楽しいですね。

本当は他のGo格言についても書きたい気持ちです。

まだ自分が見逃しているかけことばや引用があるかもしれません。「こんなのもあるぞ!」「その解釈は違う!」などありましたら@hachi8833までお知らせください。


以上、知らなくても何も困らないお話でした。

Image may be NSFW.
Clik here to view.

The post Go格言集に込められた詩情とダブルミーニング first appeared on TechRacho.

RailsのCSRF保護を詳しく調べてみた(翻訳)

Image may be NSFW.
Clik here to view.

概要

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


  • 2017/10/23: 初版公開
  • 2021/11/26: 更新

RailsのCSRF保護を詳しく調べてみた(翻訳)

現在Railsを使っていればCSRF保護を使うことがあるでしょう。この機能はRailsのほぼ初期から存在し、即座に導入して開発を楽にできるRailsの機能のひとつです。

CSRF(Cross-Site Request Forgery)を簡単に説明すると、悪意のあるユーザーがサーバーへのリクエストを捏造して正当なものに見せかけ、認証済みユーザーを装うという攻撃手法です。Railsでは、一意のトークンを生成して送信のたびに真正性を確認することでこの種の攻撃から保護します。

最近私がUnbounceのある機能を使ったとき、CSRF保護と、CSRF保護をクライアント側のJavaScriptリクエストでどう扱うかについて考慮が必要になりました。そのとき、自分がCSRF保護についてほとんど何も知らないどころか、CSRFが何の略語なのかも知らないことに気づきました。

そこで私は、Railsコードベースでこの機能がどのように実装されているかを詳しく調べることにしました。本記事では、RailsでのCSRF保護の動作を追ってみました。レスポンスごとのトークンが最初にどうやって生成されるか、およびサーバーへのリクエストの真正性のバリデーションについても解説いたします。

基本

CSRFには2つの要素で構成されます。最初にサイトのHTMLに一意のトークンを埋め込みます。これと同じトークンはセッションcookieにも保存されます。ユーザーがPOSTリクエストを送信するときに、HTMLに埋められていたCSRFトークンも一緒に送信されます。Railsはページのトークンとセッションcookie内のトークンを比較し、両者が一致することを確認します。

Image may be NSFW.
Clik here to view.

CSRF保護の利用法

Rails開発者はCSRF保護を無料で利用できます。最初に、application_controller.rbファイルでCSRF保護をオンにする以下の1行を有効にします(訳注2021/11/26: Rails 5.2以降はコントローラに書かなくてもデフォルトでconfig.action_controller.default_protect_from_forgery = trueが有効になります: Railsガイド)。

protect_from_forgery with: :exception

次に、application.html.erbに次の1行を追加します。

<%= csrf_meta_tags %>

これでおしまいです。この機能は長年Railsに搭載されているので、開発者はこれを利用するかどうか決めるだけでよいのです。しかしこの機能はどのように実装されているのでしょうか?

生成と暗号化

まずは#csrf_meta_tagsから調べてみましょう。これはHTMLに真正性トークンを埋め込むシンプルなビューヘルパーです(gist: csrf_helper.rb)。

# actionview/lib/action_view/helpers/csrf_helper.rb

def csrf_meta_tags
  if protect_against_forgery?
    [
      tag("meta", name: "csrf-param", content: request_forgery_protection_token),
      tag("meta", name: "csrf-token", content: form_authenticity_token)
    ].join("\n").html_safe
  end
end

このcsrf-tokenタグにご注目ください。すべてのマジックはここで起きます。tagヘルパーは#form_authenticity_tokenを呼んで実際のトークンを取り出します。そしてActionControllerのRequestForgeryProtectionモジュールに進むところから面白くなってきます。

RequestForgeryProtectionモジュールは、CSRF関連の一切を取り扱います。中でも有名なのはApplicationControllerで見かける#protect_from_forgeryです。これはリクエストごとにCSRFバリデーションをトリガするフックを設定し、リクエストの真正性を照合できなかった場合のレスポンスを設定します。この他にもCSRFトークンの生成/暗号化/復号化も担当します。このモジュールはスコープが小さい点が気に入りました。ビューヘルパーを別にすれば、CSRF保護の実装は1ファイルに収まっています。

続いて、CSRFトークンがHTMLに達するまでを詳しく見てみましょう。
#form_authenticity_tokenは、セッション自身を含む任意のオプションパラメータを#masked_authenticity_tokenに渡すシンプルなラッパーメソッドです(gist: request_forgery_protection.rb)。読みやすさのためコードの一部を省略しています。

# actionpack/lib/action_controller/metal/request_forgery_protection.rb

# トークンの値を現在のセッションに設定
def form_authenticity_token(form_options: {})
  masked_authenticity_token(session, form_options: form_options)
end

# リクエストごとに異なる真正性トークンのマスキング版を作成する
# マスキングはBREACHなどのSSL攻撃の緩和のため
def masked_authenticity_token(session, form_options: {}) # :doc:
  # ...
  raw_token = if per_form_csrf_tokens && action && method
    # ...
  else
    real_csrf_token(session)
  end

  one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
  encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
  masked_token = one_time_pad + encrypted_csrf_token
  Base64.strict_encode64(masked_token)
end

Rails 5でフォームごとのCSRFトークンが導入されたため、masked_authenticity_tokenメソッドはやや複雑になっています。本記事では本来の実装である「リクエストごとに1つのCSRFトークンがmetaタグに達する」ところを追うことにします。この場合、上のelse分岐で#real_csrf_tokenの戻り値にraw_tokenが設定されます。

#real_csrf_tokensessionを渡す理由がおわかりでしょうか。このメソッドは実際には2つの動作を実行するからです。1つは暗号化されていない生トークンの生成、もう1つはトークンのセッションcookieへの埋め込みです(gist: equest_forgery_protection.rb)。

# actionpack/lib/action_controller/metal/request_forgery_protection.rb

def real_csrf_token(session) # :doc:
  session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
  Base64.strict_decode64(session[:_csrf_token])
end

このメソッドは最終的に、アプリのレイアウトの#csrf_meta_tagsが呼び出されると呼び出されることを思い出しましょう。これは昔ながらのRailsマジックです。この賢い副作用によって、セッションcookieのトークンがページのトークンと一致することが保証されます。保証される理由は、ページのトークンのレンダリングが行われるときには必ず同じトークンがcookieに挿入されるからです。

とにかく、#masked_authenticity_tokenの最後の方を見てみましょう(gist: request_forgery_protection.rb)。

# request_forgery_protection.rb
  one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
  encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
  masked_token = one_time_pad + encrypted_csrf_token
  Base64.strict_encode64(masked_token)

ここでは暗号化が行われます。セッションcookieにはトークンを挿入済みなので、このメソッドはプレーンテキストHTMLで使われるトークンを返す作業にかかります。ここではいくつかの点に注意します(主にSSL BREACH攻撃の緩和のためですが、ここでは立ち入りません)。Rails 4以降はセッションcookie自体を暗号化するようになったため、セッションcookieに含めるトークンそのものは暗号化されない点にご注目ください。

最初に、生トークンの暗号化で使うワンタイムパッドを生成します。ワンタイムパッドは、長さの揃った平文メッセージをランダム生成キーで暗号化する手法で、メッセージの復号には同じキーが必要です。「ワンタイム(1回限り)」と呼ばれる理由は、メッセージごとに異なるキーを用い、利用後は破棄されるからです。Railsでは、CSRFトークンを新しく作成するたびに新しいワンタイムパットを生成し、平文トークンをビットごとのXOR操作で暗号化するためにこの機能を実装しています。このワンタイムパッド文字列は暗号化文字列の前に追加され、HTMLで使えるようBase64でエンコードされます。

Image may be NSFW.
Clik here to view.

CSRFトークン暗号化の仕組みの概要を図示します。デフォルトのトークンは長さは32文字ですが、ここでは12文字にしています。

操作が完了すると、マスクされた真正性トークンはスタックに戻され、アプリでレンダリングされたレイアウトに達します(gist: index.html)。

<!-- index.html -->
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="vtaJFQ38doX0b7wQpp0G3H7aUk9HZQni3jHET4yS8nSJRt85Tr6oH7nroQc01dM+C/dlDwt5xPff5LwyZcggeg==" />

復号と照合

CSRFトークンの生成と、トークンがHTMLとcookieに達するまでの解説が終わりましたので、次はRailsへのリクエストのバリデーションを見てみることにしましょう。

ユーザーがサイトにフォームを送信すると、フォームの他のデータとともにCSRFトークンが送信されます(デフォルトのparam名はauthenticity_tokenです)。トークンは、HTTPヘッダーX-CSRF-Tokenでも送信できます。

先ほどApplicationControllerに以下を追加したことを思い出しましょう。

protect_from_forgery with: :exception

この#protect_from_forgeryメソッドは、すべてのコントローラアクションのライフサイクルの途中にbefore-actionを追加します。

before_action :verify_authenticity_token, options

このbefore_actionで、リクエストのparamsやヘッダーにあるCSRFトークンと、セッションcookieとの比較を開始します(gist: request_forgery_protection.rb)。

# actionpack/lib/action_controller/metal/request_forgery_protection.rb

def verify_authenticity_token # :doc:
  # ...
  if !verified_request?
    # エラー処理 ...
  end
end

# ...

def verified_request? # :doc:
  !protect_against_forgery? || request.get? || request.head? ||
    (valid_request_origin? && any_authenticity_token_valid?)
end
照合が成功する場合のフローに注目できるよう、一部のコードを省略しています。

いくつかの管理系タスクを実行後(HEADリクエストやGETリクエストなどの照合は不要です)、#any_authenticity_token_valid?の呼び出しで本格的な照合プロセスが開始されます(gist: request_forgery_protection.rb)。

# request_forgery_protection.rb
def any_authenticity_token_valid? # :doc:
  request_authenticity_tokens.any? do |token|
    valid_authenticity_token?(session, token)
  end
end

リクエストはトークンをフォームのparamsまたはヘッダーとして渡すことがあるので、Railsではいずれかのトークンがセッションcookie内のトークンと一致することだけが求められます。

#valid_authenticity_token?はそこそこ長いメソッドですが、要するに#masked_authenticity_tokenの逆操作を行って復号し、トークンを比較するだけです(gist: request_forgery_protection.rb)。

# request_forgery_protection.rb
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
  # ...

  begin
    masked_token = Base64.strict_decode64(encoded_masked_token)
  rescue ArgumentError # encoded_masked_token の Base64は無効
    return false
  end

  if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
    # ...

  elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
    csrf_token = unmask_token(masked_token)

    compare_with_real_token(csrf_token, session) ||
      valid_per_form_csrf_token?(csrf_token, session)
  else
    false # 不正なトークン
  end
end

最初に、Base64でエンコードされた文字列を受けて復号し、「マスク済みトークン」を得る必要があります。この後で、トークンのマスクを解除してセッションのトークンと比較します(gist: request_forgery_protection.rb)。

# request_forgery_protection.rb
def unmask_token(masked_token) # :doc:
  one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
  encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
  xor_byte_strings(one_time_pad, encrypted_csrf_token)
end

#unmask_tokenでトークン復号に必要な暗号マジックを行う前に、マスク済みトークンを必要なパーツ(ワンタイムパッド、暗号化済みトークン自身)に分割しておきます。続いて2つの文字列のXORを取ると、最終的な平文トークンを得られます。

最後に#compare_with_real_token(これはActiveSupport::SecureUtilに依存しています)でトークン同士が一致することを確認します(gist: request_forgery_protection.rb)。

# request_forgery_protection.rb
def compare_with_real_token(token, session) # :doc:
  ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
end

ついにリクエストが承認されました!「通るがよい(you shall pass)」

訳注: これはロード・オブ・ザ・リングのセリフのもじりです(本当はyou shall not pass: ここは通さぬ)。

まとめ

Railsには実にさまざまな要素があるので、Railsでは動いて当たり前のCSRF保護でこんなに頭を使ったのは初めてでした。たまにはこうやって魔法のカーテンの向こう側を覗いて実際の動作を眺めてみるのも楽しいものです。

CSRF保護の実装は、コードベースにおける「責任の分離」のよい例になっていると思います。モジュールを1つ作成し、小さくとも一貫したパブリックなインターフェイスで公開することで、コードベースにほぼまったく影響を与えずに背後の実装に任せられます。Railsチームが長年にわたってフォームごとのトークンなどの新しい機能をCSRF保護に追加してくれているおかげで、CSRF保護が実際に動く様子をこうして見ることができます。

Railsのコードベースを詳しく調べるたびに、多くのことを学べます。本記事が、Railsのマジックに出会って仕組みを探るときのヒントになればと願っています。

関連記事

Railsアプリの認証システムをセキュアにする4つの方法(翻訳)

The post RailsのCSRF保護を詳しく調べてみた(翻訳) first appeared on TechRacho.

週刊Railsウォッチ: sanitize_sql_likeは重要、X-XSS-Protectionヘッダーのデフォルト変更、kredis gemほか(20211206前編)

Image may be NSFW.
Clik here to view.

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

Image may be NSFW.
Clik here to view.
🔗
Rails: 先週の改修(Rails公式ニュースより)

今回は以下の2つの公式更新情報から見繕いました。

Image may be NSFW.
Clik here to view.
🔗
SQLログで属性をフィルタする機能が追加

SQLログで属性をフィルタする

従来のSQLクエリのログではActiveRecord::Base.filter_attributesがフィルタされていなかった。
今回、prepared_statementが有効になっていれば属性が[FILTERED]でフィルタされるようになった。

# 改修前
  Foo Load (0.2ms)  SELECT "foos".* FROM "foos" WHERE "foos"."passw" = ? LIMIT ?  [["passw", "hello"], ["LIMIT", 1]]
# 改修後
  Foo Load (0.5ms)  SELECT "foos".* FROM "foos" WHERE "foos"."passw" = ? LIMIT ?  [["passw", "[FILTERED]"], ["LIMIT", 1]]

Aishwarya Subramanian
同Changelogより


つっつきボイス:「お、filter_attributesでフィルタをかけたときにSQLログでも属性がフィルタされるようになったのね」「これは賢い」「SQLログはログレベルを:debugにしないと出ませんが、これはやっておくべきImage may be NSFW.
Clik here to view.
👍

参考: 3.1 Rails全般の設定 — Rails アプリケーションを設定する - Railsガイド

Image may be NSFW.
Clik here to view.
🔗
Active Storageの改修2点


つっつきボイス:「1個目の#41544は、GCS(Google Cloud Storage)のcompose機能に対応したのか」「GCSのcomposeはファイルを結合する機能なのね↓」「GCSのcompose機能を使うと、たとえばログファイルを手元に持ってきて結合したりせずにGCS上でAPIを使って結合したりできるのか、へ〜!」「そんな機能があったんですね」

参考: Cloud Storage  |  Google Cloud
参考: GCS上のファイルを結合する - Qiita

「ファイル変更を見るとAWS S3やMicrosoft Azureにも追加されてますね」「どれどれ、S3やAzureの場合はeachで回して結合している↓」「S3だとIO.copy_streamで持ってきたものをeachでそのまま結合しているのね」「Azureも似たような感じ」

# 
    def compose(*source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
      content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename

      object_for(destination_key).upload_stream(
        content_type: content_type,
        content_disposition: content_disposition,
        part_size: MINIMUM_UPLOAD_PART_SIZE,
        metadata: custom_metadata,
        **upload_options
      ) do |out|
        source_keys.each do |source_key|
          stream(source_key) do |chunk|
            IO.copy_stream(StringIO.new(chunk), out)
          end
        end
      end
    end
# activestorage/lib/active_storage/service/azure_storage_service.rb#110
    def compose(*source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
      content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename

      client.create_append_blob(
        container,
        destination_key,
        content_type: content_type,
        content_disposition: content_disposition,
        metadata: custom_metadata,
      ).tap do |blob|
        source_keys.each do |source_key|
          stream(source_key) do |chunk|
            client.append_blob_block(container, blob.name, chunk)
          end
        end
      end
    end

「GCSの場合は専用のbucket.composeを使ってGCS上で結合できるからシンプル↓」「GCSはローカルに持ってこなくていいので効率よさそうImage may be NSFW.
Clik here to view.
👍

# activestorage/lib/active_storage/service/gcs_service.rb#137
    def compose(*source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
      bucket.compose(source_keys, destination_key).update do |file|
        file.content_type = content_type
        file.content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
        file.metadata = custom_metadata
      end
    end

「2つ目の#43294は、Active Storageが作るメタデータ以外に"custom"のような任意構造のメタデータも追加できるようになった↓」「これは使いたい人いそう」「今までなかったんですね」

# activestorage/test/models/blob_test.rb#L269
+ test "updating the metadata updates service metadata" do
+   blob = directly_upload_file_blob(filename: "racecar.jpg", content_type: "application/octet-stream")
+
+   expected_arguments = [
+     blob.key,
+     {
+       content_type: "application/octet-stream",
+       disposition: :attachment,
+       filename: blob.filename,
+       custom_metadatata: { "test" => true }
+     }
+   ]
+
+   assert_called_with(blob.service, :update_metadata, expected_arguments) do
+     blob.update!(metadata: { custom: { "test" => true } })
+   end
+ end

Image may be NSFW.
Clik here to view.
🔗
upsert_allupdate_only:オプションが追加

upsert_allメソッドに:update_onlyオプションが追加され、コンフリクト時に更新するカラムのリストを指定できるようになった。
従来は、:on_duplicate:オプションで更新用のSQL文をカスタマイズするしかなかった。今回:update_only:オプションが追加されたことで、コンフリクト時に更新したいカラムのリストを渡せるようになった。

Commodity.upsert_all(
  [
    { id: 2, name: "Copper", price: 4.84 },
    { id: 4, name: "Gold", price: 1380.87 },
    { id: 6, name: "Aluminium", price: 0.35 }
  ],
  update_only: [:price] # Only prices will be updated
)

同Changelogより


つっつきボイス:「upsert_allupdate_only:オプションが追加されて、更新時に更新するカラムを指定できるようになったImage may be NSFW.
Clik here to view.
🎉
」「上で言うと、レコードが既に存在する場合はpriceだけが更新されるということですね」

「テストコードでは、upsert_allを2回実行して、2回目だけupdate_only: :nameを指定すると、nameだけ更新されてisbnは更新されない↓」「レコードが存在しなかった場合は今までどおり全部のカラムがINSERTされるんですね、なるほど理解です」「テストコードを見れば振る舞いがわかるようになっていてありがたい: ドキュメントだけだと振る舞いがわかりにくいときはテストコードも見るのがおすすめです」

# activerecord/test/cases/insert_all_test.rb#L345
+ def test_upsert_all_only_updates_the_column_provided_via_update_only
+   Book.upsert_all [{ id: 101, name: "Perelandra", author_id: 7, isbn: "1974522598" }]
+   Book.upsert_all [{ id: 101, name: "Perelandra 2", author_id: 7, isbn: "111111" }], update_only: :name
+
+   book = Book.find(101)
+   assert_equal "Perelandra 2", book.name, "Should have updated the name"
+   assert_equal "1974522598", book.isbn, "Should not have updated the isbn"
+ end

Image may be NSFW.
Clik here to view.
🔗
Pathname#existenceが追加


つっつきボイス:「Active Supportにexistenceメソッドが入った↓」

# activesupport/lib/active_support/core_ext/pathname/existence.rb#18
  def existence
    self if exist?
  end

「なるほど、Active SupportのObject#presenceみたいなチェックをファイルの存在について行ってチェインできるのか↓」「ファイルが存在すればそのファイル自身を返して、存在しなければnilを返すんでしょうね」「後置ifだとチェックと処理が分かれてしまうけど、existence&.readなら簡潔に書き下せるのがいいですねImage may be NSFW.
Clik here to view.
👍

# 同PRより
content = Rails.root.join('file').read if Rails.root.join('file').exist?
↓
content = Rails.root.join('file').existence&.read

Object#presenceみたいな便利メソッドは昔から好きなので、いつかRuby本家に入ってくれたら嬉しいけど、Railsのpresent?blank?あたりの仕様がRuby側で議論になりそうな気もするので難しいかも」「議論になりそうなのわかります」「Pathname#existenceならRubyに入っても大丈夫そう」

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

Image may be NSFW.
Clik here to view.
🔗
Rails.error.handleにフォールバック値を指定できるようになった

handleにオプションでfallback:パラメータを渡せるようにし、raiseがハンドリングされたときにこの値が返されるようにした。これにはnull objectやデフォルト値、またはnil返しと異なる何らかのフラグが使える。

user = Rails.error.handle(fallback: User.anonymous) do
  User.find_by(params)
end

同PRより抜粋


つっつきボイス:「これは前回入ったRails.errorへの機能追加ですね(ウォッチ20211129)」「フォールバック値を指定する機能はたしかに欲しい」

Image may be NSFW.
Clik here to view.
🔗
コネクションプールでroleshardも指定可能に


つっつきボイス:「マルチプルデータベース関連のプルリクが多い@eileencodesさんによるものですね」「pool_configresolve_pool_configroleshardも指定できるようになった: シャーディングの条件によってコネクションプールを分けるなど、コネクションプールでこれらを細かく指定できると便利なときがありそうかなと思いました」

# activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb#L129
-       pool_config = resolve_pool_config(config, owner_name)
+       pool_config = resolve_pool_config(config, owner_name, role, shard)

Image may be NSFW.
Clik here to view.
🔗
X-XSS-Protectionヘッダーのデフォルト値が0に変更

このX-XSS-Protectionヘッダーは既に非推奨化されており、このヘッダーを当初から実装していた主要なモダンブラウザ(ただしFirefoxでは実装されなかった)では、これをトリガーしていたXSS Auditorが既に削除されてContent Security Policy(CSP)に置き換わっている。

OWASPでは、セキュリティ問題を増やす可能性のあるこのヘッダーを0に設定して古いブラウザのデフォルトの振る舞いを無効にするよう指示している。
この新しい振る舞いはRails 7.0からフレームワークのデフォルトとして追加された。
このヘッダーを0に設定する理由については以下を参照:


つっつきボイス:「OWASPが正式にX-XSS-Protectionヘッダーを0にすべきと言っているならそのとおりにするのがよさそうImage may be NSFW.
Clik here to view.
👍
」「X-XSS-Protectionヘッダー自体を削除しないのはなぜなんだろう?Image may be NSFW.
Clik here to view.
🤔
」「このヘッダーに対応しているブラウザがまだあるからじゃないかな: 一度広まったものをなくすのは大変」「ほんとにそうですよね」

参考: 9 デフォルトのヘッダー — Rails セキュリティガイド - Railsガイド

後で調べると、現時点のsecure_headers gemはデフォルトのX-XSS-Protectionヘッダーがまだ0になっていないそうです↓。

参考: Should x-xss-protection default to “0” instead of “1; mode=block” · Issue #439 · github/secure_headers

Image may be NSFW.
Clik here to view.
🔗
PerThreadRegistryを削除して非推奨化


つっつきボイス:「Rails内部用のPerThreadRegistryはずっと前に非推奨化されていたけどwarningが出されていなかったので、あらためてwarningを出すようにしたそうです」「内部ということはRailsアプリ開発者が意識しなくていいヤツですね、よかった〜」

Image may be NSFW.
Clik here to view.
🔗
Rails

Image may be NSFW.
Clik here to view.
🔗
Railsのsanitize_sql_likeは重要


つっつきボイス:「ちょうど今日のBPS社内勉強会でセキュリティを取り上げたときに話題になったRailsのsanitize_sql_likeメソッドを取り上げてみました」「そうそう、これはRails開発者がぜひとも知っておくべきサニタイズ用メソッドですね」

参考: RailsにてSQLでのワイルドカード文字をエスケープしてくれるsanitize_sql_likeは何をしているのか - Qiita

「これって何ですか?」「SQL文のLIKEの文字列でだけ機能する特殊文字は、通常のsanitize_sqlだけではエスケープされないんですよ」「え〜!そうだったんですか?」

「LIKEでは%がワイルドカード文字というのはよく知られていますけど、実はアンダースコア_も任意の1文字というワイルドカードなんですよ」「ありゃ〜、これは知っておかないとマズいヤツだ」「LIKEで使う文字列にsanitize_sql_likeをかけないとバグになりますし、下手をすると情報漏えいにもつながるので要注意」

# 安全な書き方(to_sしないと配列などをparamsに渡せてしまう)
hoge.where('name = ?', params[:name].to_s)

# =ではなくLIKEだと同じ書き方が危険になる(%や_がワイルドカードと解釈される可能性)
hoge.where('name LIKE ?', params[:name].to_s)

# LIKEの場合はsanitize_sql_likeを明示的にかける必要がある
hoge.where('name LIKE ?', ActiveRecord::Base.sanitize_sql_like(params[:name].to_s))

「意外ですが、edgeガイドにもまだsanitize_sql_likeは載っていませんでした↓」「ちなみに自分はきっとそういうメソッドがあるはずだと思って探して見つけました」「たしかに、ないとおかしい機能ですよね」「今のコードでLIKEを使っているところを全文検索して探して修正してもいいぐらい大事」「この後やらなきゃ」

参考: Securing Rails Applications — Ruby on Rails Guides

Image may be NSFW.
Clik here to view.
🔗
AWS Lambda FunctionsをRubyで書く(Ruby Weeklyより)


つっつきボイス:「AWS Lambda FunctionsをRubyで書く手順を解説した記事、よさそうImage may be NSFW.
Clik here to view.
👍

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

「Lambda Functionsのコードを書くのは別に大変ではありませんが、そのコードをどう管理してデプロイするかの方に力を使いますよね」「そうそう」「管理やデプロイの方法はたくさんあるので、要件や用途に合わせて選ぶ方が大変: Lambda Functionsは単体で使うよりも他のコードと連携させるものなので、そうなるとIAMポリシーなどを適切に設定しないと動かないし、IAMポリシーも管理しようとするとTerraformを使うことになったりするのが悩ましい」

「サーバーレスフレームワークとTerraformをどう住み分けさせるかとかも考えないといけないですよね」「そうそう、サーバーレスフレームワーク単体でもできるけど、その場合は既存のTerraformとの整合性を壊さないようにしないといけないとかで複雑になりがち」

参考: Terraform by HashiCorp

「AWS Cloud Development Kit(CDK)↓も使ってみたいんですが、Terraformと別管理になるとしたら複雑になるんじゃないかという点が気になって、まだ手出ししてません」「以前そのあたりを調べたことがあったんですが、割と強い権限が必要になる機能がいくつもあって、そのときは諦めました」「IAMのロールやポリシーを作成できる権限はどうしても強くなるので、それならTerraformでやる方がいいのかなという気持ちにもなります」「悩みは尽きない…」

参考: AWS クラウド開発キット – アマゾン ウェブ サービス

Image may be NSFW.
Clik here to view.
🔗
kredis: RedisをRuby風にアクセス可能にするラッパー(Ruby Weeklyより)

Image may be NSFW.
Clik here to view.
rails/kredis - GitHub


つっつきボイス:「Keyed Redisだからkredisなのか」「よく見たらGitHubのRailsリポジトリに置かれてるからRailsの機能なのかな?」「新しい割に★が多いですね」「DHHもコントリビュータに入っています」

Image may be NSFW.
Clik here to view.
redis/redis - GitHub

Kredis.string "mystring"のように型名とキーを指定すると.valueで値を取り出せる、まさにキーバリューストアで型やJSONのようなデータ構造が使えるもののようですね↓」「GETSETはRedisの操作なのか」

# 同リポジトリより
string = Kredis.string "mystring"
string.value = "hello world!"  # => SET mystring "hello world"
"hello world!" == string.value # => GET mystring

integer = Kredis.integer "myinteger"
integer.value = 5  # => SET myinteger "5"
5 == integer.value # => GET myinteger

decimal = Kredis.decimal "mydecimal" # accuracy!
decimal.value = "%.47f" % (1.0/10) # => SET mydecimal "0.10000000000000000555111512312578270211815834045"
BigDecimal("0.10000000000000000555111512312578270211815834045e0") == decimal.value # => GET mydecimal

float = Kredis.float "myfloat" # speed!
float.value = 1.0/10 # => SET myfloat "0.1"
0.1 == float.value # => GET myfloat

boolean = Kredis.boolean "myboolean"
boolean.value = true # => SET myboolean "t"
true == boolean.value # => GET myboolean

datetime = Kredis.datetime "mydatetime"
memoized_midnight = Time.zone.now.midnight
datetime.value = memoized_midnight # SET mydatetime "2021-07-27T00:00:00.000000000Z"
memoized_midnight == datetime.value # => GET mydatetime

json = Kredis.json "myjson"
json.value = { "one" => 1, "two" => "2" }  # => SET myjson "{\"one\":1,\"two\":\"2\"}"
{ "one" => 1, "two" => "2" } == json.value  # => GET myjson

「見た感じではRedisの機能にひととおり対応していそう」「Kredis.listという書き方もできるのね↓」

# 同リポジトリより
list = Kredis.list "mylist"
list << "hello world!"               # => RPUSH mylist "hello world!"
[ "hello world!" ] == list.elements  # => LRANGE mylist 0, -1

integer_list = Kredis.list "myintegerlist", typed: :integer
integer_list.append([ 1, 2, 3 ])        # => RPUSH myintegerlist "1" "2" "3"
integer_list << 4                       # => RPUSH myintegerlist "4"
[ 1, 2, 3, 4 ] == integer_list.elements # => LRANGE myintegerlist 0 -1

「Active Recordモデルにもこうやって書けるのね↓」「RPUSH people:5:names "David" "Heinemeier" "Hansson"みたいにActiveRecordのモデルに対してattributes的にRedisに格納する値を設定できる、へ〜!」「面白そう!」

# 同リポジトリ
class Person < ApplicationRecord
  kredis_list :names
  kredis_list :names_with_custom_key, key: ->(p) { "person:#{p.id}:names_customized" }
  kredis_unique_list :skills, limit: 2
  kredis_enum :morning, values: %w[ bright blue black ], default: "bright"
end

person = Person.find(5)
person.names.append "David", "Heinemeier", "Hansson" # => RPUSH people:5:names "David" "Heinemeier" "Hansson"
true == person.morning.bright?                       # => GET people:5:morning
person.morning.value = "blue"                        # => SET people:5:morning
true == person.morning.blue?                         # => GET people:5:morning

「RedisのプリミティブなAPIをRubyのシンプルなオブジェクトでラップしたRubyインターフェイスライブラリという感じかな」「RDBがあるのにkredisを使う理由って何だろう?Image may be NSFW.
Clik here to view.
🤔
」「速度的な理由でRDBの代わりにRedisにデータを入れておきたいことはあるかも」「gem 'kredis'をGemfileに追加してインストールとあるので、デフォルトでRailsに入るわけではなさそう」


前編は以上です。

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

週刊Railsウォッチ: フォームヘルパーの改修、Railsの監査ログgem比較、DHHとimport-mapほか(20211129前編)

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

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

Rails公式ニュース

Image may be NSFW.
Clik here to view.

Ruby Weekly

Image may be NSFW.
Clik here to view.

The post 週刊Railsウォッチ: sanitize_sql_likeは重要、X-XSS-Protectionヘッダーのデフォルト変更、kredis gemほか(20211206前編) first appeared on TechRacho.

週刊Railsウォッチ: 改訂2版『プロを目指す人のためのRuby入門』、『研鑽Rubyプログラミング β版』ほか(20211207後編)

Image may be NSFW.
Clik here to view.

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

Image may be NSFW.
Clik here to view.
🔗
Ruby

Image may be NSFW.
Clik here to view.
🔗
改訂2版『プロを目指す人のためのRuby入門』発売


つっつきボイス:「まさに今日(注: 12/2のつっつきの日)発売されたImage may be NSFW.
Clik here to view.
🎉
」「これは買わなきゃ」「チェリーが1個から2個に増えているのもポイント高い」

「改訂2版ではRubyコミッターたちがみっちりレビューしてくださったそうです」「大事なポイントImage may be NSFW.
Clik here to view.
👍
」「すごい」「本の売れ行きの初速がいいと次回の版も出しやすくなるので、皆さん買いましょう」「今ポチりました〜」

Image may be NSFW.
Clik here to view.
🔗
asmrepl: Rubyで書かれたアセンブラREPL(Ruby Weeklyより)

Image may be NSFW.
Clik here to view.
tenderlove/asmrepl - GitHub

参考: REPL - Wikipedia


つっつきボイス:「@tenderloveさんがまた面白いものをRubyで作っていました」「コンソールでアセンブラのREPLが動く、なるほど」

# 同リポジトリより
(rip 0x00000001033a4001)> mov rax, 5
=============== REGISTER CHANGES ===============
rax     000000000000000000 => 0x0000000000000005

(rip 0x00000001033a4009)> rax
0x0000000000000005
(rip 0x00000001033a4009)>

「ところで何のアセンブラだろう?」「Ruby VM(YARV)か、はたまたMIPSあたりかなと思ったら、リポジトリのAboutにx86 64ビットと書いてあった」「言われてみれば64ビットだけど、アセンブラを見ただけで何のCPUかわかるレベルでなかった…」「強い人だとわかるのが凄い」

参考: YARV - Wikipedia
参考: MIPSアーキテクチャ - Wikipedia

「予想ですが、CPU上で直接アセンブラを実行しているわけではないのかなという気がしました: インタプリタなら動いた瞬間にレジスタの値が変わりそう」「エミュレーションかも」「もしかするとコンテキストスイッチすればできるのかもしれませんが」

「お、asmreplの依存関係にfiskが入っている」「fiskも@tenderloveさんが作ったんですね(ウォッチ20210713)」「fiskはもともとwilsonというプロジェクトにインスパイアされたのか」「fiskはエミュレーションとかではなくちゃんとx86アセンブラそのものが動きそうに見えるので、もしかするとasmreplもエミュレータではないのかもImage may be NSFW.
Clik here to view.
🤔

Image may be NSFW.
Clik here to view.
tenderlove/fisk - GitHub

Image may be NSFW.
Clik here to view.
seattlerb/wilson - GitHub

Image may be NSFW.
Clik here to view.
🔗
Ruby 3.1の構文


つっつきボイス:「koicさんが、RuboCopのRuby 3.1対応に関連してRuby 3.1の構文の変更点をまとめてくださいました」「2本目のRuboCopエラー対処方法の記事もありがたいImage may be NSFW.
Clik here to view.
🙏

「匿名ブロック委譲の構文&が入るのね↓」「新しめの変更らしく、Ruby 3.1のpreview1ではまだ動きませんでした」「知らないとちょっと驚きそうな書き方かも」

# 同記事より
def foo(&)
  bar(&)
end

「そうそう、パターンマッチにpinオペレータ^が追加されるImage may be NSFW.
Clik here to view.
🎉

# 同記事より
Prime.each_cons(2).lazy.find_all { _1 in [n, ^(n + 2)] }.take(3).to_a
#=> [[3, 5], [5, 7], [11, 13]]

「1行パターンマッチのメソッド呼び出しで丸かっこ()が省略可能になる↓」

# 同記事より: Ruby 3.0ではエラー
[1, 2] => a, b

「引数のforwardingでメソッド仮引数の丸かっこ()が省略可能↓、これもありますね」

# 同記事より: Ruby 3.0ではエラー
def foo a, ...
end

「知らないとびっくりしそうな新構文が割とある感じですね」「...が生で出たりすると一瞬考えそう」「今回は抑えめですが、Rubyは新構文を比較的積極的に取り入れる傾向がありますね」

Image may be NSFW.
Clik here to view.
🔗
callerの隠れた機能(Ruby Weeklyより)


つっつきボイス:「callerというメソッドがあるんですね」「callerは、自分を呼び出したものをinspectするのに使います: たしかKernelモジュールにあった↓」「なるほど」「Kernelモジュールにどんなメソッドがあるかは一度眺めておいて損はないと思います」

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

「記事にもあるcaller_locations↓はよくデバッグで使いますね: コールスタックをどこまで掘るかを引数で指定できる」「なるほど」「でないとコールスタックが深くなったときに大変」「記事ではcallerのベンチマークも取っていますね」

# 同記事より
loc = caller_locations(1, 1)

puts "  --> called from #{loc.path} at line #{loc.lineno}"
puts "      (full path: #{loc.absolute_path})"
# 同記事より
$> ruby tea.rb
1 teaspoon per cup...
Pouring water and waiting 3.5 minutes...
Ssssip! Mmmm, Assam.
  --> called from tea.rb at line 9
      (full path: /Users/alextaylor/code/tea.

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

「デバッガに入ったときにこれらのメソッドでコールスタックを取れるのは便利Image may be NSFW.
Clik here to view.
👍

Image may be NSFW.
Clik here to view.
🔗
Rubygemsの最近の改修


つっつきボイス:「2つともruby-jp Slackで見かけたプルリクです」「Rubygems.orgで何か変わるんでしょうか?」「1つ目は、gemのオーナーシップをリクエストできるフォームの追加だそうです」「なるほど、ところで今さらですがrubygems.orgってRailsで構築されているんですね」「あ、ホントだ」「しかもきちんとメンテナンスされていて凄い」「NewRelicが入っているとか、そっちが気になってしまうImage may be NSFW.
Clik here to view.
😆

Image may be NSFW.
Clik here to view.
rubygems/rubygems.org - GitHub

「2つ目は、bundle gemを実行したときにRBSのテンプレートも追加するようにする改修だそうです」「これは話題になっていましたね: こうやってコンベンションを定めることでRBSの作成と利用を促進していくのは大事Image may be NSFW.
Clik here to view.
👍
」「RBSはsigディレクトリに置くことになるのね↓」

# bundler/lib/bundler/cli/gem.rb#75
      templates = {
        "#{Bundler.preferred_gemfile_name}.tt" => Bundler.preferred_gemfile_name,
        "lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb",
        "lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb",
+       "sig/newgem.rbs.tt" => "sig/#{namespaced_path}.rbs",
        "newgem.gemspec.tt" => "#{name}.gemspec",
        "Rakefile.tt" => "Rakefile",
        "README.md.tt" => "README.md",
        "bin/console.tt" => "bin/console",
        "bin/setup.tt" => "bin/setup",
      }

参考: Rubyで型チェック!動かして理解するRBS入門 〜サンプルコードでわかる!Ruby 3.0の主な新機能と変更点 Part 1〜 - Qiita

Image may be NSFW.
Clik here to view.
ruby/rbs - GitHub

Image may be NSFW.
Clik here to view.
🔗
『研鑽Rubyプログラミング β版』が電子書籍でリリース


つっつきボイス:「あの『Polished Ruby Programming』の日本語版『研鑽Rubyプログラミング』のβ版が電子書籍のみでリリースされました」「ついに、というかもうなんですねImage may be NSFW.
Clik here to view.
🎉
」「早!」

『Polished Ruby Programming』(Jeremy Evans著)を読みました

「β版では6章までなんですね」「ラムダノートから出版しているのがなるほど感」「たしかにラムダノートの読者に刺さりそうな内容ですよね」「Rubyがメインでないラムダノートの読者にもアピールしそう」「ちょうどn月刊ラムダノートの最新号が出てたので買っちゃいました↓」

参考: n月刊ラムダノート Vol.3, No.2(2021)(電子書籍のみ) – 技術書出版と販売のラムダノート

Image may be NSFW.
Clik here to view.
🔗
クラウド/コンテナ/インフラ/Serverless

Image may be NSFW.
Clik here to view.
🔗
Amazon ECR PublicからDocker公式イメージが取得可能に(Publickeyより)


つっつきボイス:「BPS社内Slackに貼っていただいた記事です」「ようやく実現されましたImage may be NSFW.
Clik here to view.
🎉

「Docker Hubからイメージを取得しなくてもよくなったということでしょうか?」「そうですね、Docker HubのミラーがAWS内に用意されたので、AWS環境からdocker pullしたときに通信がAWS環境内で完結するようになって、転送費用もかからなくなるはず」「なるほどありがたい」「少し前にDocker Hubからのpullに制限がかかるようになったので、Docker Hubのミラーから取れるのはとてもありがたい」

参考: Docker HubのRate Limitにやられた話 - OPTiM TECH BLOG

「今までなかったのが不思議ですよね」「憶測ですが、外部との転送が多い方がAWSの収入になるからじゃないかな」「それあるかも」

Image may be NSFW.
Clik here to view.
🔗
電子帳簿保存法の改正


つっつきボイス:「電子帳簿保存法の改正って自分には関係ないのかなと思っていましたけど」「ポイントは記事にもあるように、オンライン取引の証憑(しょうひょう)類はデジタルデータのまま保存しておかなければならなくなることですね: 紙に印刷する必要がなくなって便利になったとも言えるけど、逆にオンライン領収書を紙に印刷して保存するのがNGになる」「あ〜」

「紙の領収書をスキャンして保存するのがOKになったことの方が大きいかも」「なるほど」「ファイル名が日付_取引先名_金額でなければいけないのがちょっと面倒そうですけど」「ファイル名ぐらいはしょうがないですよ」「デジタルで保存できるだけでもかなりありがたいし、ソフトウェアエンジニアの場合は経費の領収書が大量になることはほぼないので、ファイル数は大した数にはならないでしょうね」

「あとは記事にもあるように、2023年度から始まるインボイス制度も大きい」

参考: インボイス制度の概要|国税庁
参考: 一人親方が確認すべきインボイス制度とは?年間売上1,000万円以下に消費税の納税義務!?| 会計ソフト マネーフォワード クラウド

「ところで、以前の青色申告は仕分けなどがとても面倒でしたけど、今はマネーフォワードやfreeeのようなサービスがあるので随分楽になりましたよね」「そうそう」

参考: バックオフィスから経営を強くする「マネーフォワード クラウド」
参考: 無料から使えるクラウド会計ソフト freee | クラウド会計ソフト freee会計


後編は以上です。

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

週刊Railsウォッチ: フォームヘルパーの改修、Railsの監査ログgem比較、DHHとimport-mapほか(20211129前編)

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

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

Ruby Weekly

Image may be NSFW.
Clik here to view.

Publickey

Image may be NSFW.
Clik here to view.
publickey_banner_captured

The post 週刊Railsウォッチ: 改訂2版『プロを目指す人のためのRuby入門』、『研鑽Rubyプログラミング β版』ほか(20211207後編) first appeared on TechRacho.


Rails tips: 知らないと損する4つのバリデーションレベル(翻訳)

Image may be NSFW.
Clik here to view.

概要

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


  • 2018/05/09: 初版公開
  • 2021/12/02: 更新

概要

Rails tips: 知らないと損する4つのバリデーションレベル(翻訳)

考えるまでもないことですが、アプリがユーザー入力を受け取ったらバリデーションが必要になります。Ruby on Railsアプリでバリデーションといえば真っ先に思い当たるのがモデルのバリデーションです。しかしそれ以外のレベルのバリデーションについてはどうでしょう。モデルのバリデーションがあれば完璧なソリューションになるのでしょうか?今回はRailsアプリの4つのレベルのバリデーションを簡単にご紹介しつつ、それぞれのメリットとデメリットについて説明したいと思います。お題として、Userモデルのemailカラムを使います。

1. モデルレベルのバリデーション

Railsアプリでよく見られるアプローチです。emailUserのレコードに確実に存在するようにするために、以下のバリデーションを定義できます。

class User < ActiveRecord::Base
  validates :email, presence: true
end

このデータ保護方法は間違っていませんが、これだけではメールが空のUserレコードをまだ作成できてしまうことをお見逃しなく。User#saveUser#save!を呼んでも無効なレコードはデータベースに保存されませんが、以下のメソッドを呼べば保存されてしまうのです。

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters

これらのメソッドを用いる場合、特にupdate_で始まるメソッドについては注意が必要です。では逆に、emailカラムのバリデーションが不要な場合はどうすればよいでしょうか?その場合は:ifオプションを渡すか、コントローラレベルのバリデーションを検討しましょう。

2. コントローラレベルのバリデーション

上述したように、特定の場合に限ってバリデーションを行いたいことがあります。:ifオプションや:unlessオプションを渡してもよいのですが、バリデーションルールが複雑になって読みづらくなったりテストがしにくくなるかもしれません。そこでコントローラレベルのバリデーションが選択肢として浮かび上がってきます。これを正しく行うには、Form Objectパターンの利用をおすすめします。ただし、コントローラレベルのバリデーションはモデルレベルのバリデーションに比べてメンテの難易度がぐっと上がります。Form Objectパターンは、モデルに多数のバリデーションがあり、ときどき必須にしたいバリデーションやときどきオプションにしたいバリデーションがあるような非常に大規模なアプリでとても有用です。

訳注

Form Objectについては以下の記事やForm Objectタグなどもどうぞ。

Rails: Form Objectと`#to_model`を使ってバリデーションをモデルから分離する(翻訳)

3. データベースレベルのバリデーション

データベースレベルのバリデーションは最も安全性が高く、これをすり抜けて無効な値を保存することはできません。よく使われるのはpresenceuniquenessで、どちらもUserモデルのemailカラムにうってつけです。以下のようにマイグレーションを作成して追加します。

class AddValidationOnUserEmail < ActiveRecord::Migration
  def change
    change_column :users, :email, :string, null: false
    add_index :users, :email, unique: true
  end
end

rake db:migrateを実行すればバリデーションをテストできます。モデルのバリデーションを呼び出さないupdate_columnメソッドを使ってみましょう。メールアドレスのないユーザーを試しに保存してみます。

user = User.find(user_id)
user.update_column(:email, nil) # => raises ActiveRecord::StatementInvalid: Mysql2::Error: Column 'email' cannot be null

ちゃんとエラーがraiseされました。今度はメールアドレスが重複しているユーザーを保存してみます。

user = User.find(user_id)
user_2 = User.find(user_2_id)

user.update_column(:email, user_2.email)
# => raises ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry

2つのバリデーションは種類が異なります。presenceバリデーションはカラム定義に渡せばよいのですが、uniquenessバリデーションの場合はデータベースに正しいインデックスを作成しなければなりません。このエラーは、無効なデータを保存しようとしたときにもraiseされます。

user = User.find(user_id)
user.update_column(:created_at, "string")
# => raises ActiveRecord::StatementInvalid: Mysql2::Error: Incorrect datetime value

ご覧いただいたように、パフォーマンス上の意味だけではなく、セキュリティのためにも、カラムを慎重に定義して正しくインデックスを作成することが重要です。

4. フロントエンドのバリデーション

最も安全性の低いバリデーションです。ブラウザでJavaScriptをオフにしたり、コードを使ってリクエストを直接送信したり、Postmanなどのブラウザ拡張を使ったりすれば、このバリデーションをバイパスできます。

データの保護はどんな場合であっても、バックエンド側のバリデーションで最初に行うべきです。しかしフロントエンドのバリデーションは、ユーザーエクスペリエンス向上には最も適しています。私は、フォーム送信を待たずにアプリがその場でフォームのエラーを表示してくれるのが好きです。

新着記事を見逃したくない方はTwitter(訳注: 現在はありません)をフォローしてください。もちろん「hello」だけでも構いません!

お知らせ: RSpec & TDDの電子書籍を無料でダウンロード

もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。

訳注

上記リンクは現在は以下にリダイレクトされます。

関連記事

Rails tips: カスタムバリデータクラスを作る(翻訳)

Rails: Form Objectと`#to_model`を使ってバリデーションをモデルから分離する(翻訳)

The post Rails tips: 知らないと損する4つのバリデーションレベル(翻訳) first appeared on TechRacho.

Rails 7.0.0.rc1がリリースされました

Image may be NSFW.
Clik here to view.

Ruby on Rails 7.0.0.rc1がリリースされました。

英語版Changelogをまとめて見るにはGItHubのリリースタグ↓が便利です。v7.0.0.rc1タグの日付は日本時間の12月7日6:39でした。

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

現時点のマイルストーンには8件のissueがあります。

なお、Rails 7のアセットパイプライン周りについては以下の記事がおすすめです。

参考: Rails 7.0でアセットパイプラインはどう変わるか | Wantedly Engineer Blog

Image may be NSFW.
Clik here to view.
🔗
更新の概要

以下は上記リリースノートの要点に補足を加えたものです。

フロントエンドへの新たなる回答

  • ブラウザでのES6/ESMサポート改善
  • HTTP/2を広範囲に採用
  • import mapsの導入(importmap-rails gem)
  • Node(npmパッケージ)なしのアプローチを可能に
    • node_modulesに大量の依存関係を置かないアプローチ

Image may be NSFW.
Clik here to view.
rails/importmap-rails - GitHub

参考: JavaScriptのバンドルとトランスパイルが不要なモダンWebアプリ | POSTD
参考: Rails 7 will have three great answers to JavaScript in 2021+

Rails 7: import-map-rails gem README(翻訳)

  • HotwireとStimulusとTurboの組み合わせにより、Railsアプリケーションでのフロントエンドのセットアップの完成度が最も高まった

参考: HTML Over The Wire | Hotwire
参考: Stimulus: A modest JavaScript framework for the HTML you already have.
参考: Turbo: The speed of a single-page web application without having to write any JavaScript.

  • Railsと、JavaScriptやCSSとのバンドル方法が、import mapsとjsbundling-railsやcssbundling-railsなどによって大きく改善された
    • jsbundling-rails: –javascript バンドラー名-j JSバンドラー名も可)で以下のJSバンドラーを指定可能
    • CSSバンドラーを指定できるようになった
    • cssbundling-rails: –css バンドラー名-c CSSバンドラー名も可)で以下のCSSバンドラーを指定可能

Image may be NSFW.
Clik here to view.
rails/jsbundling-rails - GitHub

Image may be NSFW.
Clik here to view.
rails/cssbundling-rails - GitHub

Active Recordの暗号化機能

Rails 7のActive Record暗号化機能(翻訳)

参考: Active Record Encryption — Ruby on Rails Guides

marginalia gemがActive RecordのQueryLogに

  • Basecampから切り出されたmarginalia gemがActive RecordのQueryLogモジュールとして取り入れられた

Image may be NSFW.
Clik here to view.
basecamp/marginalia - GitHub

参考: Mini tech note: MySQL query comments in Rails – Signal v. Noise

Relation#load_async

  • コントローラのアクションで2つの無関係なクエリを読み込む必要がある場合、Relation#load_asyncでコンカレントに読み込めるようになった(#41372

Zeitwerkに移行完了

  • オートローダーがZeitwerkのみとなった

Image may be NSFW.
Clik here to view.
fxn/zeitwerk - GitHub

参考: 定数の自動読み込みと再読み込み (Zeitwerk) - Railsガイド
参考: Upgrading Ruby on Rails — Ruby on Rails Guides

その他

  • spring gemがデフォルトではなくなった
    • (Gemfileではコメントアウトされます)

Image may be NSFW.
Clik here to view.
rails/spring - GitHub

  • コントローラのアクション内でオンザフライ生成されるファイルをActionController::Live#send_streamで手軽にストリーミング(#41488
  • CPUコア数とテスト数を比較してテストのパラレル化をスケールできるようになった(#42761
  • Active StorageのデフォルトのvariantプロセッサがImageMagickからlibvipsに変更(#41488

Image may be NSFW.
Clik here to view.
libvips/libvips - GitHub

参考: Rails 7.0.0.rc1のお試し

YJITを有効にしたRuby 3.1.0-preview1上でRails 7.0.0.rc1のrails newを手軽に試すためのdocker-compose環境を以下のリポジトリに取り急ぎ作りました。

Image may be NSFW.
Clik here to view.
hachi8833/rails7rc1_ruby310p1 - GitHub

以下は少し試してみた結果です。

  • propshaftはRails 7.0.0.rc1ではデフォルトでは入りませんでした
    • rails new-a propshaftオプションを付けて実行するとインストールされます

Image may be NSFW.
Clik here to view.
rails/propshaft - GitHub

# Gemfile
# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"

Image may be NSFW.
Clik here to view.
rails/kredis - GitHub

関連記事

Rails 7 Alpha 1、2がリリースされました(リリースノート翻訳)

The post Rails 7.0.0.rc1がリリースされました first appeared on TechRacho.

週刊Railsウォッチ: RailsでGDPRに対応する、stateful_enum gem、rubyzip 3.0ほか(20211214前編)

Image may be NSFW.
Clik here to view.

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

Image may be NSFW.
Clik here to view.
🔗
Rails: 先週の改修(Rails公式ニュースより)

今週はRails 7 RC1が出たこともあってか公式の更新情報がなかったので、以下の残りおよび差分より拾いました。リリースが近いせいか、ガイドの更新が増えています。

Image may be NSFW.
Clik here to view.
🔗
自動でshardをスワップするミドルウェアが追加

自動shardスワップ用ミドルウェアを追加。
shardの自動スワップを行う基本的なミドルウェアを提供する。アプリケーションは個別のリクエストに対してどのシャードを使うべきかを決定するリゾルバを提供する。例:

config.active_record.shard_resolver = ->(request) {
  subdomain = request.subdomain
  tenant = Tenant.find_by_subdomain!(subdomain)
  tenant.shard
}

詳しくはガイドを参照。
Eileen M. Uchitelle, John Crepezzi
同Changelogより


つっつきボイス:「shardのリゾルバをlamda(->)でコンフィグに書けるようになった」「ミドルウェアでできるようになったとありますね」「このコンフィグに書いておくことで、自分が決めたロジックでshardをミドルウェアレベルでスワップできるようになるということでしょうね」「なるほど」

「上のコンフィグの例がまさにそうですが、マルチテナントのデータベースはテナントごとにアクセスの多さがかなり違う、つまりテナントごとにlocality(局所性)が大きく違うことが多い」「ふむふむ」「上のコンフィグのように、たとえばIPアドレスやドメイン名でshardを分けると、キャッシュが乗りやすいような形できれいに分散できるでしょうね」「あ〜たしかに」「Active Recordのコードでconnected_toを書いて自分でshardを選ばなくてもミドルウェアレベルでresolveできるようになって便利になったということだと思います」「なるほど!」「ごく一部をconnected_toでやるならともかく、広範囲でやると構成の変更も難しくなるので、ミドルウェアでできるのはいいですねImage may be NSFW.
Clik here to view.
👍

Image may be NSFW.
Clik here to view.
🔗
merge_target_listsのpersistedレコードとインメモリレコードの重複を解消

merge_target_listsは2つの引数(persistedレコードのコレクション、インメモリレコードのコレクション)で呼び出される。persistedレコードがインメモリコレクションにも含まれていたとしても、persistedコレクションはすでに最新なので安全にrejectできる。
修正: #43516
同PRより


つっつきボイス:「merge_target_listsはprivateメソッドなので内部の修正ですね」「merge_target_listsはおそらく、データベース上で既に永続化されている(persisted)レコードと、まだメモリ上にあるレコードをマージするメソッドらしい」「メモリ上のレコードとpersistedレコードに重複があると#43516の問題が起きるので、以下のようにrejectで重複を取り除いだということのようですね」

# activerecord/lib/active_record/associations/collection_association.rb#L338
-         persisted + memory
+         persisted + memory.reject(&:persisted?)

Image may be NSFW.
Clik here to view.
🔗
レコード削除のエラーメッセージにレコードのクラスも表示


つっつきボイス:「お〜、_raise_record_not_destroyedのときに、削除に失敗したクラス名とキーも表示するようになった」「たしかに”Failed to destroy the record”だけだと、どのレコードで失敗したのかわからないですね」「そういうときにRailsのログを探し回るというのを何度となくやってきました」「これは出る方が便利Image may be NSFW.
Clik here to view.
👍
: ユーザーへのエラーメッセージで見せたくない場合には注意が必要かもしれませんが」

# activerecord/test/cases/associations/has_many_associations_test.rb#L2787
  test "raises RecordNotDestroyed when replaced child can't be destroyed" do
    car = Car.create!
    original_child = FailedBulb.create!(car: car)
    error = assert_raise(ActiveRecord::RecordNotDestroyed) do
      car.failed_bulbs = [FailedBulb.create!]
    end

    assert_equal [original_child], car.reload.failed_bulbs
-   assert_equal "Failed to destroy the record", error.message
+   assert_equal "Failed to destroy FailedBulb with #{FailedBulb.primary_key}=#{original_child.id}", error.message
  end

Image may be NSFW.
Clik here to view.
🔗
in_order_ofの修正

データベースにintegerとして保存されたenumにin_order_ofを使った場合の動作が期待どおりにならない。

class User < ApplicationRecord
  enum role: [:normal_user, :admin, :super_user]
end

# 現時点では期待どおりの結果が返らない
User.in_order_of(:role, %w[super_user normal_user admin])

これは”super_user”、”normal_user”、”admin”がデータベース内のintegerと比較されているのが原因。データベース内のintegerとこれらの文字列を比較できるようにするには、文字列をtype_cast_for_databaseで変換してデータベース内のintegerとマッチさせる必要がある。
同PRより


つっつきボイス:「in_order_ofのバグが修正」「type_cast_for_databaseでキャストすることでenumを正しく比較できるようにしたんですね」

# #L
    def in_order_of(column, values)
      klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
      references = column_references([column])
      self.references_values |= references unless references.empty?

+     values = values.map { |value| type_caster.type_cast_for_database(column, value) }
      column = order_column(column.to_s) if column.is_a?(Symbol)

      spawn.order!(connection.field_ordered_value(column, values))
    end

Image may be NSFW.
Clik here to view.
🔗
Railsガイドでレコード削除の例をlink_toからbutton_toに変更(現在オープン)


つっつきボイス:「まだマージされていないプルリクですが、Rails 7 RC1を試していて自分もこれを踏んでしまったので取り上げてみました」「お、そういえばRails 7からUJSのサポートがなくなることになっていますね」「UJSの代わりに今後はTurbo Driveになるんですが、Railsガイドもそれに合わせて整合性を取らないといけないという流れです」「こういう部分も試してくれているのはいいですねImage may be NSFW.
Clik here to view.
👍

参考: 控えめなJavaScript - Wikipedia — Unobtrusive JavaScript(UJS)

# guides/source/getting_started.md#L1288
<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
- <li><%= link_to "Destroy", article_path(@article),
-                 method: :delete,
-                 data: { confirm: "Are you sure?" } %></li>
+ <li><%= button_to "Delete Article", { id: @article.id}, method: :delete %>
</ul>

「今まではrails-ujsのJavaScriptコードがDestroyのlink_toに自動で<form>タグを付けてくれましたけど、Turbo Driveに変わるとそのあたりの挙動も変わるようですね: ちなみにDestroyはHTTPのPOSTメソッドを使います」「Destroy操作はリンクよりボタンがよいのではということでbuttun_toが提案されていました」「言われてみれば本来<a>タグは文書間を移動するハイパーリンクのためのものなので、その<a>タグでDestroyやCreateのような改変操作を行うのはいかがなものかと考えることもできますね」「どこに落ち着くかが気になってます」

なおrails-ujsは以前はgemでしたが、現時点ではRailsに取り込まれています↓。

参考: Add rails-ujs to Action View · rails/rails@ad3a477

Image may be NSFW.
Clik here to view.
🔗
Rails

Image may be NSFW.
Clik here to view.
🔗
RailsでGDPRに対応する(Ruby Weeklyより)


つっつきボイス:「GDPRとアカウント削除、これはちゃんとやろうとするとなかなか大変なヤツ: この種の対応でよくあることですが、たとえばログに出力されたものも適切に処理しないといけなくなる」「GDPRは最近Webサイトの通知でよく表示されていますね」

参考: EU一般データ保護規則(GDPR) - Wikipedia
参考: GDPR |個人情報保護委員会

「S3とかに保存したユーザーログも必要に応じて消さないといけなくなるんでしょうか?」「本来的にはそうなるだろうと思います: その意味では、ログを設計するときには個人情報になりうるものはなるべくログに出さないようにしておいて、使うときだけBIツールなどでその都度JOINして使うといった工夫が必要になるでしょうね」「ふむふむ」

参考: BIツール - Wikipedia

「日本の個人情報保護法などもそうですが、削除せよと指示を受けたら削除できるようにしておく必要があります: もちろんECサイトの注文履歴情報のような、ユーザー自身の個人情報ではない企業側の持つ情報は残してもよいことになっているので、そういう情報と個人情報はなるべく切り離して別テーブルなどに置くなどするのが肝心でしょうね」「たしかに」「正規化も個人情報がログに出ないようにうまくやる必要がありそう」

参考: 個人情報の保護に関する法律 - Wikipedia
参考: 関係の正規化 - Wikipedia

「記事を書いた会社ではこういうライフサイクルでユーザーデータを扱っているらしい↓」「データの保持をactive database/intermediate archiving/deletionというフェイズに分けているけど、GDPRの定義とは別に自分たちで定義しているみたいですね」

Image may be NSFW.
Clik here to view.

同記事より

Image may be NSFW.
Clik here to view.
🔗
rubyzipが3.0に

Image may be NSFW.
Clik here to view.
rubyzip/rubyzip - GitHub


つっつきボイス:「最近Rails 7 RC1でrails newしていたら以下のメッセージが表示されたことでrubyzipが更新されたことを知りました: Railsではロックされるので基本的に大丈夫ですが、3.0でインターフェイスがモダンになったので新しい方を使うときは注意だそうです」「breaking changesが入るんですね」

RubyZip 3.0 is coming!


The public API of some Rubyzip classes has been modernized to use named
parameters for optional arguments. Please check your usage of the
following classes:
* Zip::File
* Zip::Entry
* Zip::InputStream
* Zip::OutputStream
Please ensure that your Gemfiles and .gemspecs are suitably restrictive
to avoid an unexpected breakage when 3.0 is released (e.g. ~> 2.3.0).
See https://github.com/rubyzip/rubyzip for details. The Changelog also
lists other enhancements and bugfixes that have been implemented since
version 2.3.0.

「rubyzipは定番として長く使われていますけど、もうひとつziprubyというgemも昔からあるんですよ↓」「ネーミングが逆なんですね」「実はziprubyもrubyzipも名前空間がZipで始まるので、両方requireすると壊れます」「ありゃ〜」

Image may be NSFW.
Clik here to view.
fjg/zipruby - GitHub

「ところで、普段は気軽にzipを使いますけど、要件によってはzipの仕様がすごく細かく指定されることがありますよね: EPUBだとmimetypeは非圧縮zipにしないといけないとか」「非圧縮だとtarみたいな感じですね」

参考: .NETの System.Io.Compression で非圧縮のZIPファイルを作れなかったが対象が固定だったので直接バイナリファイル作って強引に解決した話 - 木俣ロバート久の覚書 - Hatena Blog
参考: EPUB - Wikipedia

Image may be NSFW.
Clik here to view.
🔗
stateful_enum gem

Image may be NSFW.
Clik here to view.
amatsuda/stateful_enum - GitHub


つっつきボイス:「amatsudaさんの作ったgemで、DiscordのRubyチャンネルでamatsudaさんがこのgemのことをつぶやいていたのを見て知りました」

# 同リポジトリより
class Bug < ApplicationRecord
  enum status: {unassigned: 0, assigned: 1, resolved: 2, closed: 3} do
    event :assign do
      transition :unassigned => :assigned
    end

    event :resolve do
      before do
        self.resolved_at = Time.zone.now
      end

      transition [:unassigned, :assigned] => :resolved
    end

    event :close do
      after do
        Notifier.notify "Bug##{id} has been closed."
      end

      transition all - [:closed] => :closed
    end
  end
end

「aasmというステートマシンgem↓のようなことをRailsのenumでやる感じに見えますね: aasm doで書く代わりにenum doで書くのが違うけど他は似ているかな」「aasm gemは超定番ですよね」「aasmは昔からあってよく使いますけど、stateful_enumはRailsの機能を使っている分よさそうに見えるImage may be NSFW.
Clik here to view.
❤

Image may be NSFW.
Clik here to view.
aasm/aasm - GitHub

# aasm/aasmより
class Job
  include AASM

  aasm do
    state :sleeping, initial: true
    state :running, :cleaning

    event :run do
      transitions from: :sleeping, to: :running
    end

    event :clean do
      transitions from: :running, to: :cleaning
    end

    event :sleep do
      transitions from: [:running, :cleaning], to: :sleeping
    end
  end

end

参考: 有限オートマトン - Wikipedia

「stateful_enumはActive Recordのenumを使うんですね」「aasmはより一般的なつくりになっていますが、aasmもActive Recordで使えますよ↓」「あ、そうでしたか」「enumはあくまでRailsのDSLですね」

# aasm/aasmより
class Job < ActiveRecord::Base
  include AASM

  aasm do # default column: aasm_state
    state :sleeping, initial: true
    state :running

    event :run do
      transitions from: :sleeping, to: :running
    end

    event :sleep do
      transitions from: :running, to: :sleeping
    end
  end

end

Image may be NSFW.
Clik here to view.
🔗
CableReadyがRuby以外の言語にも対応を表明(Ruby Weeklyより)

Image may be NSFW.
Clik here to view.
stimulusreflex/cable_ready - GitHub

Image may be NSFW.
Clik here to view.
stimulusreflex/stimulus_reflex - GitHub


つっつきボイス:「記事にはRailsのAction Cable的なJSONやコードが出てきてますね↓: CableReadyはAction Cableのメッセージや多重化周りを扱いやすくしてくれる感じかな」「CableReadyが来年にGoやPythonなどの言語もサポートするとも書かれていました」

# 同記事より
[{\"message\":\"Hello!\",\"operation\":\"consoleLog\"}]
# 同記事より
cable_ready[:foo].operation(options).broadcast

参考: Action Cable の概要 - Railsガイド
参考: Rails: StimulusReflexとCableReadyでチャット機能を作ってみる - Qiita

「CableReady v5.0 pre-releaseのサイトを見るとこんなことが書かれてる↓」「複雑なSPAフレームワークなしにリアクティブにできるのね」「でも機能が足りなくて結局ReactとSPAを使ったりしているのをよく見かけますけどねImage may be NSFW.
Clik here to view.
😆
」「StimulusJSやTurboはSPAに走らない方向で頑張っている印象あります」

CableReady offers 36 different operations that let you create reactive user experiences without the need for complex SPA frameworks.
cableready.stimulusreflex.comより

参考: Welcome - CableReady
参考: シングルページアプリケーション(SPA) - Wikipedia


前編は以上です。

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

週刊Railsウォッチ: 改訂2版『プロを目指す人のためのRuby入門』、『研鑽Rubyプログラミング β版』ほか(20211207後編)

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

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

Rails公式ニュース

Image may be NSFW.
Clik here to view.

Ruby Weekly

Image may be NSFW.
Clik here to view.

The post 週刊Railsウォッチ: RailsでGDPRに対応する、stateful_enum gem、rubyzip 3.0ほか(20211214前編) first appeared on TechRacho.

週刊Railsウォッチ: Ruby 3.1のエラー表示改善、言語の「プリミティブ」を探る、Amazon Redshift Serverlessほか(20211215後編)

Image may be NSFW.
Clik here to view.

こんにちは、hachi8833です。明日はRubyWorld Conference 2021ですね。

週刊Railsウォッチについて

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

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

Image may be NSFW.
Clik here to view.
🔗
Ruby

Image may be NSFW.
Clik here to view.
🔗
Advent of Code 2021(Ruby Weeklyより)


つっつきボイス:「Advent of Codeの2021年版が公開されています: Ruby限定ではありませんが、前回は以下の翻訳記事でも取り上げられています」「お、去年もこのサイトを見た覚えある」「CLIっぽい独特のWebレイアウトですね」「2015年から開催されてるのね」

Ruby 3.0でアドベント問題集を解く(1日目)修正のレポート(翻訳)

Image may be NSFW.
Clik here to view.
🔗
throwcatchraiserescueで迷った話(Ruby Weeklyより)


つっつきボイス:「複数の言語を扱っているとこのあたりは迷いがち」「Javaをやったことがあるとそうなりそうですね」「Rubyでthrowcatchをこんなふうに書けるとは知らなかった↓」「Rubyだと普段はraiserescueしか使わないかな」

# 同記事より
def show_rank_for(target, query)
  rank = catch(:rank) {
    each_google_result_page(query, 6) do |page, page_index|
      each_google_result(page) do |result, result_index|
        if result.text.include?(target)
          throw :rank, (page_index * 10) + result_index
        end
      end
    end
    "<not found>"
  }
  puts "#{target} is ranked #{rank} for search '#{query}'"
end

参考: Kernel.#throw (Ruby 3.0.0 リファレンスマニュアル)
参考: Kernel.#catch (Ruby 3.0.0 リファレンスマニュアル)
参考: Kernel.#fail (Ruby 3.0.0 リファレンスマニュアル)raiseはエイリアス)
参考: 制御構造 (Ruby 3.0.0 リファレンスマニュアル)rescueは修飾子)

Image may be NSFW.
Clik here to view.
🔗
言語の「プリミティブ」を探る(Ruby Weeklyより)


つっつきボイス:「『Rubyのしくみ(Ruby Under Microscope)』の著者のPat Shaughnessyさんによる記事です」「RubyとCrystal言語でArrayクラスの動作を掘り下げて詳しく調べている」「CrystalのプリミティブがRubyよりもかなり複雑で著者も驚いてるみたいですね」

Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

参考: Crystal (プログラミング言語) - Wikipedia
参考: The LLVM Compiler Infrastructure Project

「ところで普段からRubyをRubyらしく書くようにしていると、他の言語ではRubyよりコードの行数が増えることに改めて気付かされますね」「普段はまず使いませんけど、Rubyにも他の言語のforとかもある」「forは高速だけど、最適化がどうしても必要な場合以外は使うべきではないとよく言われますよね↓」「そうそう、Rubyのイテレーションはメソッド呼び出しだけど、forはプリミティブな構文なのでたしかに速い」

Ruby: eachよりもmapなどのコレクションを積極的に使おう(社内勉強会)

「Rubyには想像以上にいろんな文法やメソッドがあるので、それらを余さず使っていたら他の言語にピボットしやすいかも」「Rubyで関数型言語風や別の言語風に書く人もよく見かけますね」

Image may be NSFW.
Clik here to view.
🔗
jnchitoさんの「改訂版・チェリー本発売記念- Advent Calendar 2021」

「ちょうどこの間アドベントカレンダー記事で点字生成プログラムを書きましたけど↓、他の人のコードを見ると想像以上に書き方のバリエーションが大きいんですよ: Rubyってこんなに自由なんだと改めて思った次第です」

改訂版チェリー本発売記念tenji-maker-challengeを解いてみた

「今回やってみて、問題設定がうまいなと思いました: 例外系をほとんど考慮しなくて済むようになっているので、Rubyを書き慣れていれば1〜2時間ぐらいで書ける」「なるほど」「他の方の回答も面白いのでぜひ見てください」

「jnchitoさんのレビュー動画↓はコード以外に記事もレビューしているんですね」「課題に記事の要件も含まれているのでそこをチェックしているということですね」

Image may be NSFW.
Clik here to view.
🔗
countries: 国関連の情報をまとめたgem(Ruby Weeklyより)

Image may be NSFW.
Clik here to view.
countries/countries - GitHub


つっつきボイス:「ISOの標準に沿った国情報をまとめたgemのようですね」

参考: ISO 3166 - Wikipedia

「国の名前も別名や別の言語での名前まで取れる↓」「これもISOで定められている情報かもしれませんね」

# 同リポジトリより
c.name # => "United States"
c.unofficial_names # => ["United States of America", "Vereinigte Staaten von Amerika", "États-Unis", "Estados Unidos"]

# Get the names for a country translated to its local languages
c = Country[:BE]
c.local_names # => ["België", "Belgique", "Belgien"]
c.local_name # => "België"

# Get a specific translation
c.translation('de') # => 'Vereinigte Staaten von Amerika'
c.translations['fr'] # => "États-Unis"

ISO3166::Country.translations         # {"DE"=>"Germany",...}
ISO3166::Country.translations('DE')   # {"DE"=>"Deutschland",...}
ISO3166::Country.all_translated       # ['Germany', ...]
ISO3166::Country.all_translated('DE') # ['Deutschland', ...]

「国のsubdivisionsも取れるとは↓」「そういえば米国にも州を表す2文字のコードがありますね」

# 同リポジトリより
c.subdivisions # => {"CO" => {"name" => "Colorado", "names" => "Colorado"}, ... }
c.states # => {"CO" => {"name" => "Colorado", "names" => "Colorado"}, ... }

参考: アメリカ合衆国各州の略号一覧 - Wikipedia

「緯度経度の情報も取れるのね↓」

# 同リポジトリより
c.latitude # => "38 00 N"
c.longitude # => "97 00 W"
c.latitude_dec # => 39.44325637817383
c.longitude_dec # => -98.95733642578125

c.region # => "Americas"
c.subregion # => "Northern America"

「タイムゾーンはtzinfo gemが必要なんですね」「ちゃんと配列で取れる↓」「タイムゾーンは1つの国に複数あったりサマータイムも絡んだりしてややこしい(ウォッチ20210713)」

# 同リポジトリより
c.timezones.zone_identifiers # => ["America/New_York", "America/Detroit", "America/Kentucky/Louisville", ...]
c.timezones.zone_info  # see [tzinfo docs]( http://www.rubydoc.info/gems/tzinfo/TZInfo/CountryInfo)
c.timezones # see [tzinfo docs]( http://www.rubydoc.info/gems/tzinfo/TZInfo/Country)

「telephone routingとかあまり使わなそうな情報まで取れるんですね↓」

# 同リポジトリより
c.country_code # => "1"
c.national_destination_code_lengths # => 3
c.national_number_lengths # => 10
c.international_prefix # => "011"
c.national_prefix # => "1"

money gemを入れると通貨も取れるとあるけど、通貨が複数ある国はどうなるんだろうImage may be NSFW.
Clik here to view.
🤔

# 同リポジトリより
c = ISO3166::Country['us']
c.currency.iso_code # => 'USD'
c.currency.name # => 'United States Dollar'
c.currency.symbol # => '$'

「このgemをすぐに使うかどうかはわかりませんが、ISOでこんなにいろんな情報が定義されているということが見えてきて面白いですねImage may be NSFW.
Clik here to view.
👍

Image may be NSFW.
Clik here to view.
🔗
Ruby 3.1のエラー表示改善


つっつきボイス:「これはいいImage may be NSFW.
Clik here to view.
👍
」「ネステッドハッシュのどこでエラーが起きたかがひと目でわかるのありがたいです」

Image may be NSFW.
Clik here to view.
🔗
その他Ruby

つっつきボイス:「今年のRubyリリースイベントは12/27(月)なのね」「この間申し込みました」「忘れないうちにポチってGoogleカレンダーにも入れようっと」


「『研鑽Rubyプログラミング』、早く日本語読みたい」「レビューに協力すると早まるかもしれませんよ」「おぉ」「Scrapboxにもいろいろ情報が上がっていますね↓」

参考: 『研鑽Rubyプログラミング』をScrapboxする

募集期間
* 12/14(火) 18:00 頃を想定していますが、応募多数の場合は早めに締め切ることもあります。悪しからずご了承ください。
同フォームより

以下で同β版へのフィードバックが行われています↓

Image may be NSFW.
Clik here to view.
LambdaNote/polished-ruby-ja-feedbacks-beta1 - GitHub

Image may be NSFW.
Clik here to view.
🔗
クラウド/コンテナ/インフラ/Serverless

Image may be NSFW.
Clik here to view.
🔗
Amazon Redshift Serverless(Serverless Statusより)


つっつきボイス:「AWS re:Invent 2021で発表されたAmazon Redshift Serverlessですね」「お〜これですか」「これはなかなかいいところを衝いているサービスだと思いましたImage may be NSFW.
Clik here to view.
👍

参考: 週刊AWS – re:Invent 2021特別号(2021/11/29週) | Amazon Web Services ブログ

「お〜、Webベースのクエリエディタがこんなによくなっている↓」「Redshiftでこんなのが使えるとは」

Image may be NSFW.
Clik here to view.

aws.amazon.comより

「Redshift ServerlessがMySQLのサーバーレスと同じスタンスだとしたら、初回の起動だけ少し時間がかかって、後は使っていないときは自動的に落ちるみたいな感じになるんじゃないかな」

参考: Serverless MySQL データベースを設定し、接続する方法 | AWS

「Redshift Processing Unitsで1秒単位の課金ということはクエリを投げなければ課金されないということでしょうね」「お〜、今までだとRedshiftのインスタンスを起動している時間は課金されてたのがServerlessだと変わるんですねImage may be NSFW.
Clik here to view.
❤

Amazon Redshift Serverless では、使用するコンピューティングとストレージに対して別途料金を支払います。コンピューティング性能は Redshift Processing Units (RPU) で測定され、ワークロードの料金は RPU 時間で 1 秒単位で請求されます。ストレージについては、Amazon Redshift が管理するストレージに保存されたデータと、スナップショットに使用したストレージに対して課金されます。これは、RA3 インスタンスを使用してプロビジョニングされたクラスターで支払うのと同じです。
aws.amazon.comより

「これまでのRedshiftは、まともなパフォーマンスを出そうとすると結構強力なインスタンスを買う必要があってミニマムコストがかさむので、使い始めるまでの敷居が高かったんですが、Redshift Serverlessが完全従量課金ならコンピューテーションパワーの必要なものに使いやすくなりますねImage may be NSFW.
Clik here to view.
👍
」「お〜」「DynamoDBにも定額課金と完全従量課金があるので使い始めやすい↓」

参考: Amazon DynamoDB(マネージド NoSQL データベース)| AWS

Image may be NSFW.
Clik here to view.
🔗
CSS/HTML/フロントエンド/テスト/デザイン

Image may be NSFW.
Clik here to view.
🔗
モダンなフロントエンド開発者の学習ロードマップ

Image may be NSFW.
Clik here to view.

同サイトより


つっつきボイス:「マップになっているのが面白いですね」「フロントエンド開発者はこの図にあることを学びなさいという感じですか」

「この種の学習マップは唯一の正解があるものではないので、一度自分で作ってみて可視化する方が学習効果は高いでしょうね」「たしかに」「そのうえでこのマップと比較して自分に足りないものを確認するとはかどりそう」「開発チームがこういうマップを独自に提示して、自分のチームではこういうものが必要だよというのを示すのもよさそうですね」

「GitHubリポジトリがあって、詳細情報はみんなで足していってくれという感じのようです↓」「★18万超えなのか」

Image may be NSFW.
Clik here to view.
kamranahmedse/developer-roadmap - GitHub

「READMEを見ると他にもロードマップあるんですね↓」「ReactやらPythonやら」「Rubyもあればいいのに」


RubyとRailsだと以下のようなかわいらしい技術マップもありますね(RubyとRailsの学習ガイド 2021を買うと裏表紙により詳細な技術マップがあります)。

参考: RubyとRailsの学習ガイド 2021 | Techpit


後編は以上です。

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

週刊Railsウォッチ: RailsでGDPRに対応する、stateful_enum gem、rubyzip 3.0ほか(20211214前編)

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

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

Ruby Weekly

Image may be NSFW.
Clik here to view.

Serverless Status

Image may be NSFW.
Clik here to view.
serverless_status_banner

The post 週刊Railsウォッチ: Ruby 3.1のエラー表示改善、言語の「プリミティブ」を探る、Amazon Redshift Serverlessほか(20211215後編) first appeared on TechRacho.

Railsセキュリティ修正6.0.4.4/6.1.4.4がリリースされました

Image may be NSFW.
Clik here to view.

Ruby on Rails セキュリティ修正6.0.4.4/6.1.4.4がリリースされました。

当初は6.0.4.2/6.1.4.2/7.0.0.rc2がリリースされましたが、localhost:3000でアクセスできない問題(#43865)があったので6.0.4.3/6.1.4.3/7.0.0.rc3がリリースされました。しかしlvh.me:3000のようにポート番号を指定できない問題(#43864)があったので、最終的に6.0.4.4/6.1.4.4がリリースされました。

Rails 7.0.0.rc2および7.0.0.rc3については、これらの修正を含むRails 7.0.0最終版がリリースされましたので、以下の記事をご覧ください。

Rails 7.0.0がリリースされました

リリースタグは以下です。

コミットの比較は以下です。

Image may be NSFW.
Clik here to view.
🔗
セキュリティ修正の概要

Image may be NSFW.
Clik here to view.
🔗
Possible Open Redirect in Host Authorization Middleware - Security Announcements - Ruby on Rails Discussions

詳しくは上のリンクをご覧ください。

以下のようにドットで始まるホストをコンフィグのconfig.hostsで許可すると、悪意のあるX-Forwarded-HostによってAction PackのHost Authorizationミドルウェアがユーザーを悪意のあるWebサイトにリダイレクトする可能性があります。

config.hosts << '.EXAMPLE.com'
影響を受けるRailsバージョン
Rails 6.0.0以降
影響を受けないRailsバージョン
Rails 5.x以前
修正済みバージョン
6.1.4.2以降、6.0.4.2以降、7.0.0.rc2以降

修正用パッチ

アップグレードができない場合に用いるパッチです。


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

関連記事

Railsセキュリティ修正6.0.4.1と6.1.4.1 がリリースされました

The post Railsセキュリティ修正6.0.4.4/6.1.4.4がリリースされました first appeared on TechRacho.

Viewing all 1836 articles
Browse latest View live