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

週刊Railsウォッチ: rubygemsに「scoped gems」の提案、RSpecのブロック構文ほか(20220517後編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 scoped gemsの提案(Ruby Weeklyより)


つっつきボイス:「scoped gemsという名前の提案がRubyGemsのRFCsに上がっているそうです」

RubyGemsチームおよびこれを読んでる皆さんへ。

このプルリクには、「scoped gems」と呼ばれる機能の提案が盛り込まれています。要するに、gemの命名仕様を拡張して、関連するgemを特定の組織で予約したサフィックスでグループ化する新しい@を含めようという提案です。
命名パターンはgem名@スコープ名に沿います。
最初のgemをプッシュ(新規レコード)では、そのgemがこのパターンに沿ってscopedになっていれば、そのgemのスコープは、そのスコープを予約した組織のユーザーによって作成されたことが検証されます。scoped gemは、現在の通常のgemと同様にインストールすることもrequireもできます。

たとえば、aws-sdk-s3というAWSのS3 gemで考えてみましょう。
このgemがscopedだとすると、s3@aws-sdk(一般化するとサービス名@aws-sdk)という形で公開されます。s3@aws-sdkは、AWSというorganization(ここがaws-sdkというスコープを予約する)に所属するユーザーだけが作成できます。
一般ユーザーは、gem install s3@aws-sdkでこのgemをインストールしてrequire 's3@aws-sdk'することになります。

scoped gemsの主なメリットは、organizationが独自のgemグループを公開できること(つまり複数のorganizationが"コンフィグ"gemを持てる)、組織がgem名を予約できること(@スコープ名を予約済みプレフィックス的に使う)です。
開発者は、たとえば新しいnew-cool-feature@railsがRails公式のgemであることや、new-s3-service@aws-sdkが公式のAWS SDKクライアントであることや、socket@rubyがRuby公式のstdlib gemであることを合理的に確信できます。
この予約システムは、個人情報を盗み出そうとする"フェイク"gemが紛らわしい名前によって公式扱いされないようにするための対抗手段です。
お気づきの点があれば喜んで手法や設計を見直しますので、フィードバックをお願いします。
同PRより

gem名@スコープ名か、なるほど」「現在のRubygemsはgem名がグローバルなので、gem作者にとっては名前が衝突しないようgem名が長くなりがちですし、使う側にとっても違うgemを取ってきてしまったりという不便な点があるんですよ」「rubyzipとziprubyが同じ::Zipという名前空間を使っていて衝突するみたいなことが実際起こってますよね」

「問題はどういう仕様にするか」「👎を押してる人も数人いるぐらいなので、それぞれ意見はあるでしょうね」「スレッドを見るといろんな案がある↓」「スコープを前に置くか後ろに置くかとか」「ネストをどうするかも考えないといけないでしょうし」「@を使うとメールアドレスと間違えそうというのもちょっとわかる」

  • gem_name@scope
  • scope/gem_name
  • @scope/gem_name
  • --で区切る(rails--activerecordなど)
  • etc

「gemを作るようになってくると、こういう仕組みを整備しておくのはとても大事👍」「gem作者にとっては欲しい機能」「まだ議論中なので最終的にどうなるかは今後の流れ次第でしょうね」

🔗 Ruby 3.1の非互換変更がPsych 4に与えた影響(Ruby Weeklyより)


つっつきボイス:「Ruby 3.1でPsych 4に非互換が生じた件は、以前も話題にしたかな(ウォッチ20210518)」「少し前のRails 7.0.2でもPsych 4関連の修正が入ってましたね↓」

ruby/psych - GitHub

Rails 7.0.2がリリースされました

🔗 RSpecのブロック構文でメッセージのexpectationを書く(Ruby Weeklyより)


つっつきボイス:「RSpecのブロック構文?」「引数のチェックに便利みたいなことが書かれてるようです」

「お〜、こういうふうにreceiveのブロック(doend)の中に通常のexpectを書くのね↓: たしかにあまり見かけない方法かも」「なかなかよさそう👍」

# 同記事より
          expect(@dummy_client).to receive(:create) do |headers, body|
            expect(body[:name]).to eq "apollo creed"
            …
          end

「最終的にこんな感じになるのね↓」

# 同記事より
require "./user_builder"

RSpec.describe UserBuilder do
  before :each do
    @dummy_client = double("api client", {
      create: "Done",
      get_token: "90809080"
    })

    @params = {
      api_client: @dummy_client,
      name: "Apollo Creed",
      shoe_size: 11
    }
  end

  …

  describe "instance method" do
    before :each do
      @builder = UserBuilder.new(@params)
    end

    describe "#build" do
      it "should call the API client" do
        expect(@dummy_client).to receive(:create).and_return("Done")
        @builder.build
      end

      describe "API client call" do
        it "should set headers appropriately" do
          expect(@dummy_client).to receive(:create) do |headers, _body|
            expect(headers["Content-Type"]).to eq "application/json"
            expect(headers["Authorization"]).to eq "Bearer 90809080"
          end.and_return("Done")
          @builder.build
        end

        it "should set the body appropriately" do
          expect(@dummy_client).to receive(:create) do |_headers, body|
            expect(body[:name]).to eq "apollo creed"
            expect(body[:shoe_size]).to eq 11.5
          end.and_return("Done")
          @builder.build
        end
      end
    end
  end
end

参考: Support expect(object).to receive(xyz).with_block(block) · Issue #1182 · rspec/rspec-mocks


「ところで、例外を拾うテストはブロックでないと書けないみたいな話はありますね↓」「そうだったかも」

参考: RSpec でエラーを捉えらんないアレなミス - Qiita

🔗 その他Ruby(Ruby Weeklyより)

つっつきボイス:「珍しくRuby Weeklyで引用されていた@tenderloveさんのツイートです」「なるほどWasmでRailsをブラウザ内で動かしたいと」「Reactはサーバー側ですか😆」「続きのスレッドも盛り上がってる」「こういう夢は昔からみんなが見てましたね」

🔗 モバイルエージェント

「1990年代頃から研究されていたモバイルエージェント↓もそのひとつで、たとえばコードをユーザー側でも動かして、処理結果および処理内容が改ざんされていないかとうかを保証する研究などが行われています」「今だとブロックチェーンにも関連しそうですね」

参考: PDF モバイルエージェント技術と研究動向(2001年)
参考: ブロックチェーン - Wikipedia

「この分野の理論はいろいろ発達していて、改ざん防止もそうですけど、少し変わったものとしては、暗号化されている数値データを暗号化されたまま計算する一種のセキュリティ技術の研究を見かけた覚えがあります」「ということは...」「メモリ上に乗っている数値データが暗号化されたままなので、仮にメモリのデータを盗んでも読めない」「あ〜なるほど」(以下延々)

🔗DB

🔗 Cloudflare D1


つっつきボイス:「CloudflareのD1、話題になってますね」「SQLiteのDBファイルを使うんですか」

参考: Cloudflare D1 がヤバい

「D1は、Cloudflareが元々持っているファイル配布機能をSQLite DBファイルに応用したという感じですが、それをDBバックエンドとして使う点は新しいと思いました」

「せっかくなのでSlackに書いたコメントを引用しておきます↓」

発想としてはSQLite DBファイルを同じようにEdgeに載せるという話だとは思うけど、

  • 更新系のリクエストでRDBMSを使わなくて良い(Dynamo他を使える、トランザクション不要)
  • 更新系の変更が直ちにread系に反映されなくて良い
  • readしたいデータのサイズが小さめ、または高いlocalityをもつ

あたりを満たしているのであればアーキテクチャ上はありかもですね。
集計系クエリ使いたいみたいな話も、最近はDatalake的なものに集約してやろう、みたいな風潮もあるので解決できるのかも(中小規模だとクッソインフラ&データ集約コストが上がるので僕は懐疑的ですが)
ノウハウが溜まってくるまではエンジニア工数の方が高そうという問題はあるけど、自社サービスとかなら半分技術開拓のつもりでやってみる、とかはいけるのかもしれない
CDN edgeのlocalityとDBとしてのlocalityがどれくらい一致するユースケースがあるのか疑問だけど、データ取ったり色々すると面白いテーマな気がするなー

参考: データレイクとは

「読み取りだけでいいなら便利そう」「SQLiteは1ファイル1DBなので、データのlocality(局所性)が高ければ分割して配布もできそうに見える」

参考: 参照の局所性 - Wikipedia

🔗 CDNとデータベースのlocality

「自分が一般に気になるのは、CDNのEdgeのlocalityと、データベースのlocalityが実際のユースケースでどのぐらい一致するのかという点かな」「ふむふむ」「CDNのキャッシュの効き方もこういうlocalityに影響されるので、両者のlocalityが一致すればするほどキャッシュが効く可能性があるかもしれないと想像しています」「なるほど」

「Cloudflare D1を実際に使うかどうかはともかく、こういうのは研究向きのテーマだと思うので、実際にデータを取ってまとめたら面白いと思うんですよ」「なるほど」「個人的にはあまり効かないのではという気もしていますが、まだ知見が少ない分野ですし、やってみたらまったく違う結果になるかもしれないので、Cloudflareのような大手がデータを取ってくれたらありがたい」「クラウドの実験は大学の研究室より自前のインフラが使える大手が有利ですよね」「そうやって実用的なユースケースを明らかにして欲しい」

「ただCloudflare D1のようなものは、個人アプリレベルで使うならともかく、大規模な業務システムでいきなり導入すると、後から方向転換しにくくなる可能性があるかもしれない」「それはたしかに」

「更新がすぐに全体に反映されないという仕様は、要件定義や設計の自由度が下がるので、請負の案件で使うとなると顧客を説得しにくい場合もあるかもしれません: もちろん当日データの反映は翌日とする、みたいに要件をきちんと定義すれば十分可能ですが」


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

🔗 Cloudflare R2ストレージ

「個人的にはD1も面白いけど、同じCloudflareのR2ストレージというS3クローン的なサービスの方がコスト面も含めて興味あるかな↓」「R2はいつの間にか正式にリリースされていますね」

🔗 Railsで競合状態を回避する方法(Ruby Weeklyより)


つっつきボイス:「楽観的ロックや悲観的ロックの話が出てくるので、データベースの競合状態ですね」「こういうのをrace conditionって言うの知らなかった😅」

参考: 楽観ロック/悲観ロック(optimistic locking/pessimistic locking)とは - 意味をわかりやすく - IT用語辞典 e-Words
参考: 競合状態 - Wikipedia

「記事にもあるように、データベースのUNIQUEインデックスと、アプリケーション側のuniquenessバリデーションは違うことを知っておくのは大事」「Railsだと後者はActive Recordが行うものだから↓、前者の場合と結果が違ってくる可能性がありますよね」

# 同記事より
class User < ApplicationRecord
  validates :phone_number, uniqueness: true
end

「sidekiq-unique-jobsというgemを使ってSidekiqのジョブに一意性制約を追加する↓: こういう話題は何度か取り上げたことがありますね」

# 同記事より
Sidekiq.configure_server do |config|
  config.redis = { url: ENV["REDIS_URL"], driver: :hiredis }

  config.client_middleware do |chain|
    chain.add SidekiqUniqueJobs::Middleware::Client
  end

  config.server_middleware do |chain|
    chain.add SidekiqUniqueJobs::Middleware::Server
  end

  SidekiqUniqueJobs::Server.configure(config)
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV["REDIS_URL"], driver: :hiredis }

  config.client_middleware do |chain|
    chain.add SidekiqUniqueJobs::Middleware::Client
  end
end

mhenrixon/sidekiq-unique-jobs - GitHub

🔗 設計

🔗 オンライン投票と要件定義


つっつきボイス:「これはいい記事: オンライン投票は一見すぐできそうに見えるけど、オンライン投票を要件定義すると実はいろいろ困難や課題があることが具体例を挙げて解説されている」「秘密投票とかそういう話題もあるんですね」「この記事でやっていることがまさに要件定義になっているのが興味深い: 良い要件定義の例として読むといいと思います👍」

参考: 秘密投票 - Wikipedia


後編は以上です。

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

週刊Railsウォッチ: Active Modelで属性のパターンマッチをサポート、猫でもわかるHotwire入門ほか(20220516前編)

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

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

Ruby Weekly


CONTACT

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