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

週刊Railsウォッチ(20201202後編)Rails 6.1 RC2リリース、Ruby STMの詳細な解説記事、RSpecのdiffを見やすくするsuper_diff gemほか

$
0
0

こんにちは、hachi8833です。Rails 6.1 RC2が今朝リリースされたことをつい先ほど知りました。

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

⚓ 臨時ニュース: Rails 6.1 RC2がリリース

6.1の主な新機能や改善点も上の記事でリストアップされています。

  • マルチプルDBの強化(Horizontal Shardingなど多数)
  • 関連付けのstrict loading(#37400#38541
  • Delegated Typing(#39341
  • 関連付けをバックグラウンドジョブで非同期destory可能に(#40157
  • ActiveModel::Error#32313
  • Active Storageの強化(#34935など)
  • deprecation warning発生時にraiseするオプション(#37940
  • 各種パフォーマンス向上およびバグ修正
  • Zeitwerkより前のクラシックオートローダーが非推奨に

ウォッチ20200907でご紹介した@willnetさんのスライドを再録いたします。

⚓Ruby

⚓ @_ko1さんのSTM解説記事


つっつきボイス:「Software Transaction Memory(STM)を@_ko1さんが詳しく解説してくれてる!」「この間取り上げたSTMの英語記事のときはRactorとの絡みがよくわからなかったけど、@_ko1さんの記事はRactorにも関連して書かれていてありがたい🙏」(一同でしばらく読む)

「Ractorは基本的にスレッド間で共有するものがあってはいけないという考え方で、たとえばクラス変数もRactorでは使えないんですけど、それでも共有したいものがあるときのためにSTMを導入してはどうかという話を何かのイベントで聞いた覚えがあります」「記事でも、Ractorの外では基本的にオブジェクトを共有できないとありますね」

: 本項の以下のイタリック箇条書きはつっつきで注目した部分を元記事から抜粋・要約したものであり、必ずしも元記事の流れに沿っていません。詳しくはぜひ元記事をご覧ください。

  • Ractorではデータ共有のためにオブジェクトをコピーしてメッセージとして送受信する
  • Ractor#sendRactor.receiveRactor.yieldRactor#takeというメソッドが使える

「マルチスレッドプログラムやネットワークプログラミングではよくこのような形でデータを共有しますね」

そこで、Ractor では、メモリを共有するのではなく、オブジェクトをメッセージとしてコピーして送ったり受け取ったりすることで、データを共有します。
同記事より

  • Ractorのオブジェクトの受け渡しは原則として参照ではなくコピーされる
  • ただしコピーなしで受け渡しする場合もあり、それを共有可能オブジェクトと呼ぶ

「この辺の、どれを共有可能オブジェクトにできるか、何が共有できないかを追いかける話なども何かのイベントでしてませんでしたっけ?」「うう、思い出せない😢」「そのときにSTMの話もしてたように覚えてます」

  • たとえばちょっとしたカウンタをRactor間で簡単に共有する方法が現在のRactorにない
  • 単純なカウンタを集約して管理するRactorを作るといったことは可能

「カウンタのためにわざわざRactorを作りたくない気持ち、わかります」

  • ロックによるメモリ共有を検討した
  • しかし値をincrementするだけでもロックが必要になる
  • ロックしないとアクセスできないインターフェイスを試したところ、デッドロックした↓
# 同記事より: デッドロックするコード
c1 = Counter.new(0)
c2 = Counter.new(0)

r1 = Ractor.new do
  c2.lock do
    c1.lock do
      c1.value += 2
      c2.value = c1.value * 2
    end
  end
end

c1.lock do
  c2.lock do
    c1.value += 1
    c2.value = c1.value * 2
  end
end

#...?

「値をincrementするにもロックが必要、たしかに」「やはりデッドロックするのか」

参考: デッドロック - Wikipedia

  • ロックのアプローチに代えて、データベースのトランザクションの概念をメモリに適用するSTMを採用すればそうした問題を解決できるのではないかと考えた

「STMは、とりあえず読み書きして、アクセスが重複したらロールバックするというところがシンプルですね↓」

STM は、DB のトランザクション(楽観的ロック)と同じように、とりあえずなんか読み書きして、あとで、「あ、別の Ractor とアクセスが被った!」となったらロールバックしてしまいます。簡単ですね。
同記事より

  • STMはreadしかしないのであれば並列化して速くなりそう
  • 他にもメリットがある
  • その代わりロールバックが多発すると遅くなる可能性がある

「STMのいろいろな流派の話も興味深い↓」「メモリ操作を全部transactionの対象にするアプローチは聞いたことある」「まさにコンピューターサイエンスの記事ですね」


同記事より

  • Rubyはすべてをロールバックすることはできないので、一部のメモリだけをSTMの対象にすることにする
  • Ractorでは、Ractor.atomicallyでトランザクションを設定すると、Ractor::TVar.newに置いた値だけがトランザクションの中でロールバック対象になる
  • Ractor::TVarは、Concurrent Rubyのインターフェイスを踏襲した

「これらのメソッドが既にconcurrent-rubyのThreadにあったとは知らなかった↓」「TVarはtransaction variableの略なんですね」

ruby-concurrency/concurrent-ruby - GitHub

  • Ractor同士の変更が競合した場合は再実行される(その場合インスタンス変数やI/O処理は元に戻らない)
  • Ractor.atomicallyはネストできる

「『STMの実装』を見ると、STMは時刻をベースにしているらしい」「Ractor.atomicallyが自由にネストできるというのがスゴい」「ネストしても大丈夫だろうかと一瞬思っちゃいました」「読み出しのみの操作でもロールバックするとやり直しになる、だから再実行しても安全になるのか」

  • STMはRuby 3.0ではリジェクトされたが、gemとして公開している↓

ko1/ractor-tvar - GitHub
ko1/ractor - GitHub

「Ractorを使い倒すのであればしばらくはgemでやることになりそう」「gemになった分少し性能は落ちるらしいけど、これはしょうがないですね」


「とても丁寧な解説記事で、しかもめちゃめちゃ面白い!」「実装の話も盛り込まれていて参考になります」「これを日本語で読めるのはありがたい🙏

⚓ RactorやSTMの使われ方を想像する

「この記事で扱われているような、トランザクションをソフトウェアで行ってものすごい数の並列処理を行うといった話題は、Go言語などで作るようなマイクロサービス方面でよく登場しますね」「ふむふむ」「今後Rubyでそうした処理を行うときには、RactorとSTMが大事な考えになってくる気がします」

「RactorやSTMは、RailsのActive Recordのビジネスロジックみたいなアプリケーションコードよりは、もっとプリミティブなAPIサーバーでよく使われそうな気がします」「記事にもあったような、銀行口座の残高移動のように一貫性が一瞬でも崩れてはいけない処理でも使いたいです」

「今後RubyでRactorとSTMが本格的に使えるようになれば、現在はGo言語などで作られているような並列度の高いAPIサービスをRubyで手軽に書けるようになるかもしれませんね」

⚓ lib-ruby-parser: Rustで書かれたRubyパーサー(RubyFlowより)

lib-ruby-parser/lib-ruby-parser - GitHub


つっつきボイス:「まだ新しくて★は少ないですが、RustでRubyパーサーを作ったというのが気になりました」「MRIのparse.y↓をそのままベースとしているので、完全にMRIと同じ順序でトークンを返すとある」「かつRipperプラスjemallocよりも速いそうです」

参考: ruby/parse.y at master · ruby/ruby
参考: class Ripper (Ruby 2.7.0 リファレンスマニュアル)

「レアなエッジケースを与えたらさすがにコケるかな?」「READMEを見るとトップ300 gemでテストされていて、しかもruby/spec↓やruby/rubyでもRipper.lexと同じ結果が出ているとある!」「それはスゴい」「RubyをRustでパースしたいときはこれでしょうね😆

ruby/spec - GitHub

⚓ ShopifyのSorbet利用状況

以下で知りました。

参考: 週刊気になったITニュース(2020/11/22号) - masa寿司の日記


つっつきボイス:「ShopifyでSorbetがどのぐらい使われているかという記事で、masa寿司さんの方で見る方が読みやすそうでした」「ファイルの80%以上が型付けされてるとは」「Shopifyは静的型付け方面もすごく頑張ってるな〜」「Shopifyはサービスの規模も巨大でエンジニアの数も多くて、エンタープライズWebアプリケーションのひとつの大きな流れを形成しつつある感じがしますね」「今後Sorbetの利用を指定する案件が出てくるようになったら面白そう😋

後でShopifyのイベント↓を見てみましたが、今のところイベントの資料は見当たらないようです。

イベント: Shipit! Presents: The State of Ruby Static Typing at Shopify(終了)
類似記事: Adopting Sorbet at Scale — Development

⚓ super_diff: RSpecでデータ構造のdiffを見やすく整形(Ruby Weeklyより)

mcmire/super_diff - GitHub


つっつきボイス:「なるほど、通常だとテストがfailするとデータ構造のdiffがベタに表示されるけど、それをこんなふうに賢く表示するのか↓」「これはスーパー」「賢い〜」「複雑なデータ構造のdiffを見たいときによさそう👍


同リポジトリより

⚓DB

⚓ M1とPostgreSQL

つっつきボイス:「これはデータベース関連のベンチマークかな?」「縦軸は秒あたりのトランザクション数、横軸はクライアント数か」「これが出典のようです↓」

参考: 【x軸とy軸の覚え方】 「横がx軸で縦がy軸です」ととっさに言えますか?! | SMATU.net

「ツイートのグラフを見る限り、M1はクライアント数が増えたときのトランザクション数の伸びが頭打ちになっている感じ」「このグラフがリニアであるほど、並列化がうまくいってることになりますね」「Ryzen 9はやっぱりスゴいという話」「Ryzenつよい💪

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

⚓ 米バージニア州北部のAWS us-east-1リージョンで障害(現在は回復


つっつきボイス:「我が家のSwitchBotは今の時点でまだ回復してません😇」「主にIoT系デバイスが影響を受けてるようですね」「見た感じでは影響を受けているのはIoTデバイスだけみたい」

参考: SwitchBot(スイッチボット) | スマートホームにらくらくスイッチ| Alexa | Google Home

「お、ステータスを見ると珍しくページトップで障害をレポートしている↓」「Tokyoリージョンの障害もトップに出して欲しい」「今日のお昼はもっと障害件数多かったんですけど、今はほぼ回復してる感じですね」「なるほど、IoT SiteWise以外はグリーンに戻ってる」


同記事より

「AWSでは新しいサービスをまずus-east-1リージョンで立ち上げることが多いんですけど、Amazon Kinesisなどのサービスがまだそのリージョンにしかなかったんでしょうね」「AWSの新しいサービスをいち早く使ってる人はだいたいこのリージョンに乗っかりますけど、TOKYOリージョンに来るまで待ってから使い始めている人たちはたぶん無事だったんじゃないかな」

「記事を見ると家電が動かない報告がいろいろ上がってますね」「エアコンが動かなくて寒いとか」「ご飯が炊けなくなったとか、灯りがつかなくなって寝坊しそうになったというのも見かけました」

「エアコンは物理リモコンで回避できそうですけどね」「私はリモコンを手放さずに全部持ってますけど、リモコン使いたくない人は片付けちゃうのかも」「リモコン使いたくないからIoTを導入することも多いでしょうね」「いずれにしろこの種のデバイスはフォールバックパスを用意しておかないとこういうときに大変」

⚓言語/ツール/OS/CPU

⚓ M1とリーナス・トーバルズ


つっつきボイス:「リーナスはプロプライエタリな製品を好まないことで有名ですね」「記事にはLinuxをM1向けに最適化するつもりはないとあります」「M1 Macの仕様が公開されないとできないでしょうね」

参考: リーナス・トーバルズ - Wikipedia

⚓その他

⚓ スクラムガイド更新版が日本語に


つっつきボイス:「スクラムガイド改訂版が日本語化されたそうです」「改定されてだいぶ短くなったらしいとはてブかどこかで見かけました↓」「スクラムの仕様は定義が厳しくて、資格を取らないとスクラムマスターを名乗れないですね」「BPSでも厳密にスクラムのやり方に則って開発をしているチームはなかったと思います」

参考: スクラムガイドの変更点(2017→2020)から見えるスクラムチームが陥りやすい3つの罠
参考: スクラムマスターの役に立つ認定資格・研修一覧|オッドイー

⚓番外

⚓ 外からわかる


つっつきボイス:「最近こういう外からわかる系の技術をよく見かけるなと思って」「3つ目の記事には、脳が今聞いている音楽や言葉を判定するというのがありました」「2つ目の記事にあるロボット掃除機で音を取る手法なども昔から研究されている技術ですね: 音は物体を揺さぶるので、音の周波数より十分高いサンプリングレートで揺さぶりを観測できれば原理的に可能」「はい、窓ガラスを外からレーザー光線で測定して部屋の中を盗聴するなんてのも20年ぐらい昔にニュースになったのを覚えてます」「それだけ音の周波数は光に比べて低いということですね」

参考: レーザーマイクロフォン - Wikipedia


後編は以上です。

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

週刊Railsウォッチ(20201201前編)switch_pointがActive Record 6.0でサポート終了、Rails DBトランザクションの落とし穴ほか

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

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

Ruby Weekly

RubyFlow

160928_1638_XvIP4h


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

$
0
0

更新情報:

  • 2016/08/25: 初版公開
  • 2020/12/03: 追記

⚓ utf8の4バイト文字問題は突然に

こんにちは、hachi8833です。

160823_0838_ldO8ik

MySQLのデータベースでencoding=utf8が指定されていると、UTF-8の文字長が4バイトの文字をデータベースに保存できなくなる、いわゆるUTF-8の4バイト文字問題、またの名を「寿司ビール問題」が発生することがあります(「MySQLのutf8の4バイト文字問題とは」で後述)。

BPSWebチーム部長のmorimorihoge さんがこの問題に対応したときの手順をメモします。

⚓ utf8からutf8mb4に移行する手順

MySQLのストレージエンジンはInnoDBが前提です。utf8mb4を指定するにはMySQLのバージョンが5.5以上である必要があります。

⚓ 1. 以下のコマンドでdumpを取る

mysqldump --no-create-info --ignore-table=mydata_store.schema_migrations -uroot mydata_store

⚓ 2. my.cnfに以下を追加してrestart(MySQL 5.7.9 より前のバージョンの場合)

innodb_file_per_table
innodb_file_format = Barracuda
innodb_file_format_max = Barracuda
innodb_large_prefix

Indexサイズの問題を回避するため、ファイルフォーマットをAntelopeからBarracudaに切り替えます。

⚓ 3. config/initializersに以下の定義が入ったファイルを置く

# MySQLでutf8mb4を利用する場合、ROW_FORMART=DYNAMICが必要
# ※my.cnfへの設定追加も必要なので注意
#
# refer: http://3.1415.jp/mgeu6lf5/
ActiveSupport.on_load :active_record do
  module ActiveRecord::ConnectionAdapters
    class AbstractMysqlAdapter
      def create_table_with_innodb_row_format(table_name, options = {})
        table_options = options.reverse_merge(:options => 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC')
        create_table_without_innodb_row_format(table_name, table_options) do |td|
          yield td if block_given?
        end
      end

      alias_method_chain :create_table, :innodb_row_format
    end
  end
end

⚓ 4. database.ymlを以下に設定

 encoding: utf8mb4
  charset: utf8mb4
  collation: utf8mb4_unicode_ci

⚓ 5. db:migrate:resetする

これで全データが消えて、全テーブルがutf8mb4になります。

⚓ 6. mysqlコマンドで最初にdumpしたデータをインポートする

これで既に入っているデータを保持しつつ、utf8mb4にmigrationできるようになります。

⚓ MySQLのutf8の4バイト文字問題とは

MySQL のencodingやcharsetのutf8は、実は真のUTF–8ではなく、4バイト長の文字に対応していません。

160823_0940_JE6dBa

追記(2020/12/03): 2017年の記事「Sushi = Beer ?! An introduction of UTF8 support in MySQL 8.0 | MySQL Server Blog」より↓

歴史的な理由で、MySQLのutf8文字セットはutf8mb4ではなくutf8mb3を参照しています。3バイトのutf8文字セットは、Unicodeで定義されている文字のうち限定的なセットしかサポートしないので、基本的には基本多言語平面(BMP: basic multilingual plane)になります。追加多言語面(SMP: supplementary multilingual plane)の絵文字やその他の文字はサポートされません。同様に、追加漢字面(SIP: supplementary ideographic plane)に含まれる追加の漢字(CJK統合漢字
拡張B
: CJK unified ideographs extension B)もutf8mb3ではサポートされません。
mysqlserverteam.comの同記事より

ちょっとググるだけで、Railsに限らず、MySQLでこの問題を踏んだ多くのエンジニアの悲しい叫び声が続々と見つかります。

4バイト長UTF–8文字が問題になるのは、主に中国語と日本語です。中国語としても使われている一部の漢字が4バイト長になっていますが、一部が日本語でも人名や地名に使われることがあります。そのため、𠮷(吉の異字体)のようなマイナーな文字が使われている人名がテーブルに登録されて発覚することがあります。

参考: 第86回 「𠮷」と「吉」 | 人名用漢字の新字旧字(安岡 孝一) | 三省堂 ことばのコラム

英語圏ではこの問題に直面することはあまりなかったようですが、近年UTF–8の絵文字が多用されるようになり、絵文字の一部が4バイト長になっているため、近年は英語圏でも問題になっています。

UTF-8絵文字の中でも、特に寿司アイコンとビールアイコン(🍣🍺)が同値判定されてしまう問題が、2015年頃に「寿司ビール問題」と呼ばれるようになりました。「ケツカンマ問題」と並んで、問題を端的に表現した素晴らしいネーミングだと思います。

MySQL のバージョン5.5以降であれば、encodingやcharsetなどの項目にutf8mb4を指定することで4バイト長の文字に対応できるようになります。

とはいうものの、utf8が真のUTF–8でないことに変わりはありません。

MySQL側でutf8をUTF-8としての正しい挙動に変更したときの影響の大きさを考えれば、utf8mb4追加による対応は致し方ないという気もしますが、MySQLを初めて扱うエンジニアが踏みがちなブービートラップとして当分永らえそうに思えました。

追記(2020/12/03): 同じく「Sushi = Beer ?! An introduction of UTF8 support in MySQL 8.0 | MySQL Server Blog」には2017年の時点でデフォルト文字セットutf8mb4に移行する構想が述べられており、その後MySQL 8.0.1からはデフォルト文字セットがutf8mb4になりました

参考: MySQL8.0の文字コード設定 | blog.kotamiyake.me

⚓ メモ: コレーションについて

MySQLに限らず、RDBMS、そして自然言語を対象にインデックスを生成するあらゆるソフトウェアでは、コレーション(collation)の指定も重要です。

コレーションは、インデックス作成時にどの文字とどの文字を同値として扱うかという戦略を指定するためのものであり、要件に応じて適切なものを指定する必要があります。たとえば、検索時にカタカナの濁点・半濁点(「ハ」「パ」「バ」)を区別するかどうかに影響します。

コレーションの問題は寿司ビール問題と同時に発生することもありえますが、寿司ビール問題がMySQL固有のエンコード/文字セットの扱いの問題である一方、コレーションは普遍的なテーマなので、それぞれ別の問題です。

追記(2020/12/03): MySQLのコレーションについては、8.0.1でutf8mb4_ja_0900_as_csを含む以下のコレーションが追加されました。

-- mysqlserverteam.comより
mysql> select collation_name from information_schema.collations where character_set_name='utf8mb4' and collation_name like '%as_cs' order by collation_name;
+----------------------------+
| collation_name             |
+----------------------------+
| utf8mb4_0900_as_cs         |
| utf8mb4_cs_0900_as_cs      |
| utf8mb4_da_0900_as_cs      |
| utf8mb4_de_pb_0900_as_cs   |
| utf8mb4_eo_0900_as_cs      |
| utf8mb4_es_0900_as_cs      |
| utf8mb4_es_trad_0900_as_cs |
| utf8mb4_et_0900_as_cs      |
| utf8mb4_hr_0900_as_cs      |
| utf8mb4_hu_0900_as_cs      |
| utf8mb4_is_0900_as_cs      |
| utf8mb4_ja_0900_as_cs      |
| utf8mb4_la_0900_as_cs      |
| utf8mb4_lt_0900_as_cs      |
| utf8mb4_lv_0900_as_cs      |
| utf8mb4_pl_0900_as_cs      |
| utf8mb4_ro_0900_as_cs      |
| utf8mb4_sk_0900_as_cs      |
| utf8mb4_sl_0900_as_cs      |
| utf8mb4_sv_0900_as_cs      |
| utf8mb4_tr_0900_as_cs      |
| utf8mb4_vi_0900_as_cs      |
+----------------------------+
22 rows in set (0.02 sec)

参考: MySQL 8.0.1: Accent and case sensitive collations for utf8mb4 | MySQL Server Blog
参考: 寿司とビールについて話し合いをしてきました | エンジニアブログ | GREE Engineering

Rubyの内部文字コードはUTF-8ではない…だと…?!

$
0
0

更新情報
2016/10/13: 初版公開
2020/11/27: 追記

こんにちは、hachi8833です。

少し前に、babaさんから「Rubyの内部文字コードはUTF-8じゃないよ」とツッコミがありました。

(追記: 上は会話の途中から切り取りましたのでご了承ください)
いきなりの展開にくらくらきましたが、babaさんはさらにたたみかけます。

こうしたことはとっくにご存じの方も多いと思いますが、「Rubyといえば2.0以来UTF-8完全対応なんじゃないの」と勝手に思い込んでた私は脳に掌底を食らったような思いです。ああ、でもこういうことがあるから面白い。

⚓ プログラミング言語と内部文字コードの関係

まず最初に押さえておきたい点です。プログラミング言語で文字コードに関連する部分は、「文字列」「正規表現」「入出力」「コード中の文字リテラル(””の中など)」「コード中の文字リテラル以外の要素(変数名など)」「ファイル名」などが中心になります。そして文字列に関連して「ソート順」などについても考慮が必要です。

とbabaさんが指摘しているとおり、Rubyで文字コードに関連するのはほとんどの場合標準ライブラリです。

⚓ UCS正規化方式

Java、C#、Python、Perl、Goなど、多くの言語では内部でUnicodeを用いています。言い方を変えれば、文字コードを固定してそれ以外のコードについては変換のみで対応するということです。このように内部表現をUnicodeの文字コードに統一する方式をUCS正規化(UCS Normalization)と呼びます【注: リンク修正いたしました】。UCSは「UnicodeUniversal Character Set」の略です。

UCS正規化を採用している言語では、たとえばStringクラスなどで原則としてその文字コードしか保存できません。たとえばJavaのStringクラス、Characterクラス、char型であれば文字をUTF-16で保存します。そのため、UTF-16Unicodeに含まれない文字(実はそれなりにあるのです)は原則として言語標準のStringクラスなどでは扱わない/扱えないことになります。

こうした言語でも、標準のStringクラスなどを使わずにchar[]型などに保存して自力でハンドリングしたり別ライブラリで扱ったりする分には構いませんし、その必要が生じることはいくらでもありえます。

⚓ Rubyは1.9から「CSI方式」

これに対し、Rubyは1.9でCSI(Code Set Independent)という独自の多言語化方式を導入しました。

UCS正規化と異なり、CSI方式では特定の内部コードを仮定しません。たとえばStringクラスの変数にはその気になればUnicode以外の文字コードでも直接保存できます。つまり文字列は内部的に事実上バイナリとして保存されているのです。その代わりStringクラスで文字コード情報を持てるようにする(以下のコード例の#encoding)などの改良が行われました。

実装が複雑になることもあり、CSI方式を導入した言語はRuby以外にはなかなかないようですが、多様性に富んだチャレンジングな多言語化方式とされています。なお、Ruby 1.9でオレオレ文字コードを導入してみた豪の者を見つけました。

また、1.9ではマジックコメントが導入され、スクリプトファイルの冒頭(冒頭がshebangの場合は2行目)に# coding: euc-jpなどの方法でファイルのエンコーディングを指定できるようになりました。

#!/usr/bin/env ruby -
# coding: utf-8
str='ab漢字'
puts str[2]
puts __ENCODING__
puts str.encoding

161011_1500_pn3QyE

ただし1.9の場合、マジックコメントを指定しない場合のデフォルトのファイルエンコーディングはUS-ASCIIです。UTF-8ではありません。

#!/usr/bin/env ruby -
puts __ENCODING__

161014_2315_pFG4MN

⚓ Ruby 2.0での文字コード周りの変更とその後

Rubyが2.0になってから、このマジックコメントを指定しない場合のデフォルトのファイルエンコーディングがUS-ASCIIからUTF-8に変更されました

#!/usr/bin/env ruby -
puts __ENCODING__

161014_2321_vty3fY

2.0でのデフォルト文字コードの変更はもちろん大きなものであり、他にも文字コードについての変更点はありますが、CSIの部分に関しては引き続き変わっていません。つまり2.0の文字列は1.9のときと同様、内部では事実上バイナリなのです。

Rubyでは、1.9でのCSI導入、2.0でのデフォルトのファイルエンコーディングのUTF-8への変更という段階的な方法で多言語化を切り替えてきました。2.0以降、これらの点について変更はありません。

私がRuby 2.0からUTF-8完全対応だと思い込んでいたのはいろんな意味で誤りでした。後述するようにRubyは1.9より前からUTF-8を含む複数の文字コードを扱えましたし、2.0でUTF-8になったのは「デフォルトのファイルエンコーディング」の部分です。

⚓ 参考: Ruby 1.8以前の場合

Ruby 1.8以前は文字列をデフォルトでASCII単位でとして扱い、文字列メソッドもバイト単位で動作しました。たとえば1.8でstr="ab漢字"の後でstr[2]の値を取り出すと0xB4(EUCの「漢」の最初のバイト)になりました。

str='ab漢字'
p sprintf('%#x', str[2])

このkanji.rbはEUC-JPで保存されているとします。

161011_1321_mpCxn4

1.8以前でこうしたスクリプトを実行するには、スクリプトファイルの文字コードに応じた-Ku(UTF-8)や-Ke(EUC-JP)や-Ks(Shift_JIS)などのオプション指定が欠かせませんでした(2.0から非推奨)。指定したオプションは$KCODEで確認でき、正規表現は$KCODEに応じて文字数の数え方が変わりました。文字列ごとに異なる文字コードを指定することはできませんでした($KCODEは1.9で廃止)。

⚓ 注意

UCS正規化とCSIには、それぞれメリットとデメリットがあります。どちらが優れているというものではなく、ユースケースによって変わります。

一般にUCS正規化では文字コードの実装を一本化できますが、Unicodeにない文字などは標準的な方法では直接扱えないので別のライブラリなどで対応する必要があります。

CSIでは標準的な方法で複数の文字コードを同時に扱うことができますが、正規表現の文字数の数え方などを文字コードに応じて実装側で切り替えるなど、実装が複雑になることが考えられます。

⚓ 参考文献

今回の記事では『プログラマーのための文字コード技術入門』に大変お世話になりました。

追記(2020/11/27): 2018年に同書の改訂版が出ました↓。

同書の裏帯に書かれているように、特定の文字コード体系を押したりせず、評価を読者の判断に任せている点に好感が持てます。同書ではRuby 1.8と当時最新だったRuby 1.9を比較しており、決して新しい内容ではありませんが、今回取り上げたRuby 1.9でのCSI導入についても詳しく解説されていて、現在でも読む価値のある良書です。Unicode、UTF-8が万能ではないということがわかったのは自分にとっては大きな収穫でした。

文字コードはどこまで行っても奥が深いので、理解の甘いところがありましたらTwitterで私までお知らせいただけると幸いです。

⚓ 追記(2020/11/27)

RubyでCSI正規化が採用された経緯についてMatz自らQuoraで回答していました🎉

関連記事

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

週刊Railsウォッチ(20201208前編)レガシーRailsアプリを引き継ぐときの6つの作業、サーバーレスプロジェクトをRailsに移行ほか

$
0
0

こんにちは、hachi8833です。

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

今回は、BPS昼の定例勉強会でつっつき会を行いました。

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

公式の更新情報から見繕いました。


つっつきボイス:「6.1 RC2が出ましたし、最終リリースまであとちょっとという感じになってきましたね」「お、今見るとマイルストーンでオープンのissueが0件になってる」「RC2でまたいくつかissueが追加される可能性はありそう」「最終リリースは年内ぐらいかな?案外来週ぐらいにしれっと出たりするかもしれませんけど」「Rubyとどっちが早く出るかな?」

その後、ウォッチ公開日のマイルストーンでオープンのissueは3件になっています。

⚓ 新機能: ERBでHashをHTML属性に変換できるようにする

ERBで以下のようにHashをHTML属性に変換して展開可能にする。

<input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>
<%# => <input type="text" aria-label="Search"> %>

ActionView::Helpers::TagHelper#tag_optionsの実装を利用してERBと属性変換を組み合わることで、テンプレートでHTML文字列をtagcontent_tagで置き換えなくてもやれるようにする。
同PRより大意


つっつきボイス:「attributesヘルパーメソッドが追加されたんですね↓」「上のようにハッシュをHTML属性として展開するのは前からできてたような気もしたけど、あれはSlimの書き方だったか」「ERBでもこれが使えるようになったのはいいですね👍

# actionview/lib/action_view/helpers/tag_helper.rb#L56
+       def attributes(attributes)
+         tag_options(attributes.to_h).to_s.strip.html_safe
+       end

参考: slim/README.jp.md at master · slim-template/slim

以下の記事によるとHaml 4以降でもできるそうです。

参考: HAML 4+ expands nested element attributes - makandra dev

⚓ 新機能: where.associatedで関連付けにデータが存在するかどうかをチェックできるようになった

以下は自分たちのアプリからの抜粋。

class Account < ApplicationRecord
  has_many :users, -> { joins(:contact).where.not(contact_id: nil) }
end

このuserは、contactのdelegatedy typeであるcontactablecontactableを置き換えて1件のuserをバックグラウンドで削除するというのはよくあるパターン。

上の書き方では関連付けの先にデータが存在するかどうかだけを知りたい場合に構文が少々煩雑になるが、これを以下のように書けるようになる。

class Account < ApplicationRecord
  has_many :users, -> { where.associated(:contact) }
end

これは#34727で追加されたwhere.missingの鏡写しになる。
同PRより大意


つっつきボイス:「元のjoins(:contact).where.not(contact_id: nil)は、joinした先のcontact_idが存在するかどうかだけを確認したいときに使いそうなクエリですね: これはたしかに悩ましくて、contactに制約が付いていない場合なんかだとcontactにcontact_idの外部キーが存在しない可能性があるので、関連付け先にデータが確実に存在するかどうかを確認するにはjoinsを書かないといけなくなりますが、改修後はwhere.associated(:contact)のように既に関連付けにデータが存在するという意味でassociatedを使って簡潔に書けるようになったということでしょうね」「なるほど」「こう書きたい気持ちはわかります」

「慣れるまではassociatedがコードで使われていてもすぐにピンとこないかも」「制約を付けて回避できるなら制約でやる方がいいでしょうね」「プルリクメッセージで引用されているwhere.missing↓はassociatedと対照的に、関連付けが存在しないものをフィルタで取り出すメソッド」

⚓ rails statsにCSSやERBの情報も表示するようになった

GitHubのモノリスのサイズ情報を得る方法を探していて、rails statsではapp/viewsディレクトリやapp/assets/stylesheetsディレクトリの情報が含まれていないことに気づいた。
これらのフォルダについても情報を出せば便利だと思う。このプルリクはこれらをViewsとStylesheetsという項目として追加する。


同PRより大意


つっつきボイス:「rails statsの出力項目に情報が増えましたね」「ビューとCSSが今までなかったのか」

# railties/lib/rails/code_statistics.rb#L43
-   def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|ts|coffee|rake)$/)
+   def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|ts|css|scss|coffee|rake|erb)$/)

なお手元のRails 6.0.3では以下のように表示されました。ここにはminitestのディレクトリも出力されていますが、試しにtestディレクトリを削除したら出なくなりました。

⚓ 新機能: リッチテキスト関連付けを一括でeager loadingできるようになった

Action Textはwith_rich_text_#{name}ヘルパーを提供して、リッチテキストの関連付けを楽にプリロードできるようになっている。これは1個のモデル上の1個のリッチテキストフィールドではうまくいくが、リッチテキストフィールドが複数の場合は個別のフィールドを読み込むためにActionTextテーブルへのクエリが繰り返される。

以下のようにモデルの中にユーザーが生成した動的なコンテンツが多数ある場合を考える。

class Page < ApplicationRecord
  has_rich_text :header
  has_rich_text :sub_header
  has_rich_text :content
  has_rich_text :aside
  has_rich_text :footer
  ...
end

Pageのすべてのコンテンツをeager loadingで表示しようとすると以下のようになる。

Page
  .with_rich_text_header
  .with_rich_text_sub_header
  .with_rich_text_content
  .with_rich_text_aside
  .with_rich_text_footer
  .find(params[:id])

この場合Railsが6回もクエリを実行するとことになる(1回はPageの読み込み、5回は個別のActionText読み込み)。

このプルリクはwith_all_rich_textを追加する。これはeager_loadを使い、has_rich_text関連付けへのリフレクションを行って、すべてのリッチテキストの関連付けを一括読み込みする。

Page.with_all_rich_text.find(params[:id])

その他
#37976によると現在のActionTextの内部は流動的とのことだが、この機能を今後のリリースに追加することに関心があるか、あるいはアプリケーション固有のヘルパーの方がよいかどうかをチェックして欲しい。
同PRより大意


つっつきボイス:「with_rich_text_*はテンプレートで複数使うこともありそうなので、それをwith_all_rich_textでひとつのクエリで書けるようにしたということか」「項目の数だけクエリが発行されなくて済むのはいいですね👍

# actiontext/lib/action_text/attribute.rb#L50
+     def with_all_rich_text
+       eager_load(rich_text_association_names)
+     end
+
+     private
+       def rich_text_association_names
+         reflect_on_all_associations(:has_one).collect(&:name).select { |n| n.start_with?("rich_text_") }
+       end

⚓ travel_toブロックで日時をStringで取るときにアプリのタイムゾーンが使われるよう修正

バグのように見えるが、ドキュメントにこの機能の説明が見当たらなかった。
travel_toは”2004-11-24 01:04:44″のようなstringを引数に取れるが、Stringで追加定義されるto_timeメソッドによってアプリケーションのタイムゾーン情報が失われてローカルに設定されてしまう。

# 現状
travel_to "2004-11-24 01:04:44" do
  Time.zone.now.to_s(:db) # => "2004-11-24 06:04:44" 
end 

# 期待する動作
travel_to "2004-11-24 01:04:44" do
  Time.zone.now.to_s(:db) # => "2004-11-24 01:04:44"  
end 

同PRより大意

参考: ActiveSupport::Testing::TimeHelpers

# activesupport/lib/active_support/testing/time_helpers.rb#L157
        if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
          now = date_or_time.midnight.to_time
+       elsif date_or_time.is_a?(String)
+         now = Time.zone.parse(date_or_time)
        else
          now = date_or_time.to_time.change(usec: 0)
        end

つっつきボイス:「バグ修正のようです」「例を見るとたしかにtravel_toにStringを渡した場合にタイムゾーンが変わってる」「内部でto_timeが呼ばれるとString形式の日時からタイムゾーンが落ちてたのか」「ここを意識したことがなかったということは、今までの自分はTimeオブジェクトを渡していたということかも」

travel_toはテストで使うメソッドでしたっけ?」「travel_toのブロック内だけ日時を変更してテストしたいときなどに使いますね」

Rails: Timecopを使わなくても時間を止められた話

⚓Rails

⚓ レガシーRailsアプリを引き継いだときにやること6つ

つっつきボイス:「記事の冒頭に、Rails 1.0がリリースされてから15周年とある」「もうそんなに経つんですね」「動画配信サービスのHuluもRailsとは知らなかった」「おそらくすべてではなくユーザーが目にするフロント部分などで使っているんでしょうね」

「ドキュメントの確認や整備、カスタムフォルダが追加されているかの確認などはいずれも大事」「カバレッジを100%に持っていくのは大変ですけどね」「ルーティングやDB構造、デプロイやstagingサーバーの構築なども同じく重要」

「この記事ではそうした作業が終わってからLinterを入れるのか: 動くことを確認してからlintをかけないとどこで壊れたかわかりにくくなることを考えれば、一理ある」「たしかに」「そうしてひととおり動くようになってコードをきれいにしてからRailsやgemのアップグレードを始める」「レガシーアプリに対応するときに順序としては基本的にこういう形になるでしょうね」「定番の作業項目をチェックできるのはよさそう👍


同記事見出しより:

  • 1. コードレビューとローカルセットアップ
    • ドキュメントの概要をレビュー(技術的負債もチェック)
    • テストをレビュー
    • ルーティングやデータベース構造をレビュー
    • 残っているカスタムフォルダをレビュー
  • 2. テストのカバレッジを100%にする(理想的には)
  • 3. デプロイ方法のチェックとstagingサーバーのセットアップ
  • 4. RuboCopとPrettierでコードベースにlintをかける
  • 5. stagingとproductionにデプロイする
  • 6. Rails、Ruby、gemをアップグレードする

⚓ サーバーレスプロジェクトをRailsに移行する(Ruby Weeklyより)


つっつきボイス:「AWS Lambdaで動かしていたプロジェクトがつらくなってきたのでRailsに引っ越したという記事です」「たとえばサーバーサイドで複雑な処理を行うような場合はLambdaに合わないことは考えられますね」「たしかに」「AWS Lambdaはマイクロなプロセスを発行することを主に想定していますし、マイグレーション的なしくみが組み込まれていないので、CRUD的なデータ操作や重たいバッチ処理を多用するような複雑な処理をLambdaですべてまかなうのはしんどいでしょうね」

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

⚓ Matestack: HTMLやJSを書かずにRailsをリアクティブにするエンジン(Ruby Weeklyより)

matestack/matestack-ui-core - GitHub


つっつきボイス:「書き方がちょっと面白かったので拾ってみました↓」「Matestackを見た感じでは、Railsに独自フロントエンドを入れてすべてをRubyで書きたいという人は今も結構いるようですね」「Basecampが出しているstimulus jsも使いたくなかったりするのかな?」「まだ新しそうなので日本語圏では情報がなさそうですが、英語情報はそこそこ出始めてるみたい」

# 同リポジトリより
class Components::Card < Matestack::Ui::Component

  requires :body
  optional :title
  optional :image

  def response
    div class: "card shadow-sm border-0 bg-light" do
      img path: image, class: "w-100" if image.present?
      div class: "card-body" do
        heading size: 5, text: title if title.present?
        paragraph class: "card-text", text: body
      end
    end
  end

end

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


以下のツイートはつっつき後に見つけました。


前編は以上です。

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

週刊Railsウォッチ(20201124)strict loading violationの振る舞いを変更可能に、Railsモデルのアンチパターン、quine-relayとさまざまなクワインほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20201209後編)Ractorベンチマーク記事、Railsで複合主キーを使う、AWS re:Invent 2020ほか

$
0
0

こんにちは、hachi8833です。

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

⚓Ruby

⚓ Ractorはどのぐらい速いか(Ruby Weeklyより)


つっつきボイス:「RubyやRailsのベンチマーク記事↓でおなじみのNoah GibbsさんがRactorのベンチも取ってみたそうです」「お〜」

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

「初期セットアップのオーバーヘッドの表↓を見た感じでは、Ractorのその部分はスレッドより速そう」


同記事より

「次のベンチマークの表↓を見ると、Ractorはスレッドとほぼ同じぐらいで、標準偏差が他の項目より大きいのか」「ホントだ」


同記事より

「記事の末尾には、Ractorは2コアだと最大で16%速いこともあると書かれてる」「記事にもあるようにRactorはこれからの技術ですが、この結果を見ると少なくとも遅くはならなさそうですし、Ractorに本格的に取り組んでみるのはよさそうですね👍

この記事は翻訳してみたいです。

⚓ Rubyの内部関数のバグを修正した話(Ruby Weeklyより)


つっつきボイス:「-p-iのようなオプションはRubyのワンライナーで使われることがありますね」「どうやらRubyのそういうオプションをshebangに書いたときに内部でパースの結果をチェックしていなかったらしい↓」

# (同記事のshebangを無効な内容に置き換えたもの: これでも動いてしまう)
#!/some/invalid/dir/ruby -pi.bak

BEGIN {
  puts "It is starting!"
}

$_.gsub!(/perl/, "ruby")


同記事より

# 同記事より
$ echo "I like perl, it is my favourite language." > temp.txt

$ ruby script.rb temp.txt
It is starting!

$ cat temp.txt
I like ruby, it is my favourite language.

$ cat temp.txt.bak
I like perl, it is my favourite language.

参考: シバン (Unix) - Wikipedia

なお、-i.bakは拡張子を変えてバックアップするオプション、-p$_の値を出力するオプションだそうです。

参考: Rubyの起動 (Ruby 2.7.0 リファレンスマニュアル)

「こんなバグよく見つけましたよね」「shebangで-pi.bakみたいな指定って普通やらないと思いますけどね…自分はたぶん今後も使わないかも」「記事の最後でRubyにプルリク投げてマージされたそうです↓」

このバグ修正はRuby 2.6や2.7にもバックポートされるそうです(#17117)。

⚓ Rubyのdefault gemとbundled gemの違い(RubyFlowより)


つっつきボイス:「Bundlerのdefault gemとbundled gemの違いについて解説した記事です」「gem化されてないライブラリも含めると3種類になるとかがよく話題になりますね」「@hsbtさんのこの発表は、そのあたりや今後の動きについて詳しく解説してくれているのでとても参考になります↓」

参考: bundled gem と default gem の違い - @znz blog

⚓DB

⚓ Railsで複合主キーを導入する(Ruby Weeklyより)

参考: 複合主キーとは - IT用語辞典 e-Words


つっつきボイス:「Shopifyの記事です」「記事の前半ではShopifyがマルチテナントアーキテクチャであることなどを考慮して、通常の主キーに代えてshop_idorder_idによる複合主キーを使う方向に寄せることを考えたという話などをしていますね」「ふむふむ」

# 同記事より
class Order < ApplicationRecord
  self.primary_key = :id
  # 省略
end

「そして上のモデルに対応するのが以下のSQLテーブル定義↓ですが、お〜、これを見ると複合主キーはあくまでデータのローカリティ(局所性)を高める、つまりインデックス用にだけ使うけど、Railsのサロゲートキーは変えずに残しておくという考え方のようですね」「あ、そういうことですか!」「PRIMARY KEYにはshop_ididを指定しているけど、idのAUTO_INCREMENTは変えずに残しているあたりがそういう感じ」

-- 同記事より
CREATE TABLE `orders` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `shop_id` bigint(20) NOT NULL,
  -- (他のカラムは省略)
  PRIMARY KEY (`shop_id`,`id`),
  KEY `id` (`id`)
  -- (他のセカンダリキーは省略)
)

「ちなみにRailsには昔からcomposite_primary_keysという複合主キーの定番gemがあるんですけど↓、こちらは他のgemとの組み合わせなどによっては副作用が生じることもあるので、そういうときには他のgem向けにRailsのサロゲートキーが別途欲しくなることも考えられそう」

composite-primary-keys/composite_primary_keys - GitHub

「Shopifyのようにサロゲートキーと複合主キーを両方持つやり方はちょっと特殊に思えますが、マルチテナンシーなどの要件がうまくマッチすれば、複合主キーをこのように使うこともあるのかもしれませんね」「なるほど」「Shopifyのような実装は、一般的な複合主キーの実装ではあまり見かけない感じに思えますが、いろいろ興味深いです👍


「ところで、記事の中でlhm(large hadron migrator)という粒子加速器みたいな名前のツールが紹介されていました↓」「見た感じでは、Shopifyが作った独自のデータマイグレーションツールのようで、複合主キーとは直接関係なさそうかな」「Percona Toolkitを使った似たようなマイグレーションツールがあったかも」

shopify/lhm - GitHub

jbravata/percona_migrator - GitHub

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

⚓ AWS re:Invent 2020での各種発表(Publickeyより)

aws/eks-distro - GitHub


つっつきボイス:「そのうちまとめ記事が出そうですが一応」「今回のAWS re:Inventも、AWS LambdaがDockerコンテナをサポートしたとかいろいろ発表されていますね: この見出しの他には、AWS Lambdaの課金が100ミリ秒単位だったのが1ミリ秒単位に変わったという発表もありました↓」「あ、そうでしたか」「1ミリ秒課金になったことで、重いDockerコンテナを使うよりもAWSの既存のランタイムを使う方がよい局面も出てくるかもしれませんね(どこまでやるかにもよりますが)」

参考: [アップデート] Lambdaの実行時間の課金単位が1ミリ秒に短縮されました #reinvent | Developers.IO

「見出し最後のEC2 Macインスタンスは力技で面白かった」「社内Slackでbabaさんが『Mac miniを物理的にずらっと並べてるのか』ってウケてたヤツですね↓」「AWSのアナウンスにも、仮想化したmacOSではなくてMac miniコンピュータを使っていると書かれていますね」

「ただEC2 Macインスタンスはアナウンスにも書かれているようにminimum host allocation durationが24時間なので、ビルドのときだけ立ち上げるような使い方だとコストがかさみそうなのは今後改良して欲しいかな」「出先でPCとMacを両方使いたいけど2台も持ち運んでいられないような人にはありがたいかも」「将来M1のインスタンスも使えるようになったらまた違ってきそうですね」「これでリモート開発やリモートデバッグができるようになって、Macを買わなくてもiPhoneアプリを開発できるようになったらよさそう(Appleのライセンスでそこまで許されるかどうかにもよるでしょうけど)」

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

⚓ 2021年度のWebデザイントレンド


つっつきボイス:「来年のWebデザイントレンドか」「以下に日本語のまとめ記事もあります↓」

参考: ついに出た!2021年注目のWebデザイン人気トレンド9個まとめ - PhotoshopVIP

「Neumorphismは、新しいSkeumorphismということらしい」「スキュー?」「だいぶ昔のiPhoneのアイコンなどで使われていた実物っぽいデザインがたしかSkeumorphismと呼ばれていましたね」「Neumorphismの影の付き方とかがそれっぽいかも」

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

「Abstract Art Compositionは、20世紀前半のモンドリアンの絵みたいな感じなのかな」「こういう感じのデザインはSVGで軽量に作りやすそうですし、CSSのレンダリングやアニメーションと相性がよさそうなので、それもあって流行りつつあるのかなと思いました」

参考: Broadway Boogie Woogie - Wikipedia

「主に海外の流行のようなので、ものによっては日本に入ってくるまで時間差があるかもしれませんが、こういう記事を一度眺めておくと、今後デザイナーに「XXのような感じでお願いします」と大枠の指定を出したりするときに役立つと思います👍

⚓その他

⚓ Big SurとRuby


つっつきボイス:「ついに@jnchitoさんも開発環境をBig Surに移行」「そういえばHomebrewもBig Surで動くようになったらしいので↓、MySQLを複数入れたりもできるでしょうね」

参考: 「macOS Big Sur」に対応、M1での動作も可能になった「Homebrew 2.6.0」 - 窓の杜

「昔はMySQLやぽすぐれをHomebrewで複数バージョンインストールして管理してましたけど、今ならもうDockerでやりたい」「そうですよね」「もちろんローカル環境でたくさんのプロジェクトを扱うのでなければ今までどおりHomebrewなどでインストールすればいいと思いますけど、自分の場合はいろんなプロジェクトを扱っているので、昔みたいにMySQLやPostgreSQLやRedisのバージョンが3つずつぐらい同居すると、メモリを食いすぎるので手動で起動したりとかいろいろつらい」「ポート番号も使い分けないといけないのもつらいです😢」「Dockerはやっぱりありがたい」

なお、Docker Desktop for MacもBig Surに対応したそうです↓。

参考: Docker for Mac Stable release notes | Docker Documentation


後編は以上です。

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

週刊Railsウォッチ(20201124)strict loading violationの振る舞いを変更可能に、Railsモデルのアンチパターン、quine-relayとさまざまなクワインほか

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

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

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Publickey

publickey_banner_captured

週刊Railsウォッチ(20201214前編)Rails 6.1の直近コミットを見る、RuboCop Rails 2.9リリース、ar_lazy_preload gemほか

$
0
0

こんにちは、hachi8833です。Rails 6.1がリリースされましたね。

速報: Ruby on Rails 6.1がリリースされました

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

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

今回はいつもと趣向を変えて、6-1-stableブランチの直近のプルリクを中心に見繕いました。したがって、以下のいずれのプルリクも6.1にマージされています。

⚓ 6.1: バリデーション時のstrict loadingを修正

strict loadingはバリデーション時にエラーを出して欲しくない(バリデーションのためにレコードを読み込む必要があるため)。
今回の変更では、owner.validation_contextをチェックするようにした。これがnilの場合は、createやupdateでオブジェクトを現在バリデーションしていないことがわかる。それ以外の値が設定されている場合はバリデーション中なので、strict loadingでraiseをスキップしたい。
同PRより大意


つっつきボイス:「validation_contextとは…?」「なるほど、strict loadingを有効にすると、バリデーションの実行中にエラーになったときにraiseされてしまっていたので、バリデーション中はエラーを投げないようにしたということのようですね」

# activerecord/lib/active_record/associations/association.rb#L212
      private
        def find_target
-         if owner.strict_loading?
+         if owner.strict_loading? && owner.validation_context.nil?
            Base.strict_loading_violation!(owner: owner.class, association: klass)
          end

-         if reflection.strict_loading?
+         if reflection.strict_loading? && owner.validation_context.nil?
            Base.strict_loading_violation!(owner: owner.class, association: reflection.name)
          end

「テストコードを見ると、AuditLogRequiredモデルのrequired: trueがバリデーションに失敗したときにralseしないことをチェックしている↓」

# activerecord/test/cases/strict_loading_test.rb#L89
  def test_strict_loading_is_ignored_in_validation_context
    with_strict_loading_by_default(Developer) do
      developer = Developer.first
      assert_predicate developer, :strict_loading?

      assert_nothing_raised do
        AuditLogRequired.create! developer_id: developer.id, message: "i am a message"
      end
    end
  end

「このプルリクの日時を見ると、わずか15時間前(つっつき時点)にマージされてたんですね」「Rails 6.1がリリースされる直前じゃないですか」「間に合ってよかった🎉

⚓ 6.1: I18n.translateでキーがStringの場合に対応

# actionview/lib/action_view/helpers/translation_helper.rb#L70
      def translate(key, **options)
        return key.map { |k| translate(k, **options) } if key.is_a?(Array)
+       key = key.to_s unless key.is_a?(Symbol)

        alternatives = if options.key?(:default)
          options[:default].is_a?(Array) ? options.delete(:default).compact : [options.delete(:default)]
        end

つっつきボイス:「translateのキーがシンボルでなくStringの場合も対応するようになったのね」「前はStringでもできてたような気がしたけど、#39989のコメントを見ると、そこでパフォーマンスを改善したときにto_sを外したことでキーがたとえばIntegerの場合にうまくいかなくなると指摘されているので、それを受けて上の#40773でキーがシンボルでない場合にも対応できるよう修正したという流れのようですね」「なるほど!」「translateのキーに数値を入れることは実際にはあまりなさそうですけどね」

「要するにシンボルでなかったら数値でも何でも文字列にしとけば大丈夫と」「Rubyのto_sはObjectクラスにあるので、Objectクラスを継承するオブジェクトは必ずto_sで文字列に変えられますよね」

ドキュメント: Object#to_s (Ruby 2.7.0 リファレンスマニュアル)

⚓ 6.1: Ruby 3.0のStringの挙動修正に対応

Ruby 3では、Stringクラスのメソッドがサブクラスのインスタンス上で呼び出されたときにStringクラスのメソッドが常にStringのインスタンスを返すという非互換の変更が導入された。
https://bugs.ruby-lang.org/issues/10845
ruby/ruby#3701

これはStringのサブクラスであるActiveSupport::SafeBufferにわずかに影響するので、SafeBuffer#[]SafeBuffer#*でRuby 2の振る舞い(別のSafeBufferインスタンスを返す)をRuby 3でも維持するパッチを用意した。

なお、Ruby 3.0で変更されたメソッドのほぼすべては、既にSafeBufferでStringを返すためのオーバーライドが完了しているので、このテストをパスするために必要なパッチはこの2つだけ。
同PRより大意


つっつきボイス:「@amatsudaさんによるプルリクです」「Ruby 3.0の足音が聞こえてきそう」「修正の経緯について#10845に書かれているみたい↓」

「まず、従来のRubyではStringを継承したクラスが返すオブジェクトが以下の*+%のように不揃いだった↓のが、Ruby 3.0でStringを返すように統一された」

# 10845より
class MyString < String
end

MyString.new("foo").*(2).class                        #=> MyString
MyString.new("foo").+("bar").class                #=> String
MyString.new("%{foo}").%(foo: "bar").class #=> String

「RailsのSafeBufferは以下のようにStringを継承しているので、Ruby 2.xまではSafeBufferを返していたメソッドがRuby 3.0ではStringを返すように変わってしまったということか」

# activesupport/lib/active_support/core_ext/string/output_safety.rb#133
module ActiveSupport #:nodoc:
  class SafeBuffer < String

...

    def [](*args)
      if html_safe?
-       new_safe_buffer = super
+       new_string = super

-       if new_safe_buffer
-         new_safe_buffer.instance_variable_set :@html_safe, true
-       end
+       return unless new_string

+       new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
+       new_safe_buffer.instance_variable_set :@html_safe, true
        new_safe_buffer
      else
        to_str[*args]
      end
    end

...

    def *(*)
-     new_safe_buffer = super
+     new_string = super
+     new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
      new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
      new_safe_buffer
    end  

「それを上のように、返すものがSafeBufferでない場合はSafeBuffer.newすることでRuby 2.xと3.0で挙動が変わらないよう三項演算子で修正したんですね」「あ、なるほど!」「Ruby 2.xではSafeBufferを返すのでこれまでと同じ挙動になる」「Rubyのバージョンをチェックするif文を書かずに挙動を揃えているのがうまいですね😋


後で以下の#3701を見ると、Ruby 3.0のStringクラスの#+#-以外のメソッドはすべて、StringのサブクラスのインスタンスではなくStringインスタンスを返すよう統一されるんですね。#3701のコメントの中でもSafeBufferについて言及されていました。

⚓ 6.1: Relation#mergeの利用法をガイドに追加

現在のActive Record クエリインターフェイスガイドの「結合されたテーブルで条件を指定する」にはRelation#mergeの利用法を示すサンプルがない。
既存のサンプルは比較的基本的なjoinedテーブルの条件を生成するにはよいが、Relation#mergeは高度なSQLクエリ生成や既存の名前付きスコープの利用に欠かせない。
同PRより大意


つっつきボイス:「Relation#mergeのドキュメントはたしかに欲しいですね」「こういう情報がガイドに追加されるのはありがたい🙏

# 同PRのガイドより
class Order < ApplicationRecord
  belongs_to :customer
  scope :created_in_time_range, ->(time_range) {
    where(created_at: time_range)
  }
end

time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Customer.joins(:orders).merge(Order.created_in_time_range(time_range)).distinct

⚓ 6.1: require_dependencyhelperから削除

これだけ今年5月にマージ済みのプルリクです。

プルリクの動機は以下の2本立て:

  • require_dependencyは現在フレームワークから段階的に削除を進めている
  • config.add_autoload_paths_to_load_pathが無効の場合にhelperが動くようにする
    同commitより大意

コントローラのhelperクラスメソッドは、stringやsymbolで指定されているヘルパーモジュールをrequire_dependencyではなくString#constantizeで読み込むようになった
Action Pack Changelogより


つっつきボイス:「使わないことになったrequire_dependencyがRailsフレームワークに残っていたのを削除したんですね」「Rails 6.1のアップグレードガイド(edge版)を見て知りました」

参考: 2.6.3 require_dependencyについて — Rails アップグレードガイド - Railsガイド

require_dependencyの既知のユースケースはすべて排除されました。自分のプロジェクトをgrepしてrequire_dependencyを削除してください。
railsguides.jpより

⚓Rails

⚓ RuboCop Rails 2.9がリリース


つっつきボイス:「@koicさんのツイートでこれを含むさまざまな更新情報を知ることができて助かります🙏」「え、今日ちょうどRuboCopのバージョン上げたところなんですけど😳」「こちらはRuboCop Railsなので本体のRuboCopとは別物ですね」「よかった〜」「RuboCop本体もこの後Rubyのコーナーで取り上げます(明日のウォッチに掲載します)」

rubocop-hq/rubocop-rails - GitHub

RuboCop Rails 2.9.0リリースノートの変更点より:

  • RuboCop 0.90以上が必須になる
  • Rails/SquishedSQLHeredocsをunsafeに変更

⚓ acts_as_tenant: マルチテナンシーgem(Ruby Weeklyより)

ErwinM/acts_as_tenant - GitHub


つっつきボイス:「READMEのこのサンプルコード↓ではサブドメインでテナントを分けていますね: サブドメインでマルチテナント化する設計はよく使われていて、Slackなどでも行われています」

# 同リポジトリより
class ApplicationController < ActionController::Base
  set_current_tenant_by_subdomain(:account, :subdomain)
end

「ただ、マルチテナントの要件はプロジェクトごとに詳細がいろいろ異なるんですよ: acts_as_tenant gemはまだ使ったことはありませんが、詳細を把握しきっていないgemでマルチテナンシーすることを考えると、おそらく自分ならマルチテナンシーの機能を自分で実装する方を選ぶことが多いかもしれませんね」「あぁ、たしかに!」「gemが案件にフィットするかどうかは案件の成長なども含めて検討しておく必要があるでしょうね: acts_as_tenantはたぶんそういう種類のgemだと思います」

「もちろん、それまで十分使い慣れていて、要件に合致することが確かめられているなら使ってもよいと思います👍」「acts_as_tenant gemは歴史もあるしサポートも継続しているようなので、その点は大丈夫そうですね」

「Railsの認証機能でよく使われるDevise gem↓などもそうなんですが、gemで機能を取り入れるということは、そのgemの機能の範囲で構築せざるを得なくなることでもありますよね」「そうなんですよね…」「gemの詳細や案件との調和をよく調べないうちにDeviseのようなgemを安易に導入すると、後がつらくなるから気をつけようという話もよく目にします」「はい、身に沁みてます😅

heartcombo/devise - GitHub


「そういえば、昔のRails向けgemにはacts_as_なんちゃらという名前がよくあったというお話しを以前されてましたね」「昔はそういうネーミングのライブラリが多かったんですが、そういう名前でもライブラリが古いとは限らないでしょうね」「あ、そうか」「このacts_as_tenantは新しいのかな?」「リリースをさかのぼってみると2012年からありますね」「ホントだ」「★はあと少しで1000になるくらいかな」「おそらく今話したみたいに、Railsのマルチテナンシー機能については案件に応じて自前で実装することが多いのかもしれませんね」


追いかけボイス:「後でacts_as_tenantの実装を少し追いかけてみた限りでは、Deviseと比べてだいぶ薄めのgemのようです」「なお、マルチテナンシーだとapartmentというgem↓もあって、もしかするとこちらの方がメジャーかもしれません」

influitive/apartment - GitHub

⚓ ar_lazy_preload: GraphQLでも役立つlazy load gem(Ruby Weeklyより)

DmitryTsepelev/ar_lazy_preload - GitHub

TechRacho翻訳記事でお世話になっているEvil Martiansがスポンサーになっています。


つっつきボイス:「このgemの#lazy_preloadメソッドを使って読み込んでおくと、たとえば後でmapしてもクエリを1回しか実行しなくなるということか↓」

# 同リポジトリより
users = User.lazy_preload(:posts).limit(10)  # => SELECT * FROM users LIMIT 10
users.map(&:first_name)

「コンフィグでlazyなオートプリロードをオンにすることもできる↓」

# 同リポジトリより
ArLazyPreload.config.auto_preload = true

#preload_associations_lazilyも使える↓」

# 同リポジトリより
posts = User.preload_associations_lazily.flat_map(&:posts)
# => SELECT * FROM users LIMIT 10
# => SELECT * FROM posts WHERE user_id in (...)

「READMEを眺めた限りでは比較的シンプルな機能のgemみたい」「lib/を覗いてみても、コンフィグも少ないし、比較的シンプルそうですね」「同じコードを自力で書くよりはこういうgemでやる方がよさそう👍


ArLazyPreloadは、関連付けのlazy loading機能をRailsアプリケーションに導入するgemです。N+1クエリ問題を解決するRails組み込みメソッドはたくさんありますが、プリロードする関連付けのリストが明確でない場合があります。そんなときはこのgemで大半をカバーできます。

シンプル
利用に必要なのは、#includes#eager_load#preload#lazy_preloadに置き換えることだけです。
高速
ベンチマークをご覧ください(TASK=benchTASK=memory)。
GraphQLとの親和性
読み込む関連付けのリストをトップレベルのリゾルバで定義すれば後はこのgemにおまかせ
オートプリロードのサポート
関連付けのリストを指定したくない場合はArLazyPreload.config.auto_preloadtrueに設定します。

同リポジトリより

「READMEにはGraphQLでも便利と書かれていますね」「以下のような感じで、GraphQLのリゾルバでカラムを取得してからmapするような操作はGraphQLでよく使うので、たぶんそれを指しているんじゃないかな」

# 同リポジトリより
users = User.lazy_preload(:posts).limit(10)  # => SELECT * FROM users LIMIT 10
users.map(&:first_name)

「GraphQLでは、最終的に欲しいカラムをGraphQLのリクエスト側で指定できるんですが、おそらくこのgemの機能を使うと、GraphQLリゾルバの直前まではActive Recordのリレーションのまま加工して、最後の最後でGraphQLからのカラムを渡すと、そのカラムでSELECTするクエリを発行して結果を返す、という感じでクエリ発行が1回で済むようにするのがやりやすくなるんでしょうね」「なるほど!」「なかなかよさそうなgemですね👍

参考: GraphQL - Resolvers


前編は以上です。

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

週刊Railsウォッチ(20201209後編)Ractorベンチマーク記事、Railsで複合主キーを使う、AWS re:Invent 2020ほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20201216後編)Ruby 3.0.0-rc2とRuboCop 1.6がリリース、Ruby 3の静的型解析記事、CentOS 8のEOLが短縮ほか

$
0
0

こんにちは、hachi8833です。

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

⚓Ruby

⚓ Ruby 3.0.0-rc2リリース(Ruby公式ニュースより)


つっつきボイス:「Ruby 3.0.0のRC2がついに出ましたね」「Rubyはクリスマスにメジャーリリースするのが通例なので、もう後2週間🎄」「2.xから3は大きいですけど、無事に出てくれるかしら?」「大丈夫だと思いますよ」「今もTypeProf周りとかにゴリゴリ手を入れているようですし、たぶんギリギリまで作業するでしょうね」

「あ、今になってRuby 3.0アドベントカレンダーがあることに気づいた↓(後で読もうっと)」

参考: Ruby 3.0 Advent Calendar 2020 - Qiita

つっつきの後で、上の記事の中から銀座Rails#26で@osyoさんが発表したスライドを見つけたので貼っておきます。

他のRuby関連アドベントカレンダーも貼っておきます。

参考: Ruby Advent Calendar 2020 - Qiita
参考: Ruby その2 Advent Calendar 2020 - Qiita
参考: 一人 bugs.ruby Advent Calendar 2020 - Qiita

⚓ Rubyがruby-build-v20201210でM1チップでのビルドに対応

「M1チップ上で、インテルバイナリでないarm64バイナリのRubyが動くようになったとは」「お〜すげ〜!」「M1 Macまだ持ってないけど嬉しいです😂

⚓ Ruby 3.0の静的型解析関連記事


つっつきボイス:「そうそう、@mameさんの静的型解析関連記事が2本も出ていましたね」「@mameさんの解説が記事の形でみっちり書かれているのが本当にありがたい🙏」「動画を見るには集中力も必要だし、高度な話題を扱うとたまについていけなくなりますけど、文章で書かれていると自分のペースで読めますよね」「Ruby 3.0の静的型解析周りの機能については@jnchitoさんもやっているように自分で触って理解するのがいいでしょうね」「後で読まなきゃ」

「@mameさん記事の冒頭で、RBSとTypeProfとSteep/Sorbetの違いを3行でまとめてくれているのも助かる!」「新しいのでどれがどれだかたまにわからなくなりがちでした😅」「Ruby 3にバンドルされるのがRBSとTypeProfで、SteepやSorbetは自分で導入できるツールということか」

soutaro/steep - GitHub

sorbet/sorbet - GitHub

「今やってるRailsアプリ、Ruby 3.0にアップグレードしようかな、どうしようかな?」「動作確認してみるのはよいと思います!」「後はRailsで使うgemがどのぐらい3.0で動くかでしょうね」「あ、それもそうか」「しばらくは動作確認の期間になるかな」

⚓ RuboCop本体も1.6がリリース


つっつきボイス:「こちらはRuboCop本体のアップグレードです」「え、RuboCop本体はちょっと前にやっと1.0になった気がするんですけど、もう1.6ですか!この間やっとRuboCopを0.8xに上げたところなのに…」「リリース履歴を見てみよう↓」

「1.0のリリースが今年2020年の10月!」「1.3も28日前ですね」「わずか2か月で1.6とは」「1.6にいたってはsome hours agoですね(つっつき時点)」「これは早い」「怒涛の勢い」「RuboCopは1.0になるまでが長かったですけどね」

「自分のRailsアプリなどでRuboCop本体を最新のものにアップグレードすることについては基本的に問題はないと認識してます(rubocop.ymlの更新はそれなりに手間だと思いますが)」

⚓ Rubyコードにendが余分にあるエラーをsyntax_search gemで修正する(Ruby Weeklyより)

zombocom/syntax_search - GitHub


データベースのランダム読み出しは要注意(翻訳)

つっつきボイス:「TechRacho翻訳記事でもお世話になっているRailsコントリビューターの@schneemsさん↑の最近の記事で、Rubyコードのendが1個多いエラーを解決するためにsyntax_searchというツールを使ったそうです」「余分なendがどこにあるのかわかりにくいのあるある😆

Syntax Search: Extra end

「ところでこれってIDEとかのlinterにかければ一発でできるのでは?」「あ、それもそうか」「でもこうやって直したくなった気持ちもわかる」

⚓ RubyのRange#bsearchでは0と無限大の中間値が「1.5」になる(Ruby Weeklyより)


つっつきボイス:「Range#bsearchはRubyの二分探索(binary search)メソッド」「0と無限大の中間値って数学的にはどうなるんでしたっけ?」「数学好きなkazzさんが今日のつっつきにいてくれれば聞けたのに😢」「記事によるとRubyのRange#bsearch的には1.5が中間値ということになるのかな?」「0とinfiniteの中間値を取るのがそもそもありなのかどうかが気になる…」

参考: Range#bsearch (Ruby 2.7.0 リファレンスマニュアル)
参考: 二分探索 - Wikipedia

「記事ではビット表現を図解してそのあたりを追求してますね↓」


同記事より

⚓DB

⚓ Slackで使われているVitess(DB Weeklyより)

vitessio/vitess - GitHub


つっつきボイス:「Vitessは以前ちょっとだけウォッチで取り上げたことがあって(ウォッチ20181225)、YouTubeのバックエンドでも使われていたそうです」

「記事の冒頭を見ると、Slackも立ち上がりの頃はLAMPで構築されていたと書かれているところに歴史を感じますね」「ランプ?」「Linux、Apache、MySQL、PHPのことで、昔からよくある組み合わせです」「あ、そういう言葉があるんですか😳」「今のSlackの規模だとMySQL単体ではもう無理でしょうね」

「Vitessはこの図のような概念らしい↓」「記事によるとSlackのSQLトラフィックの99%がVitessに置き換わったそうです」


同記事より

「VitessのバックエンドはMySQLなのか」「MySQLをこういう感じにドーピングというか拡張するツールは昔からいろいろありますね」「Vitessは配下にあるMySQLサーバーのテーブル群をクラスタリングしてアクセスできるようadaptしてくれるみたい」「Vitessはこの図のような感じでクエリを分散したりシャーディングをすごく賢く行うなどの最適化を図るんでしょうね↓」


同記事より

「Vitess面白そうだけど、後から導入するのは大変なんでしょうか?」「この種の製品は、アプリケーションからVtGateにアクセスするときはMySQLと同じにやれるようにすると思うので、たぶん移行はそこまで大変じゃないと思いますけどね」「アプリケーションからするとつなぎ先を変えるだけという感じですか?」「アプリケーションコードを変える部分は少ないと思います」「お〜!」

「たぶん別製品だったと思いますけど、このような感じで既存のDB接続に挟んでいい感じに強化するツールの営業を昔受けたのを思い出しました」「そういえばHAProxyなんてのもあったかも↓」「HAProxyはVitessと設計上の位置が割と近そうですが、Vitessはテーブル同士のlocalityとかまで考慮してクラスタリングするようなので、よりDB特化したツールのイメージを感じますね」「Vitessはオープンソースなのか」「この種のソフトウェアは商用が多いです」

参考: HAProxy - Wikipedia

「この記事↓をざっと見た感じでは、Vitessにはkeyspaceという概念があるとのことなので、アプリケーションからはVitessのデータベースとして見えるようですね」「なるほど」「おそらくスキーマ定義さえされていれば、普通にクエリを投げるだけならMySQLとほぼ等価に扱えそうな雰囲気は感じられる」「そこから先は記事やドキュメントを詳しく読まないとですね」

参考: Vitess(ヴィテス)をさわってみよう!(Part 1) | スマートスタイル TECH BLOG|データベース&クラウドの最新技術情報を配信

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

⚓ 書籍『イラストでわかるDockerとKubernetes』


つっつきボイス:「はてブでバズっていました」「図を多用してわかりやすく解説するのは大事ですね👍」「Kubernetes界隈は動きが早いので、最新情報を取り入れるにもよさそう」「5年前と比べたらKubernetesも相当変わってるんだろうな…」

⚓言語/ツール/OS/CPU

⚓ CentOS 8がサポート期間を繰り上げて2021年に終了

参考: CentOS 8が2021年で終了することに関する基礎知識 - orangeitems’s diary


つっつきボイス:「CentOS 8の件、これを踏んだプロジェクトはつらいでしょうね」「CentOS使っているところは今後どうするか相談中みたいですよ」「そんなに影響あるんでしょうか?」「SI方面でよくある、パッケージのデフォルト構成を変えずに使いたいような要件の固い開発プロジェクトが主に影響を受けると思いますし、実際CentOSはそういうところでよく使われているんですよ」「なるほど、ちょっと想像が付きました」「CentOSは日本で使っているところが割と多いですね」

「この記事↓にもありますけど、今回CentOS 8のEOL(end-of-life)が大きく短縮されたために、今ではCentOS 7の方がEOLが長いんですよ」「まさかEOLが逆転するとは思いませんよね」「CentOS 7を使っているところはEOLが変わらないので難を逃れられますけど、CentOS 8を使っているところは…😇

「たとえばPHPの5系のような古いバージョンはPHPの公式ではサポート終了していますが、CentOSだとその後もサポートされていたりするんですよ」「そういえばCentOSのPHPって割と古かった覚えがあります」「CentOS 7ですら、今PHPのデフォルトバージョンを調べてみると5.4ですね」「そんなに前のバージョンだったとは…」

「そのようなPHPはPHP公式では既にサポートされなくなっていますが、CentOSでは致命的な脆弱性についてはパッチを当て続けてくれていたんですよ」「そうそう」「古いPHPが必要なプロジェクトでは、あえてCentOSを選んで主要なパッチがサポートされるようにすることもあります(すべてをサポートするわけではありませんが)」「PHPのバージョンを上げるのが大変だったのを思い出しました😢」「PHPが古いとフレームワークのバージョンも上げられなかったりしますよね」「CakePHPも2から3に上げるのは大変だって聞きました」「2から3はアーキテクチャが変わるので大変です」(以下延々)

参考: CakePHP - Build fast, grow solid | PHPフレームワーク


追記(2020/12/16): 以下の記事もどうぞ。

参考: CentOS Streamは継続的デリバリーです - 赤帽エンジニアブログ

⚓その他

⚓ おかえりなさい


つっつきボイス:「はやぶさ2のカプセルが無事帰ってきました」「あれ、帰ってきてたの知らなかった」「テレビあんまり見てなくて実感があまりなかったけど、ニュースでは報道されてるのかな?」「NHKが結構力入れて報道してました」


昔買った『探査機はやぶささん』(JAXA監修)↓を久しぶりに読み返しちゃいました。

その後カプセル内から砂粒が確認されたそうです↓。

参考: はやぶさ2「大粒試料どっさり、言葉失った」 小箱開封で黒い石確認 - 毎日新聞


後編は以上です。

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

週刊Railsウォッチ(20201214前編)Rails 6.1の直近コミットを見る、RuboCop Rails 2.9リリース、ar_lazy_preload gemほか

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

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

Ruby 公式ニュース

Ruby Weekly

DB Weekly

db_weekly_banner

週刊Railsウォッチ(20201221前編)aws-sdk-rails gemの機能をチェック、RubyWorld Conference 2020のDHHインタビューほか

$
0
0

こんにちは、hachi8833です。約400年ぶりという木星と土星の超大接近は12/21(月)なので今夜ですね。と思ったらもう西の空に沈んでしまったようです。

参考: 【特集】2020年12月 木星と土星の超大接近 - アストロアーツ

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

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

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


つっつきボイス:「Rails 6.1がリリースされた直後なので、コミットリストも6.1修正が多そうですね」「今手掛けているRailsアプリも早速6.1にアップグレードしました」「お、どうでした?」「論理削除でおなじみのparanoia gemが動かなくなりました😇: それ以外は大丈夫っす」「gemが追いついてないのはよくありますね」

rubysherpas/paranoia - GitHub

「そうなんですよね、リリースされてから使い始める自分💦」「rcが取れるまでに多くの人がお試しするのが本当は望ましいですよね」

⚓ translatenilキーを渡したときの挙動を修正


つっつきボイス:「コミットメッセージにnilがいっぱい書かれててややこしい…」「translatenilを渡すとエラーになったのを修正したということのようだけど、defaultオプションを効くようにもしたらしいとありますね」

I18n.translatenilキーを渡すと、defaultも同時に指定されていない場合はnilを返す。defaultも指定しておくと、nilキーは「見つからないキー」として扱われる。

Rails 6.0のtranslateヘルパーは、nilキーを渡すと常にnilを返すが、#40773以後はtranslateヘルパーにnilキーを渡すと常にI18n::ArgumentErrorをraiseするようになった。このコミットは、defaultを指定せずにI18n.translatenilキーを渡すときの振る舞いと同じになるようtranslateヘルパーを修正する。
同コミットより大意

「テストコードとChangelogの方が見やすいかな↓: default:オプションを指定しておけばnilキーを渡してもdefault:で渡したキーにフォールバックしてくれるようになったのね」「あ、そういうことですか」「例外を出されて止まっちゃうと困るので、これは必要な修正ですね」

# actionview/test/template/translation_helper_test.rb#80
  def test_returns_nil_for_nil_key_without_default
    assert_nil translate(nil)
  end

  def test_returns_default_for_nil_key_with_default
    assert_equal "Foo", translate(nil, default: "Foo")
    assert_equal "Foo", translate(nil, default: :"translations.foo")
    assert_predicate translate(nil, default: :"translations.html"), :html_safe?
  end

translateヘルパーにnilキーを渡したときに、常にnilを返すのではなく、defaultで指定したキーに解決するようになった。
Changelogより

⚓ nonnamed expression indexを追加した後マイグレーションで元に戻せるように修正


つっつきボイス:「revertibleって何をrevertするんでしょう?」「Changelogには、ロールバックすると以下でエラーになったとあるけど…」

add_index(:items, "to_tsvector('english', description)")

「元のissueを見る方がいいかな↓」

「普通のデータベースインデックスではない、以下の"to_tsvector('english', description)", { using: :gin, name: 'index_items_on_to_tsvector_english_description' }のような関数インデックスをマイグレーションで追加すると、その関数インデックス名が自動生成されていた場合はマイグレーションをロールバックしたときにエラーになるということか」「あ、そういうことですか」「ロールバックするときに自動生成済みインデックス名を解決できなくて落ちてたのを、ロールバックできるように修正したということのようですね」

# #40732より
class AddFullTextSearchIndexToItemDescription < ActiveRecord::Migration[6.0]

  # Note: this migration is irreversible, to revert it,
  # please use the __Reversable__ block below and comment out the Irreversible block
  # then rake db:rollback will work
  def change
    # __Reversable__
    # add_index(
    #   :items,
    #   "to_tsvector('english', description)",
    #   { using: :gin, name: 'index_items_on_to_tsvector_english_description' }
    # )

    # Irreversible
    add_index(
      :items,
      "to_tsvector('english', description)",
      { using: :gin }
    )
  end
end

「つまりrevertibleはマイグレーションをロールバックできるという意味なんですね」「この修正、超大事じゃないですか!」「知らずにロールバックしたら即死ですもんね😇」「こういうマイグレーションってあまり書かないだけに踏んだときがつらそう…」

⚓ Unreleased: 失敗したリクエストをconfig.exceptions_appに渡すときのリクエストメソッドをGETに変更


つっつきボイス:「これはChangelogのUnreleasedに書かれていたので、6.1には入っていないようです」「こっちの関連issueを見てみるかな↓」

「RailsガイドによるとActionController::UnknownHttpMethodは自動的にrescueされてmethod_not_allowed(HTTP 405)になるはずだったのにHTTP 500エラー(内部エラー)になってしまったのね」「ガイドと挙動が違ってた問題ですか」「RailsのRackミドルウェアの構成によっては期待どおりに動かないことがあったらしい」

参考: 3.9 Action Dispatchを設定する — Rails アプリケーションを設定する - Railsガイド

「RailsのRackミドルウェアがUnknownHttpMethodのエラーを食べてしまうと正しく405エラーを返せないことがあったので、内部的にはrequest methodをGETということにして処理を継続するようにしたようですね↓」「それだけだと元のリクエストの種類がわからなくなるので、それをoriginal_request_methodに保存したということか」

# actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L43
      def render_exception(request, exception)
        backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
        wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
        status  = wrapper.status_code
        request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
        request.set_header "action_dispatch.original_path", request.path_info
+       request.set_header "action_dispatch.original_request_method", request.raw_request_method
        request.path_info = "/#{status}"
+       request.request_method = "GET"
        response = @exceptions_app.call(request.env)
        response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
      rescue Exception => failsafe_error
        $stderr.puts "Error during failsafe response: #{failsafe_error}\n  #{failsafe_error.backtrace * "\n  "}"
        FAIL

「GETじゃなくて405を返すのはダメでしょうか?」「ミドルウェアスタックの途中で処理を止めたくないでしょうし、未定義のリクエストメソッドが来る可能性もあるので、処理継続のために便宜上GETにしたんじゃないかな」

参考: Rails と Rack - Railsガイド

UnknownHttpMethod自体普通はあまりないエラーだと思います」


#40246で修正された#38998と似たような感じで、ActionDispatch::Requestでメソッドが呼び出されると必ず行われるHTTPメソッドバリデーションで、予想外の妙な結果が生じることがある。たとえば、config.exceptions_app = self.routesの場合、ActionDispatch::ShowExceptionsというミドルウェアフェイルセーフで例外が発生する。

この冗長な例外を防ぐため、envconfig.exceptions_appに渡る前にrequest_methodを上書きした。オリジナルのリクエストメソッドは維持されるのでaction_dispatch.original_request_methodで維持されるので、インスペクトもできる。
同PRより

⚓ 6.1: attribute_for_databaseメソッドを追加

ここからは少し趣向を変えて、@kamipoさんの記事より見繕いました。


つっつきボイス:「上のマージ自体は今年10月に行われていて、ウォッチでは取り上げてませんでした」「ああ、例のenumのデータベース上の値を取れるようにした改修ですね」

enum state: {active: 0, inactive: 1}とかした時に、typecast前の0とか1を取りたい
blog.kamipo.netより

なお、attribute_for_databaseなどのattributeは属性名に置き換わるので、属性名_for_database属性名_before_type_castという感じの名前になります。

attribute_for_databaseメソッドはたしかに欲しい!」「attribute_before_type_castだとenumがtypecastしているかどうかを意識しながらになるので、それだと欲しいものとちょっと違うという気持ちもわかります」

参考: attribute_before_type_cast — ActiveRecord::AttributeMethods::BeforeTypeCast

「以下で言うと、book.statusはデータベースに2が入っていて、そのデータベースの値を取ろうとすると、Rubyはbook.status"published"を返すので、book.status_before_type_castを使ってデータベースから読み込み済みの2を取っていた」「ふむふむ」「それがこの改修で、book.status_for_databaseと書けばenumの"published"に対応する2をデータベースから読み込んで取れるようになった」「あ、なるほどわかりました」

# 同PRより
book = Book.new(status: "published")

# returns "published", but what we really want is 2.
book.status_before_type_cast

attribute_before_type_castよりattribute_for_databaseというメソッド名の方がデータベースから取ってくる操作なのがわかりやすいかも」「何かの都合でデータベースにあるenumの値を取りたいことはよくありますね」

⚓ 6.1: whereで関連付け名をjoinedテーブルのエイリアス名として参照できるようになった

belongs_to :author, class_name: 'User'したときにleft_joins(:author).where("author.id": nil)とか書きたい
blog.kamipo.netより


つっつきボイス:「こちらも今年8月にマージ済みでしたがウォッチで取り上げていなかったので」「そうそう、神速さんのツイートにもあるようにbelongs_toで別名を使うと、joinsは別名で、whereは元のテーブル名を使わないといけなかったんですよね↓」

「それがこの修正によって、以下のテストコードのFirm.includes(:clients).where("clients.new_name": "Summit")のようにドット付きの"clients.new_name"で参照できるようになったのか」「ネステッドハッシュによるアクセスとは違うけど、"clients.new_name"というドットアクセスをキーに書けるようになったんですね」

# activerecord/test/cases/associations/eager_test.rb#202
  def test_type_cast_in_where_references_association_name
    parent = comments(:greetings)
    child = parent.children.create!(label: "child", body: "hi", post_id: parent.post_id)

    comment = Comment.includes(:children).where("children.label": "child").last

    assert_equal parent, comment
    assert_equal [child], comment.children
  end

  def test_attribute_alias_in_where_references_association_name
    firm = Firm.includes(:clients).where("clients.new_name": "Summit").last
    assert_equal companies(:first_firm), firm
    assert_equal [companies(:first_client)], firm.clients
  end

「ツイートのコードで言うとauthor.何とかみたいに書けるようになったということですか?」「ですです」「お〜これは便利そう!」「joinsがスッキリ書けてありがたい🙏」「ネステッドハッシュで書けるかどうかは少なくともこのテストには見当たらないですね」


あるテーブルが複数回joinsされると、それらのテーブルは最初の名前でない別名になる。
これは自己参照的な関連付けで起こりやすく、現在はその場合にwhere条件の別名テーブルでカスタム属性(type casting)や属性エイリアス名の解決が効かない。
この問題を修正するために、whereで関連付け名をエイリアス名で参照できるようにする。関連付け名がwhereで参照されると、それらの名前にjoinedテーブルのエイリアス名が使われる。
同PRより大意

# 同PRより
class Comment < ActiveRecord::Base
  enum label: [:default, :child]
  has_many :children, class_name: "Comment", foreign_key: :parent_id
end

# ... FROM comments LEFT OUTER JOIN comments children ON ... WHERE children.label = 1
Comment.includes(:children).where("children.label": "child")

なお@kamipoさん記事3つ目の#39830は以下の記事をどうぞ。

Rails 6.1: 属性にデフォルト値を設定しても型が失われなくなった(翻訳)

この後さらに@kamipoさん記事が出ていました。こちらもつっつきで少し見てみましたが記事では割愛します🙇

⚓Rails

⚓ RubyWorld Conference 2020のキーノート: DHHインタビュー by Matz


つっつきボイス:「(2020/12/17 20:30頃)さっきRubyWorld Conference 2020がちょうど終わった頃だそうで、最後のキーノートであるMatzによるDHHインタビューが各所で評判になっていました↑」「お〜、DHHがRailsとフロントエンドの話もしたんだ!」「そこに関心のある人が多いからフロントエンドの話題は欠かせないでしょうね」「自分はRailsは当分滅びないと思っていますけど、フロントエンドをRailsでやる理由はだいぶ少なくなったとも思ってます」「今度動画見てみようっと」「誰か速攻で文字起こしとかしないかな」

なお、つっつきの時点では英語版動画にしか気づいていませんでしたが、以下の同時通訳付き動画でもキーノートインタビューを見られます(頭出し済み: 7:11:59)↓。

満を持してのDHH登壇だったそうです↓。

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

⚓ RailsとCableReadyとStimulus ReflexでリアクティブなTwitterクローンを構築する

以下のツイートで知りました。

hopsoft/chatter - GitHub

hopsoft/cable_ready - GitHub

hopsoft/stimulus_reflex - GitHub


つっつきボイス:「Stimulusといえば、11月の銀座RailsでもStimulus関連の発表がありました↓」「お、そうなんですね」

「Stimulusってつっつきで取り上げてましたっけ…よく知らなかった😅」「割と登場してたと思います(サイト内検索)」「StimulusはRailsでお馴染みのBasecampが作って使っているJavaScriptライブラリで、Rails方面の記事でちょくちょく見かけますね↓」「なるほど〜」「StimulusはRailsのオフィシャルに近い印象あります」「今のRails wayに則ってフルスタックでフロントエンドまでやるならStimulusとRailsの相性はいいでしょうね」

stimulusjs/stimulus - GitHub


なお、Stimulusの2.0もリリースされたそうです。

⚓ @kamipoさんの記事より


つっつきボイス:「@kamipoさんのこの記事は唸りました: こうやって解説してもらわないとわからない世界」「ですよね」

「SELECT … FOR UPDATEは割とよく使われるSQL構文で、デッドロックしないという都市伝説がまことしやかにあったんですが、実際はそんなことはなくてデッドロックは起きるというお話」「プライマリキーとセカンダリキーで並び順が変わるデータにしておくと、プライマリキーとセカンダリキーがクエリプランのSELECT … FOR UPDATEでロックの一方が上から順、もう一方が下から順に同時に進んでいって、それでデッドロックになるというのを記事で再現してますね」「あ〜、そういうことですか!」「同じテーブルの中でのロックを取る順序というものをこれまで気にしたことがなかったことに気付かされました」「そんなことがあるとは…」

「ロックを取る順序が一意になるようにクエリやクエリプランを揃えるのが一般的な回避法だそうですが、そこまでチェックしないといけないのか」「@kamipoさんも『眼の良さを活かして気合いで対処』なんですね」「これはデータベース強者でないとなかなか気づけない問題」「データベース強者、なりたいです…」「取りあえずオラマスを目指すところからですかね↓」

参考: ORACLE MASTER Portal - be an ORACLE MASTER - | オラクル認定資格制度 | Oracle University


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

⚓ aws-sdk-rails gemの新機能(Ruby Weeklyより)

aws/aws-sdk-rails - GitHub


つっつきボイス:「aws-sdk-rails gemって知らなかった」「aws-sdk-ruby↓ではないんですね😆」「どちらもAWS公式のgemですけど、そういえばaws-sdk-rubyの方は以前のSDKがバージョンアップされたときがちょっと大変でしたね」「私も思い出しました」

aws/aws-sdk-ruby - GitHub

参考: AWS SDK for Rubyの S3署名バージョン2廃止に対応しました [2019/6/24期限迫る!] - LCL Engineers’ Blog

「どれどれ」「お、DynamoDBセッションストアなんて機能がある↓」

# aws.amazon.comより
rails generate dynamo_db:session_store_migration

参考: Amazon DynamoDB(マネージド NoSQL データベース)| AWS

「次はActive Support Notification Instrumentationサポート」「お〜、言われてみればRailsのInstrumentationで投げた通知をAWS X-Rayあたりで拾いたいことって割とありそうなので、このSDKでできるなら便利そう」「その分AWSにロックインすることになりますけどね」

参考: AWS X-Ray(分散アプリケーションの分析とデバッグ)| AWS
参考: Active Support の Instrumentation 機能 - Railsガイド

「これを見ると↓、ActiveSupport::Notificationssubscribeでaws-sdk-railsのメソッドを拾うこともできる」「これは有能そう」「こういうのは欲しい機能ですね」

# 同リポジトリより
ActiveSupport::Notifications.subscribe('put_object.S3.aws') do |name, start, finish, id, payload|
 # process event
end

# Or use a regex to subscribe to all service notifications
ActiveSupport::Notifications.subscribe(/S3[.]aws/) do |name, start, finish, id, payload|
 # process event
end

「aws-sdk-railsのリポジトリも見てみると、Railsのencrypted credentialもサポートしてるのか↓」「お〜便利そう!」「SDKにロガーも付いているとは知らなかった」

# 同リポジトリより
# config/credentials.yml.enc
# viewable with: `rails credentials:edit`
aws:
  access_key_id: YOUR_KEY_ID
  secret_access_key: YOUR_ACCESS_KEY

参考: Rails5.2から追加された credentials.yml.enc のキホン - Qiita

「RailsのAction Mailerのdelivery_methodにAWS SES(Simple Email Service)を指定することもできる↓」「便利そうなものがいろいろ入ってますね」「aws-sdk-rails、あまり注目してなかったけどちょっと見直しました」

# 同リポジトリより
# for e.g.: config/environments/production.rb
config.action_mailer.delivery_method = :ses

参考: Action Mailer の基礎 - Railsガイド
参考: Amazon SES(高可用性で低価格なEメール送信サービス)| AWS

「Active JobのバックエンドでAWS SQS(Simple Queue Service)を使うこともできる↓」「これもよさそう😋

# 同リポジトリより
# config/application.rb
module YourApp
  class Application < Rails::Application
    config.active_job.queue_adapter = :amazon_sqs # note: can use either :amazon or :amazon_sqs
    # To use the non-blocking async adapter:
    # config.active_job.queue_adapter = :amazon_sqs_async
  end
end

# Or to set the adapter for a single job:
class YourJob < ApplicationJob
  self.queue_adapter = :amazon_sqs
  #....
end

「キューにSQSを使うだけかと思ったらワーカーをローカルでも動かせるのね↓」

RAILS_ENV=development bundle exec aws_sqs_active_job --queue default

参考: Active Job の基礎 - Railsガイド
参考: Amazon SQS(サーバーレスアプリのためのメッセージキューサービス)| AWS

「aws-sdk-rails、思ったよりよさそう👍」「こういうの見ると使いたくなっちゃいます」「最近やってるRailsアプリの環境がAWS ECSやAWS Fargateになっていることが増えてきて、ワーカーやキューをどうしようかと考えることが多いんですけど、aws-sdk-railsが使えるところがありそう」

参考: AWS Fargate(サーバーやクラスターの管理が不要なコンテナの使用)| AWS

「AWSが公式に提供しているのでサポート面でもありがたい🙏」「あとはSDKのアップグレードがaws-sdk-rubyのときほど大変にならなければさらに嬉しい」


前編は以上です。

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

週刊Railsウォッチ(20201216後編)Ruby 3.0.0-rc2とRuboCop 1.6がリリース、Ruby 3の静的型解析記事、CentOS 8のEOLが短縮ほか

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

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

Rails公式ニュース

Ruby Weekly


週刊Railsウォッチ(20201222後編)TypeProfプレイグラウンド、Ruby 3リリースイベント、Ruby 3は3倍速くなったかほか

$
0
0

こんにちは、hachi8833です。2020年度最後の週刊Railsウォッチをお送りします。来年もどうぞよろしくお願いします🙇

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

⚓Ruby

⚓ @mameさん作のRuby 3 TypeProfプレイグラウンド


つっつきボイス:「Ruby 3のTypeProfをすぐ試せるプレイグラウンドを@mameさんが作ってくれたそうです」「お〜これは凄い!」「バグレポート用のボタンも用意されていますね」

「デフォルトのサンプルコードで早速Analyzeボタンを押すと以下のようにエラー行が出力された↓」

# Errors
test.rb:6: [error] failed to resolve overload: String#+

# Classes
class Object
  private
  def hello_message: (User user) -> String
  def type_error_demo: (User user) -> untyped
end


## Version info:
##   * Ruby: 2.7.2
##   * RBS: 0.20.1
##   * TypeProf: 0.10.0

「RBSで以下のようにage: Integerと型が記述されていて、それが静的チェックで元コードの"The age is " + user.ageに対して上のfailed to resolve overload: String#+を表示したんですね」

class User
  def initialize: (name: String, age: Integer) -> void

  attr_reader name: String
  attr_reader age: Integer
end

def type_error_demo: (User user)が出力でuntypedと認識されてる」「では元コードを"The age is " + user.age.to_sのようにto_sでStringに変換してみると…エラーも消えた🎉」「おぉ〜」

「こうやってコードに対応するRBSで型を記述しておくことで、コード実行前にTypeProfで型の不一致を検出できるということですね」「こういうのをさっと動かして実感できるのありがたい🙏

「上は@mameさんの別のツイートです」「ライブラリのRBS整備、言われてみればたしかに」「Stringのところにうっかり数値を入れるなどはありがちなので、自分が作るコードでこうやってチェックできるだけでも大きいと思います👍」「人間が気づかないところはコンピュータにお任せしたい」「Rubyの型推論はこれからですね」

⚓ Ruby 3は3倍速くなったか(Ruby Weeklyより)


つっつきボイス:「RubyやRailsのベンチマークでおなじみのNoah Gibbsさんの記事です」「Ruby 2.0と比べてRuby 3.0がどのぐらい速くなったかという記事のようですね」「ざっと流し読みした限りでは、細かくはいろいろ述べられていますけど、Ruby 3.0は2.0と比べておおむね3倍速を達成したと思うそうです」「3倍速来た!」「RailsはRuby 2.0から3.0で70%(1.7倍)程度の高速化だそうです」

「いやほんとに、Ruby 2.0の頃から比べると今のRubyは随分速くなりましたよね」「記事によるとRuby 2.0〜2.6のときの高速化が大きかったそうです」「Ruby 1.9の頃のRubyは今思い出しても遅かった」

「Ruby 2.7から3.0ではどのぐらい速くなったのかな?」「今の話からするとRuby 2.7から3.0の高速化は2.0〜2.6のときほど大きくはなさそうですね」「2倍速くなるだけでもプログラミング言語としては凄い達成だと思いますよ」

「Rubyが遅いと言われがちだったのも、1.9のような昔の遅さの印象がアップデートされていないことが多い気がします」「メインで使ってないプログラミング言語やフレームワークの情報や印象がアップデートされないままになるのは自分たちも含めてよくあることなので、そこはしょうがないでしょうね」「ですよね、いろいろ思い当たります」

「PHPも書かなくなって随分経つので最近の様子はわからないです」「もうPHPは書かないんですか?」「業務上読むことはたまにありますけど、モダンなPHPは書けない」「PHPも今は8なんですね」「自分が一番PHPを書いてたのは4系の時代だったかな」「自分も5系が多かったかも」「その頃PHPに初めてクラスというものが導入されたときもありましたね」「それ覚えてます」「当時はコンストラクタってあったかな?」「あの頃自分でデストラクタを実装した覚えもちょっとあります」(以下延々)

参考: PHP: クラスの基礎 - Manual


なお、同記事のコメント欄にもNoah Gibbsさんが「Railsのエンドツーエンドの速度が1.7倍速くなったのは本当に凄いこと: これは覚えておいて欲しい」とコメントを書いていました。

⚓ Ruby 3.0リリースイベント


つっつきボイス:「12/26(土)にRuby 3.0リリースイベントがオンライン開催されるそうなので申し込みました」「お、自分も申し込んどこう」「パネリストが豪華!」「ああ、もう年末なんですね」(以下帰省の話など)

⚓ その他Ruby



つっつきボイス:「RubyのStringの移り変わりを追っている記事です」「そうそう、こういうふうにUTF-8文字の中にinvalidな文字があるときにencode("UTF-8", invalid: :replace)のような方法で除去したいときってありますよね」「2.1より前はinvalidな文字を検出できなかったけど、2.1から検出できるようになったのか」「という文字、ときどき見かけます」「なおこのという文字は:replaceオプションで変えられますよ」

# 同記事より
# in ruby <= 2.0.x
content =  "Is your pl\xFFace available?".force_encoding("UTF-8")
content.encode("UTF-8", invalid: :replace) # => "Is your pl\xFFace available?"

# in ruby 2.1.x
content =  "Is your pl\xFFace available?".force_encoding("UTF-8")
content.encode("UTF-8", invalid: :replace) # => "Is your pl�ace available?"

「RubyのStringは非常によく使うので、こうやって歴史を概観できるのはいいですね👍

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

⚓ AWSのカオスエンジニアリング機能: AWS Fault Injection Simulator


つっつきボイス:「AWS Fault Injection Simulator、まだ詳しく見てなかったけどどういう感じでできるのかな?」

参考: カオスエンジニアリングの原則 - Principles of chaos engineering

「このニュースでBPS CTOのbabaさんが喜んでいました」「stagingで確認できることが増えるのはいいですよね: DBのレプリケーションが詰まったらどうなるかを確認するとか」「ですよね」

「少しググってみた感じでは、AWS Fault Injection Simulatorの具体的な機能はリリースされてないみたい」「あ、まだ発表止まりですか」「今はComing soonで、2021年に登場予定らしい↓」「障害率とかを設定したりテストシナリオのテンプレートを使ったりできるのかなと予想はしていますけど、詳しくはリリースされてからですね」「そうですね」

参考: AWS Fault Injection Simulator – Fully managed chaos engineering service – Amazon Web Services

⚓ AWS Auroraにもある障害シミュレーション機能

「ちなみに、AWSのAuroraにも障害シミュレーション用のクエリが使える機能がありますよ」「あ、そうなんですか!」「やったことなかった」「AuroraのコンソールでALTER SYSTEM CRASHにオプションを指定して実行することで、DBクラッシュやレプリカやディスクの障害のような特定の障害状況をシミュレーションしてテストしたりできます」「こんなのやれるとは知らなかった」「これはAurora専用ですけどね」

参考: 障害挿入クエリを使用した Amazon Aurora のテスト - Amazon Aurora

「DBは壊れたときの影響が非常に大きいので、こういう障害シミュレーション機能を使って障害時にどんな振る舞いになるかを事前に確認できるのは大事」

⚓JavaScript

⚓ JavaScriptが25歳の誕生日を迎える

以下で知りました。

参考: 週刊気になったITニュース(2020/12/05号) - masa寿司の日記


つっつきボイス:「サイトを開くと昔懐かしのNetscapeっぽい画面!」「昔のブラウザってこうでしたよね」「今25歳のエンジニアがJavaScriptと同い年ってことか、はぁぁ」「1995生まれですね😆

参考: Netscapeシリーズ - Wikipedia

「上からスクロールしていくと、LiveScriptという初期の言語名やMicrosoftのJScript、いろいろ見られる」「ECMAScript 1が1997年から始まってたというのは思ったより早いかも」「これは歴史ですね」「出たXMLHttpRequest!」「まだAJAXという言葉がなかった頃ですね」「それまではXMLHttpRequestを生で使ってたの思い出しました」「そのAJAXの登場が2005年だったとは」

参考: XMLHttpRequest - Wikipedia
参考: Ajax - Wikipedia

「他にもAngularjs、Mozilla登場、jQuery、React、Vuejs、Next.jsなどなど」「こうやって歴史をコンパクトに辿れるのはいいですね👍

⚓言語/ツール/OS/CPU

⚓ if-then-elseは発明されなければならなかった


つっつきボイス:「!!Con West 2019というカンファレンスで発表されたものだそうです」「そういえばRubyでもifthenを使う書き方は一応できますね、あまり使われませんけど」「thenは使わないですね」

参考: !!Con West

「これも歴史ものかな?」「英語の接続詞として使われることのないelseがどこから来たかなどを調べたりしてるようです」「プログラミングではifthenelseと当たり前のように書いてますけど、言われてみればたしかに英語の話し言葉や書き言葉ではそういう言い回しはしませんよね」「会話でのelseというと思い付くのはsomeone elseぐらいかも」

「記事ではその辺の歴史をひもといているみたいですね」「何だかすっごく昔の知らない言語がいろいろ出てきてるんですけど」「WHENEVEROR WHENEVERとかOTHERWISEとか、いろんな書き方があったんですね」「プログラミング言語好きな人にはきっとたまらない内容❤」「ALGOL 60という言語のこのif文の構造↓がカオスすぎる😆」「この飛び方はエグい😆

参考: ALGOL - Wikipedia


後編は以上です。

良いお年をと言うにはまだ早いですね。どうぞ皆さまよいクリスマス、よいRuby 3.0、よいRails 6.1を!

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

週刊Railsウォッチ(20201221前編)aws-sdk-rails gemの機能をチェック、RubyWorld Conference 2020のDHHインタビューほか

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

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

Ruby Weekly

StatusCode Weekly

statuscode_weekly_banner

速報: Ruby 3.0.0がリリースされました

$
0
0

Ruby 3.0.0が予定通りリリースされました🎉🎉🎄🤶🏼🎅

第一報は、以下のwww.ruby-lang.orgのRSSフィードで知りました(2020/12/25 15:09)。リリース情報は日本語・英語共かなり詳しく書かれています。

直後に、Rubyのリリースマネージャー@nalshさんのツイートに気づきました。ツイートの方がわずかに早かったんですね。

ruby-buildを見てみると↓、1時間前に3.0.0が乗っていたので、私も早速rbenvで3.0.0をインストールしました。

rbenv/rbenv - GitHub

10分ほどでインストール完了しました。

なお、GitHubリポジトリ上の3.0.0の更新情報(英語)は以下となります。

以下、Ruby 3.0.0関連の記事やスライドなどをご紹介します。お気づきの点がありましたら@hachi8833までどうぞ🙇

追記(2020/12/25):つい先ほど、クックパッド開発者ブログに「プロと読み解く Ruby 3.0 NEWS」が公開されました。

⚓ Ruby 3.0 release event

既に週刊Railsウォッチでお伝えしたように、明日12/26(土)にリリースイベントがオンライン開催されます↓。枠はまだ空きがあります。

⚓ 銀座Rails#26『これからのRubyと今のRubyについて』

@osyoさんの以下のスライドも合わせて読むとRuby 3.0の大枠を追いやすいと思います。

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

⚓ @jnchitoさんによるRuby 3.0の主な新機能と変更点

@jnchitoさんによるRuby 3.0.0の詳しい記事です。1つ目の記事は週刊Railsウォッチでもご紹介しましたが、追加記事が出たので改めてリストアップしました🙇

上の記事は、週刊Railsウォッチでもご紹介したRuby TypeProf Playgroundを使いながら読むとはかどりそうです↓。


私もzenn.dev版の記事にサポート投げました!

なお上記記事は以下のアドベントカレンダーからも参照できます。

⚓ インタビュー記事『試行錯誤と改善を続けたRubyリリースマネジメントの歴史。その歩みを歴代の担当者たちが振り返る』

Findy Engineer Labサイトで先ごろ公開された、Rubyの歴代リリースマネージャー4名へのインタビュー記事です。とても読み応えがあります!

⚓ 銀座Rails#23『Ruby 3.0 JIT on Rail』

@k0kubunさんによるRuby 3.0 JITとRailsに関するスライドです。

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

⚓ 『MemoryView: Ruby 3.0 から導入される数値配列のライブラリ間共有のための仕組み』

@mrknさんが先ごろ公開した「MemoryView」の記事です。

以下でRuby 3.0にマージされたようです。MemoryViewは当初別の名前だったんですね。

⚓ Next-rubyもRuby 3.0の新構文をキャッチアップ

Ruby 3.0.0そのものではありませんが、以下の翻訳記事でご紹介したRuby NextトランスパイラもRuby 3.0のいくつかの新しい構文(findパターン、endレスメソッド、引数forwarding...の拡張)に対応したそうです。

Ruby NextトランスパイラでRubyの新機能を使おう(翻訳)

ruby-next/ruby-next - GitHub

リリースノート: Release 0.11.0 🎄 · ruby-next/ruby-next

⚓ おまけ: 3.0.0がリリースされるまで

12/25の朝7:00の時点ではまだリリースされていないようでした。

上述のRubyリリースマネージャーインタビュー記事で@nalshさんが現在のリリースマネージャーであることを確認できたので、@nalshさんのTwitterと、ruby_3_0ブランチのコミットを見張りました。

rbenvで使うruby-buildのリポジトリも監視しました。

12/25、8:45にv3.0.0p0のコミットがruby_3_0ブランチに入りました(気づいたのは11:30頃でした)。

12/25、12:48にruby_3_0ブランチ画面を更新すると、コミットが5つ増えていました。GitHubのリリースページ↓にv3_0_0が掲載されましたが、これはリリースと思っていいんだろうか…?@nalshさんのツイートが動くまで待ってみよう。

そうこうしているうちにRSSフィードでリリース第一報を知りました。

関連記事

Ruby 2.7: ハッシュからキーワード引数への自動変換が非推奨に(翻訳)

Ruby 3: 引数をforwardする`…`記法が第2パラメータでも使えるようになった(翻訳)

Ruby: 文字列リテラル同士はスペース文字で結合される

$
0
0

Slackの以下の書き込みで、今まで気づかなかったRubyの文字列リテラルの挙動を知りました。

⚓ Rubyの文字列リテラルの挙動

まずは調べてみました。使ったのはRuby 3.0.0-preview2です。

⚓ 結合される場合

# 二重引用符の文字列リテラル、スペース区切り
"foo" "bar"
#=> "foobar"
# 一重引用符の文字列リテラル、スペース区切り
'foo' 'bar'
#=> "foobar"
# 引用符をミックス、スペース区切り
"foo" 'bar'
#=> "foobar"

'foo' "bar"
#=> "foobar"
# 引用符の間にスペースなし
"foo""bar"
#=> "foobar"

'foo''bar'
# => "foobar"
# バックスラッシュと改行での区切り
"foo"\
"bar"
#=> "foobar"

⚓ 結合できない場合

だいたい予想は付きますが、やってみました。

数値リテラルと文字列リテラルのスペース区切りはエラーになります。

1 "foo" 
#=> syntax error, unexpected string literal, expecting end-of-input

シンボルと文字列も同じくエラーになります。

:foo "bar"
#=> syntax error, unexpected string literal, expecting end-of-input

シンボルを文字列に変換してみてもエラーです。以下で変換された文字列は文字列リテラルではないので、文字列リテラルとは扱いが異なると考えればよいと思います。

:foo.to_s :bar.to_s
#=> wrong number of arguments (given 1, expected 0) (ArgumentError)

⚓ おまけ: スペース文字以外の場合

以下は、試しにスペース文字(U+0020)以外のホワイトスペース文字をいくつか区切りに使ってみた結果だけを書きます。

タブ文字(U+00A0
成功
nbsp(U+00A0
成功
en space(U+2002
エラー(syntax error, unexpected local variable or method, expecting end-of-input)
em space(U+2003
エラー(syntax error, unexpected local variable or method, expecting end-of-input)

参考: スペース - Wikipedia

⚓ 仕様を探してみた

以下のruby/specを探してみました。

ruby/spec - GitHub

# https://github.com/ruby/spec/blob/master/language/string_spec.rb#L225
describe "Ruby String literals" do
  def str_concat
    "foo" "bar" "baz"
  end

  def long_string_literals
    "Beautiful is better than ugly." \
    "Explicit is better than implicit."
  end

  it "on a single line with spaces in between are concatenated together" do
    str_concat.should == "foobarbaz"
  end

  it "on multiple lines with newlines and backslash in between are concatenated together" do
    long_string_literals.should == "Beautiful is better than ugly.Explicit is better than implicit."
  end
  # 略

文字列リテラルのスペース文字や改行による結合はspecに含まれていますね。

また、公式ドキュメントの「文字列リテラル」にも記載されています。

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

空白を間に挟んだ文字列リテラルは、コンパイル時に1つの文字列リテラルと見倣されます。
docs.ruby-lang.orgより

⚓ RuboCopには怒られる

そもそも文字列リテラルを+なしで結合しようとすると、素のRuboCopで怒られが発生します。冒頭のコードでやってみました。

# frozen_string_literal: true

a = { 'a' => 1, 'b' => 2, 'c' => 3 }.slice('a', 'b' 'c')
p a

上をtest.rbに保存し、RuboCopをインストールしてチェックします。

$ gem install rubocop
# (略)
Successfully installed parallel-1.20.1
Successfully installed ast-2.4.1
Successfully installed parser-2.7.2.0
Successfully installed rainbow-3.0.0
Successfully installed regexp_parser-2.0.0
Successfully installed rubocop-ast-1.3.0
Successfully installed ruby-progressbar-1.10.1
Successfully installed unicode-display_width-1.7.0
Successfully installed rubocop-1.6.1
$ rubocop test.rb
warning: parser/current is loading parser/ruby30, which recognizes
warning: 3.0.0-dev-compliant syntax, but you are running 3.0.0.
warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
Inspecting 1 file
W

Offenses:

test.rb:3:49: W: Lint/ImplicitStringConcatenation: Combine 'b' and 'c' into a single string literal, rather than using implicit string concatenation. Or, if they were intended to be separate method arguments, separate them with a comma.
a = { 'a' => 1, 'b' => 2, 'c' => 3 }.slice('a', 'b' 'c')
                                                ^^^^^^^

というわけで、この+なしの文字列リテラル結合を業務用のコードで積極的に使うことはなさそうです。

⚓ まとめ

  • Rubyの2つ以上の文字列リテラルは、スペース文字を挟んで配置されると1つの文字列リテラルとして扱われる
  • 文字列リテラルの囲みが一重引用符と二重引用符かどうかでこの挙動は変わらない
  • スペース文字以外に改行やタブ文字で区切った場合や、区切り文字を挟まない場合も同様
  • この挙動は文字列リテラル同士が隣り合っている場合に発生する

関連記事

速報: Ruby 3.0.0がリリースされました

Rubyの内部文字コードはUTF-8ではない…だと…?!

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

$
0
0

更新情報
2017/12/11: 初版公開
2020/12/23: 細部を更新

ActiveRecordに任意の属性を定義したり既存の属性を上書きしたりできるRails 5以降の標準機能です。

概要

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

参考: Rails 5のActive Record attributes APIについて y-yagiさんの良記事です。

⚓ Rails5: ActiveRecord標準のattributes API(翻訳)

⚓ メソッド

⚓ 定数

  • NO_DEFAULT_PROVIDED = Object.new

⚓ attribute(name, cast_type = Type::Value.new, **options)

(publicインスタンスメソッド)

型を持つ属性をこのモデルに定義します。必要な場合、既存の属性の型をオーバーライドします。これにより、モデルへの代入時に値がSQLと相互に変換される方法を制御できるようになります。また、ActiveRecord::Base.whereに渡される値の振る舞いも変更されます。これを使って、実装の詳細やモンキーパッチに依存せずに、ActiveRecordの多くに渡ってドメインオブジェクトを使えるようになります。

  • name” 属性メソッドの定義対象となるメソッド名、およびこれを適用するカラム。
  • cast_type: この属性で使われる:string:integerなどの型オブジェクト。利用例について詳しくは以下のカスタム型オブジェクトの情報をご覧ください。

⚓ オプション

以下のオプションを渡せます。

  • default: 値が渡されなかった場合のデフォルト値。このオプションを渡さなかった場合、前回のデフォルト値があればそれが使われる。前回のデフォルト値がない場合はnilになる。
  • array:(PostgreSQLのみ)array型にならなければならないことを指定する(以下の例を参照)。

  • range:(PostgreSQLのみ)range型にならなければならないことを指定する(以下の例を参照)。

⚓

ActiveRecordで検出される型はオーバーライド可能です。

# db/schema.rb
create_table :store_listings, force: true do |t|
  t.decimal :price_in_cents
end
# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
end
store_listing = StoreListing.new(price_in_cents: '10.1')

# 変更前
store_listing.price_in_cents # => BigDecimal.new(10.1)

class StoreListing < ActiveRecord::Base
  attribute :price_in_cents, :integer
end

# 変更後
store_listing.price_in_cents # => 10

デフォルト値を指定することもできます。

# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :my_string, default: "original default"
end

StoreListing.new.my_string # => "original default"
# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :my_string, :string, default: "new default"
end

StoreListing.new.my_string # => "new default"

class Product < ActiveRecord::Base
  attribute :my_default_proc, :datetime, default: -> { Time.now }
end

Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
sleep 1
Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600

属性の背後にデータベースのカラムがなくても構いません。

# app/models/my_model.rb
class MyModel < ActiveRecord::Base
  attribute :my_string, :string
  attribute :my_int_array, :integer, array: true
  attribute :my_float_range, :float, range: true
end
model = MyModel.new(
  my_string: "string",
  my_int_array: ["1", "2", "3"],
  my_float_range: "[1,3.5]",
)
model.attributes
# =>
  {
    my_string: "string",
    my_int_array: [1, 2, 3],
    my_float_range: 1.0..3.5
  }

⚓ カスタム型の作成

値型で定義されるメソッドと対応していれば、独自の型を定義することもできます。この型オブジェクトでは、deserializeメソッドまたはcastメソッドが呼び出され、データベースやコントローラから受け取った生の入力を取ります。前提とされるAPIについてはActiveModel::Type::Valueをご覧ください。型オブジェクトは既存の型かActiveRecord::Type::Valueのいずれかを継承することが推奨されます。

class MoneyType < ActiveRecord::Type::Integer
  def cast(value)
    if !value.kind_of?(Numeric) && value.include?('$')
      price_in_dollars = value.gsub(/\$/, '').to_f
      super(price_in_dollars * 100)
    else
      super
    end
  end
end
# config/initializers/types.rb
ActiveRecord::Type.register(:money, MoneyType)
# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :price_in_cents, :money
end
store_listing = StoreListing.new(price_in_cents: '$10.00')
store_listing.price_in_cents # => 1000

カスタム型の作成について詳しくは、ActiveModel::Type::Valueのドキュメントをご覧ください。型をシンボルで参照できるように登録する方法については、ActiveRecord::Type.registerをご覧ください。シンボルの代わりに型オブジェクトを直接渡すこともできます。

⚓ クエリ

ActiveRecord::Base.whereが呼び出されると、そのモデルクラスで定義された型が使われ、型オブジェクトでserializeを呼んで値がSQLに変換されます。次の例をご覧ください。

class Money < Struct.new(:amount, :currency)
end
class MoneyType < Type::Value
  def initialize(currency_converter:)
    @currency_converter = currency_converter
  end

  # deserializeまたはcastの結果が値になる
  # ここではMoneyのインスタンスになることが前提
  def serialize(value)
    value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
    value_in_bitcoins.amount
  end
end
# config/initializers/types.rb
ActiveRecord::Type.register(:money, MoneyType)
# app/models/product.rb
class Product < ActiveRecord::Base
  currency_converter = ConversionRatesFromTheInternet.new
  attribute :price_in_bitcoins, :money, currency_converter: currency_converter
end
Product.where(price_in_bitcoins: Money.new(5, "USD"))
# => SELECT * FROM products WHERE price_in_bitcoins = 0.02230

Product.where(price_in_bitcoins: Money.new(5, "GBP"))
# => SELECT * FROM products WHERE price_in_bitcoins = 0.03412

⚓ dirtyトラッキング

属性の型には、dirtyトラッキングの実行方法を変更する機会が与えられます。ActiveModel::Dirtychanged?changed_in_place?が呼び出されます。これらのメソッドについて詳しくはActiveModel::Type::Valueをご覧ください。

⚓ define_attribute( name, cast_type, default: NO_DEFAULT_PROVIDED, user_provided_default: true )

(publicインスタンスメソッド)

これはattributeの背後にある低レベルAPIです。型オブジェクトのみを受け取り、スキーマの読み込みを待たずに即座に動作します。自動スキーマ検出と#attributeはどちらもこのメソッドを背後で呼び出します。このメソッドが提供されていることでプラグイン作者によって使われる可能性もありますが、おそらくアプリのコードで#attributeを使うべきです。

  • name: 定義される属性の名前。Stringで定義します。
  • cast_type: この属性で使う型オブジェクト。

  • default: 値が渡されなかった場合のデフォルト値。このオプションを渡さなかった場合、前回のデフォルト値があればそれが使われる。前回のデフォルト値がない場合はnilになる。procを渡すことも可能であり、新しい値が必要になるたびにprocが1度ずつ呼び出される。

  • user_provided_default: デフォルト値がcastdeserializeでキャストされるべきかどうかを指定。

  • GitHubソース

関連記事

Rails: Form ObjectとVirtusを使って属性をサニタイズする(翻訳)

Rails: dry-rbでForm Objectを作る(翻訳)

週刊Railsウォッチ(20210112前編)Active Recordの範囲指定バリデーション改善、soleとfind_sole_byメソッド、AlgoliaとRailsほか

$
0
0

こんにちは、hachi8833です。今年も週刊Railsウォッチをよろしくお願いします🎍🙇

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇
  • お知らせ: TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

12/25以降のコミットリストのうち、Changelogに記載されているものから見繕いました。

⚓ button_toが常にHTMLの<button>をレンダリングするようになった

ActionView::Helpers::UrlHelper#button_toの第1引数やブロックでコンテンツを渡さない場合も「常に」<button>要素をレンダリングするように変更された。

        <%= button_to "Delete", post_path(@post), method: :delete %>
        <%# => <form method="/posts/1"><input type="_method" value="delete"><button type="submit">Delete</button></form>

        <%= button_to post_path(@post), method: :delete do %>
          Delete
        <% end %>
        <%# => <form method="/posts/1"><input type="_method" value="delete"><button type="submit">Delete</button></form>

Sean Doyle, Dusan Orlovic
changelogより大意


つっつきボイス:「今まで<input type="submit" />で作っていたボタンが<button>タグになるのね」「<input type="submit" />で作るボタン、懐かしい👴」「今まで<button>じゃない部分が残ってたとは知らなかった」「button_toメソッド、使ったことなかったかも」「button_toなんてメソッドがあったんですね、今度使ってみよう」

「もしかするとbutton_toはあまり使われてなかったのかもしれませんが、もしふんだんに使っている人がJavaScriptとボタンを連携させていたりしたらHTMLタグが変わるのでbreaking changeになるかもしれませんね」「あ、たしかに」「button_toをオーバーライドすればいいと思います」「どんなにマイナーな機能でも使っている人がいる可能性はあると思った方がよいでしょうね」

<button>タグがある今、ボタンを作るのに<input type="submit" />を使うこともあまりやらなくなりましたよね」「Webの歴史を感じてしまいました」「若い人だと<input type="submit" />でボタンを作れること自体知らないかも」「そういう時代になったんですね…」

参考: <BUTTON> -HTMLタグリファレンス

⚓ バリデーションのnumericalityにrange..で値を渡せるようになった

数値バリデーション(パーセント値など)の範囲指定方法を簡潔にするプルリク。

validates :percentage, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }

# ↓

validates :percentage, numericality: { in: 0..100 }

length: { in: x..y }バリデーションがインライン化される。
同PRより大意


つっつきボイス:「お〜なるほど、in:でrangeリテラル..を使って書けるようになったのか!」「これいいじゃないですか!」「これはよい👍

「今までなかったのがちょっと不思議なぐらいですね」「自分も今までgreater_than_or_equal_to: 0, less_than_or_equal_to: 100みたいに書いてたけど、言われてみれば..で書ける方が楽ですよね」「greater_than_or_equal_toって長い…」「長い長い」

「かといってgteqとかlteqみたいに詰めるのもちょっと考えてしまう」「Perlはeqとか使う文化ですね」「Bashも-gtとか-eqとか使います」「-geとか-leもあった」「やっぱり詰めると読みづらいですよね…」「やむを得ずPerlのソースを読むことになったときに最初に戸惑ったのがその辺の表記でした😢」「Perlのように歴史の長い言語だと、前方互換のために後から導入する記号のやりくりで苦心しがちですよね」

参考: Perl - Wikipedia

⚓ ActiveModel::Nameの初期化でlocaleを渡せるようになった

概要
とある理由のため、モデル名の複数形化を言語に合わせた形で行いたいと思った。調べてみるとpluralizeメソッドはlocaleを引数に取れるが、それをActiveModel::Nameの初期化に渡す方法がなかった。
同PRより大意

# activemodel/test/cases/naming_test.rb#161
class NamingWithSuppliedLocaleTest < ActiveModel::TestCase
  def setup
    ActiveSupport::Inflector.inflections(:cs) do |inflect|
      inflect.plural(/(e)l$/i, '\1lé')
    end

    @model_name = ActiveModel::Name.new(Blog::Post, nil, "Uzivatel", :cs)
  end

  def test_singular
    assert_equal "uzivatel", @model_name.singular
  end

  def test_plural
    assert_equal "uzivatelé", @model_name.plural
  end
end

つっつきボイス:「ActiveModel::Name.newにロケールを渡せるようになった」「単数形や複数形は言語によっていろいろ違っていますね」

「↓以下のように元々pluralizeにはlocaleを渡せるようになっていて、ActiveModel::Name.newがそれに対応してなかったのをできるようにしたようですね: これができるようになったときのことをちょっと覚えてます」「その下で単数形用のsingularlizeにもロケールを渡すようになってる」

# activemodel/lib/active_model/naming.rb#L170
      @unnamespaced = @name.delete_prefix("#{namespace.name}::") if namespace
      @klass        = klass
      @singular     = _singularize(@name)
-     @plural       = ActiveSupport::Inflector.pluralize(@singular)
+     @plural       = ActiveSupport::Inflector.pluralize(@singular, locale)
      @element      = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
      @human        = ActiveSupport::Inflector.humanize(@element)
      @collection   = ActiveSupport::Inflector.tableize(@name)
      @param_key    = (namespace ? _singularize(@unnamespaced) : @singular)
      @i18n_key     = @name.underscore.to_sym

-     @route_key          = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
-     @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
+      @route_key          = (namespace ? ActiveSupport::Inflector.pluralize(@param_key, locale) : @plural.dup)
+     @singular_route_key = ActiveSupport::Inflector.singularize(@route_key, locale)
      @route_key << "_index" if @plural == @singular

pluralizeしたものやsingularlizeしたものをさらにconstantizeすることもあると思いますけど、そうやってできたクラス名にUzivateléみたいな名前が出てきたらちょっとびっくりしそう…」

⚓ 新機能: solefind_sole_by


つっつきボイス:「soleと言っても靴の裏のことじゃなくて、solely(単独で)のsoleだそうです」「どことなくラテン語風味を感じる言葉」「たしかにsolelyって単語ありますね」「onlyやaloneあたりを固い言葉で言い換えるときに使う印象あります」「たしかに技術ブログではたまに見ますね」「話し言葉ではあまり聞かない印象もあります」

「で、このsoleはレコードが1個しかない場合だけそれを返すメソッドのようです」「該当レコードが複数あったらどうなるんだろう?」「複数の場合や1件もない場合はエラーにするそうです」「あ、ActiveRecord::SoleRecordExceededっていうのがそれですね」

レコードが1個きっかり存在することを調べたりアサーションしたりするFinderMethods#soleおよび#find_sole_byを追加。
用途としては、レコードを1行だけ取りたいが、その条件にマッチするレコートが他に複数存在しないこともアサーションしたい場合(特にデータベースの制約が不十分だったりちゃんと効いてない場合)。

同Changelogより大意

「プルリクメッセージを見ると、Django(PythonベースのWebフレームワーク)にはそういうメソッドがあるということみたい↓」

参考: Proposal + patch: FinderMethods#only! for asserting there’s only one result row - rubyonrails-core - Ruby on Rails Discussions
参考: Django - Wikipedia

「Railsのfind_byで結果が複数ある場合はどうなるんだったかな…普段そういう使い方してないからな〜」「私もしません😆」「find_byは1件目だけを返すとAPIドキュメントにありますね↓」

Finds the first record matching the specified conditions.
api.rubyonrails.orgより

「条件に合致するレコードが2件以上あったり1件もなかったりしてはいけない場合にこれらのメソッドが使えるということのようですね」「find_byで実はレコードが複数ある場合を排除したいときとか」「アサーションを書くのに便利そう」「こういうコードを書くのはテストコードが多いでしょうね」「レコードが1件しかあってはならないことをこうやってメソッドで明示するのは好きです」

find_sole_byか…このメソッド名でいいのかな?」「プルリクメッセージを見ると、find_sole_byというメソッド名について議論されてますね」「あれ、Changelogにはfind_by_soleって書かれてる↓」「これはドキュメントの変更漏れかな?」「ホントだ」「来週のウォッチ公開日にもし修正されてなかったら、Railsにプルリクするチャンスですね」

# 同Changelogより
Product.where(["price = %?", price]).sole
# => ActiveRecord::RecordNotFound      (if no Product with given price)
# => #<Product ...>                    (if one Product with given price)
# => ActiveRecord::SoleRecordExceeded  (if more than one Product with given price)

user.api_keys.find_by_sole(key: key)  # 編集部注: find_by_soleは修正前の誤りです
# as above

「メソッド名の議論を見るとこんなコメントがあった↓」「find_sole_byの方がsoleで探し方を示していてよさそうだということですね」「DHHがfind_sole_bysole_firstがいいと言ってる」「こういうやりとりの間にfind_by_soleがドキュメントに残ってしまったのかもしれませんね」

May I propose a slight tweak: find_sole_by. “Find solely by” seems to describe the way to find, rather than what to find. For example, “I tracked him down solely by date of birth” means “I used only his date of birth to track him down”, rather than “He was the only person with that date of birth.”
#40768のコメント(by jonathanhefner)より

なお、その後調べてみると、現時点でのRailsのmasterブランチ↓では既に修正済みでした(2717b08)。

Product.where(["price = %?", price]).sole
# => ActiveRecord::RecordNotFound      (if no Product with given price)
# => #<Product ...>                    (if one Product with given price)
# => ActiveRecord::SoleRecordExceeded  (if more than one Product with given price)

user.api_keys.find_sole_by(key: key)
# as above

find_sole_byなら昔からあるfind_by_XXXな書き方と取り違えられずに済むでしょうから、それもあってこの名前にしたのかもしれませんね」

その後調べると、古い動的ファインダーメソッドの一部はRails 4.0で以下のgemに切り出されたようです↓。

rails/activerecord-deprecated_finders - GitHub

また、RuboCopのRailsスタイルガイド↓ではfind_by_XXXのような書き方は警告されると社内で教わりました🙇

参考: rubocop-hq/rails-style-guide: A community-driven Ruby on Rails style guide

⚓ ActiveRecord::AttributeMethods::Queryのgetterメソッドをオーバーライドできるようにした

# 同PRより
# 修正前
  class User

    def admin
      false # getterをオーバーライドして常にfalseを返すようにする
    end

  end

  user = User.first
  user.update(admin: true)

  user.admin # false (getterのオーバーライドによる期待どおりの結果)
  user.admin? # true (DBカラムの値が返った: 期待どおりでない)

修正後はuser.admin?が期待どおりfalseを返すようになる。
同PRより大意


つっつきボイス:「ああ、やりたいことはわかりました: こういうことはあまりやって欲しくない気持ちがありますが」「どういう改修でしょうか?」「Userテーブルにadminカラムがあるときに、Active Recordが生成するadminメソッドを上のようにオーバーライドして、たとえば常にfalseを返すようにするというのは、たまに見かける書き方ではあります」「わかります😆」「で、adminメソッドはオーバーライドできるけど、admin?メソッドがオーバーライドされてなくてデータベースの値を読み込んで返していた、それをオーバーライドされるように改修したということですね」「なるほど!」

オーバーライド(override)
Ruby では上位クラスや include したモジュールで定義されているメソッドを再定義することを「オーバーライドする」という。オーバーライドしたメソッドからは super によって元のメソッドを呼び出すことができる。
Ruby用語集 (Ruby 3.0.0 リファレンスマニュアル)より

「これが欲しい気持ちはわかるんですけど、adminをオーバーライドしたときにadmin?もオーバーライドする機能って果たして必要なんだろうかって思う気持ちもありますね」「自分もこれは要らない気がします…」

「むしろwarningを出して欲しいですよね」「たしかに!」「『adminがオーバーライドされたけど、admin?はオーバーライドされてないよ』という具合に」「現場ではその方が嬉しいかも」「知らないうちにadmin?もオーバーライドされることで何か起きるんじゃないかとちょっと心配」「たぶん自分は使わないかな」

「RuboCopが注意してくれるといいかも」「言われてみれば、attributeを直接オーバーライドするメソッドの警告はRuboCopにありそうですね」

後でrubocop-railsを探してみましたが、Active Recordの組み込みメソッドを直接オーバーライドしたときの警告は見つかったものの、それ以外の警告は見つけられませんでした。

参考: Rails/ActiveRecordOverride Rails Cops - A RuboCop extension focused on enforcing Rails best practices and coding conventions.

⚓Rails

⚓ rbs_railsでRailsアプリにSteepを導入


つっつきボイス:「昨年末のRuby 3.0リリースイベントの↓中でこの記事の話題が出ていたことで知りました」「Pockeさんは最近RBSとRails関連の記事をいろいろ書いてますね」

参考: Ruby 3.0 release event - connpass — 終了

pocke/rbs_rails - GitHub

「Pockeさんのこの記事ぐらい新しければ大丈夫ですけど、RubyのRBSや型推論周りはここ数か月でだいぶ動いたので、ちょっと前の記事だともう現状に追いついてないでしょうね」「あ、たしかに」「少なくともRuby 3.0リリース後の記事を見つけるようにしたいですね: もちろんRBSは今後も変わるかもしれませんが、正式リリース後はそうそう破壊的な変更にならないだろうと予想しています」「steepやsorbetは今後も変わるのかな?」

soutaro/steep - GitHub

sorbet/sorbet - GitHub

⚓ activerecord-importの:on_duplicate_key_ignoreオプション


つっつきボイス:「@kamipoさんの記事1本目です」「activerecord-import gem自体はお馴染みのものですけど、@kamipoさんの記事にはおぉ〜っと思いましたね」

zdennis/activerecord-import - GitHub

「記事を読んでて、MySQLでそんなことができるとは、そう言えばどこかで聞いたような、と思ったのがINSERT IGNOREでした」「おぉ?」「MySQLにはDB制約を無効にする機能があって、セッションレベルで無効にすることもできれば、この記事のようにINSERT IGNOREを使ってそのINSERT文だけで制約をオフにすることもできます」「へ〜!」

参考: MySQL :: MySQL 8.0 Reference Manual :: 13.2.6 INSERT Statement

「これはデータベースへのインポートでよく使われる機能で、外部キーが相互参照しているようなデータをmysqldumpすると、インポートするときに制約を無効にする必要が生じることがあるんですよ」「なるほど!」「制約を無効にしないと整合性が『タマゴが先かニワトリが先か』のような状態になってしまうので、バッチでデータをインポートするときなどにこの機能が必要になります」「今思えばINSERT IGNOREどこかで使ったことあったかも」

「そんなときはRails 6.0からinsert_all↓というそれ用のメソッドがあるからそっちを使ってねというお話で締めくくられています」「記事の『MySQLチョットデキル』、こんなこと自分も言ってみたいです〜」「この言い回しカッコいいですよね」

⚓ @kamipoさんのツイートより


つっつきボイス:「こちらも@kamipoさんのツイートです」「そうそう、Rails 6.1からこのあたりのクエリがちょっとキレイになったんですよね: それまではorを重ねていくとwhere文のネストがものすごく深くなって#39032のようにStack level too deepになったりしてた」「こういうのを修正する@kamipoさんはやっぱりすごい人」

⚓ その他Rails


つっつきボイス:「アルゴリア!」「これ何でしたっけ?」「いい感じのインクリメンタル検索を実現するAPIサービスですね」「あ、思い出しました」「TechRachoでもAlgoliaの作者インタビューを翻訳したことあります↓」「インクリメンタル検索をやりたいときには便利ですよね」

インタビュー: 超高速リアルタイム検索APIサービス「Algolia」の作者が語る高速化の秘訣(翻訳)

「AlgoliaはよくAPIドキュメントサイトなどで使われてますね」「そうそう、単純なインクリメンタル検索じゃなくて、部分一致の複数検索とか重要度の高いものから上に出すとか」「記事をざっと眺めた限りでは、Railsで特殊なことをあまりせずにAlgoliaを使えるような感じですね」

以下はAlgolia自身のインクリメンタルなAPIドキュメントサイトですが、他にもAlgoliaを使っているドキュメントサイトを見かけます。

参考: REST API | API Reference | Algolia Documentation

「ところで記事の中に出ているAutocomplete.jsってjQueryじゃなかったっけ?」「あ、そうかも」「記事のサンプルコードの書き方↓がどことなくjQueryの匂いを感じたけどやっぱりjQueryみたい」


同記事より

「特に上の1行目の('#search-input', { hint: true }とか、下から3行目の.onあたりにjQueryフレーバーを感じました↑」「あ〜わかる気がします」

algolia/autocomplete.js - GitHub

「見つけたこのリポジトリ↑がalgolia/autocomplete.jsになっているから、この記事のはAlgoliaが出しているAutocomplete.jsの方か」「AlgoliaのAutocomplete.jsもやっぱりjQueryでした」

参考: jQuery - Wikipedia


前編は以上です。

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

週刊Railsウォッチ(20201222後編)TypeProfプレイグラウンド、Ruby 3リリースイベント、Ruby 3は3倍速くなったかほか

週刊Railsウォッチ(20201221前編)aws-sdk-rails gemの機能をチェック、RubyWorld Conference 2020のDHHインタビューほか

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

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

Rails公式ニュース

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

$
0
0

こんにちは、hachi8833です。

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

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

⚓Ruby

⚓ @_ko1さんのRuby 3.0 Ractor解説


つっつきボイス:「こちらの記事も、昨年のRuby 3.0リリースイベント↓の中で@_ko1さんが『つい先ほど公開しました』と話していたことで知りました」「お、読んだ気がすると思ったら年越し前に同じくこのイベントで知ったので休みの間に読んじゃいました」「そうか、もう公開から2週間経ったんですね」「Ractorはいいと思います!」「とりあえずRactorの概要を知っておくことが大事ですね」

参考: Ruby 3.0 release event - connpass — 終了

⚓ Ruby 3のFiber Schedulerを作った


つっつきボイス:「中国語が見える」「中国の方が書いたらしいですが、この記事は英語ですね」

後で「代码混音师」をGoogle翻訳にかけると「コードミキサー」と出たので、ブログドメイン名のcodemixer.comそのままでした。

「Ruby 3でFiber Schedulerをカスタマイズできるようになったので(ウォッチ20200609)、やってみたようです」「お〜、眺めた限りですが真面目にスケジューラを作ってるように見える」「スゴそう」

「記事の中でio_uringepollkqueueのようなLinuxの新しいAPIをチェックしている」「そういえばちょっと前にio_uringなどの話題になりましたね(ウォッチ20200804)」

参考: ソケットAPIが遅すぎる?新たなio_uringを試す!. 新しいAPIが作られるたびに、私たちは、古いAPIを置き換えるだけで高速化という… | by FUJITA Tomonori | nttlabs | Dec, 2020 | Medium
参考: Man page of EPOLL
参考: kqueue|kamezawa.hiroyuki|note


後で気が付きましたが、同記事末尾で著者がFiberをRactor-safeにするプルリクを投げたと書かれていて、見てみると既にマージされていました↓。

Fiber#schedulerは今のところ英語版のRubyドキュメントにのみ載っているようです↓。るりまサーチではまだ見つかりません。

Ruby 3.0.0 リリースノートでFiber Schedulerについて以下の動画を紹介していたのでここにも貼ります。

⚓ Rubyスレッドリークの隠れたコスト


つっつきボイス:「コードはよくあるApache Kafkaのconsumerのようですね」

参考: 開発者のための Apache Kafka サービス | Heroku
参考: Apache KafkaのProducer/Broker/Consumerのしくみと設定一覧 - Qiita

元記事中で使っているKarafkaはRuby向けのKafkaフレームワークです↓。

karafka/karafka - GitHub

# 同記事より: 問題が起きたコードの簡略版
class EventsConsumer < Karafka::BaseConsumer
  def initialize(...)
    super
    @processor = Processor.new
  end

  def consume
    @processor.call(params_batch.payloads)
  end
end

「上が時間とともにメモリリークしてパフォーマンスが落ちていったので、とりあえず下のように解決してしのいだそうです」「どうやら、上のProcessor.newがconsumerの数だけProcessorを増やしてしまっていたのを、Processor.instanceのようにシングルトンに変えて同じキューが使われるようにしたということのようですね」「マルチスレッド系のバグはコードを見るときに疑ってかからないと発見が難しいですね」

# 同記事より: 解決版
class Processor
  include Singleton

  # 略
end

class EventsConsumer < Karafka::BaseConsumer
  def initialize(...)
    super
    @processor = Processor.instance
  end

  # 略
end

「記事の最後は、何も対策しなかった場合にどうパフォーマンスが落ちるかをちょっと比較してみたそうです」「グラフは上に行くほど低い…のね」「お〜、JRuby以外はstaleしたスレッドが増えるとパフォーマンスが落ちてる」「CRubyはRuby 2.7.2より3.0.0-preview2の方が落ち方が少し増えてるのか」


同記事より

念のため元記事末尾の文を引用します。

Ruby 3.0(訳注: preview版です)の方がRuby 2.7.2より遅かったことに興味を惹かれたので、その理由について近々調べてみようかと思います。
注: 自分はこれがJITのベストなユースケースとは信じていませんので、どうか上のグラフを見てパフォーマンスについて騒ぎ立てないでいただきたく思います。
同記事より大意

⚓ その他Ruby


つっつきボイス:「へ〜、Ruby 2.7以降ではIRB.confでUSE_READLINEを指定すると、irbでライブラリのreadlineが使われてしまって、irb独自のオートハイライトやオートインデントが効かなくなるのか」

参考: それ行けLinux~readline~

2.7以降のirbで使われているのが以下のrelineというライブラリです(ウォッチ20191001)。

ruby/reline - GitHub

「ややこしいですけど、このオプションをtrueにするとrelineの機能がirbで使えなくなるということでしょうか?」「はい、そういうことですね」「記事にもありますけど、以前はreadline付きでRubyをビルドするのに苦労することもあったりしたようですね」「今ならそういう苦労なしにRuby 3.0をインストールできるのに昔は大変だったんだな…」

後でRuby 3.0.0でやってみました↓。

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

⚓ AWS CloudShell

参考: 待望の新サービス AWS CloudShell がリリースされました! #reinvent | Developers.IO


つっつきボイス:「BPSの社内Slackに貼っていただいた記事です」「AWS CloudShellは、AWSが提供するコンテナのLinuxシェル環境にログインできるもので、ホームディレクトリに容量制限はあるものの、CloudShellを使うとAWS CLIなどが使えるシェルにアクセスできます: 言い換えると、自分の手元ではEC2インスタンスを1個も立ち上げずに、AWS CLIをコマンドラインで使うためだけのコンソールを使えるようにするサービスですね」「なるほど、ちょっと想像が付きました」「それをブラウザでできるんですね」「はい、ブラウザ以外でもできるかどうかはまだよくわかりませんが」

参考: AWS コマンドラインインターフェイス(CLI: AWSサービスを管理する統合ツール)| AWS

「あとお値段の方も、リージョン1つにつき最大10コンカレントシェルまで無料とあるので、普通の使い方をしていれば基本的に無料」「ありがたい!」「その代わりホームディレクトリは1GBまでという容量制限があるので、ここにソースコードを置いてあれこれ変えるようなユースケースはあまり想定されていないと思います」

「AWS CloudShellでTerraformを実行するぐらいはできるかな?: デフォルトでは入ってなさそうですが、npm(Nodejsのパッケージマネージャ)やpip(Pythonのパッケージ管理ソフトウェア)がインストール済みとあるからできるかも」

参考: Terraformとは?基本知識とTerraformのメリット4つを紹介 | テックマガジン from FEnetインフラ

hashicorp/terraform - GitHub

参考: pip - Wikipedia
参考: npm (パッケージ管理ツール) - Wikipedia

後で探すと、既にやってみた人の記事を見つけました↓。

参考: AWS CloudShell で Terraform を実行してみた! - ForgeVision Engineer Blog

「ただ、AWS CloudShellはCloudWatch Logsなどにログが残らない残らないという話も見たんですよ」「あ〜、そうなんですか」「少なくとも今はなさそうなので、ログを残さないといけないというポリシーがある組織やプロジェクトだと合わない可能性がありますね」

「でもAWS CLIが使えるシェルが手軽に欲しいということは割とありますし、値段も安いので、トラブルシューティングのときとかにCloudShellを使うのは悪くないと自分は思いました」「そうですね」「さしあたってはAWS CloudShellというものがある、という存在を知っておけば後々いろんな使いみちがあるでしょうね👍

⚓JavaScript

⚓ Vercel: Next.jsの開発元が立ち上げたホスティングサービス

Next.jsの他にVueでお馴染みのNuxt.jsやAngularなども公式にサポートしているそうです↓。

参考: Deploying a Static Nuxt.js App with Vercel - Vercel Guides


つっつきボイス:「お、今日のWebチームミーティングのチーム内発表に登場したVercelですね」「はい、ウォッチで扱ったことがありそうでありませんでした」

「発表によればVercelはNext.jsの開発元が作った、フロントエンド向けのHeroku的なホスティングサービスということでしたが、後発だけあって使い勝手などがかなりよくできている印象でしたね」「そうですね、stagingとproductionが最初から使えるとか、アカウントもGitHubでもGitLabでもBitBucketでも使えるとか」

元々ZEITという社名だったのがVercelに社名変更したそうです↓。

参考: Vercel (日本語訳)

「VercelにはServerless functionsという機能もあって、pages/apiディレクトリにバックエンドの処理も置けるそうですが、サーバーサイド側がどこまで使えるかが知りたいところですね」「商用で使う前に、自社内用のツールをVercelにホスティングしていろいろ試してみてもいいなと思いました」

「お値段も、カスタムドメイン、Continuous Deplyment(CD)、CDN(Contents Delivery Network)、API制限なし、Serverless Functionsまで使えて無料なんですよね」「お〜!」「メンバーが10人を超えたら有料なのか」「自分の趣味プログラムをホスティングするなら事実上無料ですね」

「お、もうひとつありがたいことが書かれていますね: 登録にクレジットカードが不要だそうです」「へ〜!」「自分は大学でWeb開発を教えているんですけど、学生に使ってもらうときにとても大事なポイントなんですよ: クレジットカードを持ってる大人は関係ありませんけど、学生がクレジットカードを持っているとは限らないので使ってもらいにくい」「なるほど!」

「GoogleのGCPやAWSだと、1円も課金しないで使うとしてもクレジットカード登録が必要なんですよ」「クレカ登録が不要なら小学生でも使えますね」

注: Vercelのサービス規約上未成年でも利用できるかについては確認していません。ご利用の際はクレカの有無だけでなく各サービスの利用規約についても各自ご確認をお願いします。

「あとはVercelが今後もサービスを継続できるかどうかでしょうね」「発表のときもVercelが資金調達したことが話題になってましたね(本項冒頭のツイート)」「つい最近なんですね」

「Next.js自体も@mizchiさんなどが推していてReactのフルスタックフレームワークとして現時点で出来がいい方らしいので、VercelはそのNext.jsの開発元が公式に提供しているホスティングサービスということで、筋はよさそうに思えますね」

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

⚓ @kazuhoさんが解説するHTTP/3


つっつきボイス:「お、HTTP/3の話」「H2Oの作者であるFastlyの@kazuhoさんによるHTTP/3の講演をPublicKeyががっつり解説記事にしていました」

参考: H2O (Webサーバ) - Wikipedia

「HTTP/2の登場が2015年とあるから、もう5年も経ったのか」「2016年にHTTP/2対応したのを思い出しました」「優先順位をExtensible prioritiesで設定できるようになるのか: 今までネットワークレイヤでやってた制御がここに上がってきた感じ」

「HTTP/2の時点で、1個のTCPコネクションの中でマルチストリームができるようになりましたけど、HTTP/3ではその優先順位をこれで制御するんだろうな」「通信を多重化するとたいていQoS(Quality of Service)をやりたくなりますよね」

参考: Quality of Service - Wikipedia

「HTTP/3のEarly Hintsは、そういえばRailsもHTTP/2のときに対応してましたね↓」「Changelogで見た覚えあります」(以下レイテンシやパケットロスの話題など延々)

【速報】Rails 5.2.0正式リリース!Active Storage、Redis Cache Store、HTTP/2 Early Hintほか

「HTTP/3も実用の段階が近づきつつあるんですね」「この辺の技術を有効に使い尽くそうとすると、たぶんRailsのようなモデルよりも最近のフロントエンドのようなモデルにしないとなかなかやりづらいだろうなと感じるところはあります」「そうなんですね」「ゲーム業界のように速度を果てしなく追求する分野だと、RailsのようにサーバーでHTMLをレンダリングして返すモデルは上限に突き当たるのが早そうな予感がします」

「ソシャゲでRails使って応答速度を追求するのはしなさそうですね」「もちろん、ゲーム業界でも極端な応答速度を求められない機能ならRailsや他のフレームワークで作るというのはあるでしょうが、そうするとサーバーサイドが複数言語のハイブリッド構成になって複雑化してしまうんですよ: それなら最初から全部JavaScriptやGolangを使って書くことで言語はひとつにしたい、というのが昨今のRailsやめたい勢の意見のひとつかなと感じています」

「あと、AWSのALBがHTTP/3にどう対応するかも気になるところですね」

参考: Application Load Balancer とは - Elastic Load Balancing

⚓ BCP56

「以下はHTTP/3と直接関係ありませんが、@kazuhoさんのツイートを拾いました↓」「このBCP56というドキュメントはなかなか便利ですね👍

「いわゆるRFCの仕様はHTTPサーバーを実装する側向けのドキュメントですが、BCP56はHTTPを利用してコンテンツやアプリケーションを作る側にとってのベストプラクティス的なことが盛り込まれていて、Webアプリの本来あるべき姿が示されているのはありがたいです」「お〜」「@kazuhoさんも続きでまさに同じことをおっしゃってますね↓」「こういうドキュメントがあると設計の議論がしやすくなりそう」

「BCP56はかなり新しいドキュメントみたい」「ドキュメントの日付を見ると2021年01/07…って今日?」「上のツイートを見つけたのが昨日なのに?」「ドラフトなので更新の日付でしょうね」「あ、更新中ですか」

「このBCP56和訳しませんか?」「翻訳してる間に本家がガンガン更新されそうな予感です😆」「でしょうね」「BCP56の一部をピックアップして解説する記事はあってもいいでしょうね」

⚓言語/ツール/OS/CPU

⚓ xv6: 教育用のミニUnix実行系

mit-pdos/xv6-public - GitHub

参考: xv6 - Wikipedia


つっつきボイス:「このxv6は、Railsチュートリアルでお馴染みのYassLabの安川さん↓に教えていただいたもので、コードベースがとても小さいので教育用のUnix系OSとして米国の大学でとてもよく使われているんだそうです」「へ〜!」

「昔は教育用といえばMINIXが使われてましたけど、あれも巨大化しちゃいましたよね」「昔はMINIXのソースコードをみんなで読んで勉強してましたけど、その現代版という感じ」「xv6のリポジトリを見てみたんですが、OSと思えないぐらいコードが少なくて、ちゃんと最小限のシェルまであるみたいです」「シェルがないと使えませんから😆

参考: MINIX - Wikipedia

「そういえばこの間のRuby 3.0.0リリースイベントが終わった後の雑談で安川さんがこのあたりの話をしてたような覚えがうっすらあります」「そうでしたか!私は力尽きて雑談まで見られなかった…」

「xv6もリポジトリが14 years agoとかあるので結構歴史ある感じ」「POSIXに準拠してるみたい」「MINIXは1980年代だからさらに古いですけどね」

「xv6、コマンドやシェルを除くとたしかにとても小さそう」「これなら印刷して読める分量ですね」「たしかに」

「お、xv6のRISC-V版の方がディレクトリが整理されているっぽい↓」「更新もされてますね」「FreeBSDやNetBSDなどのコードも拝借してると書かれてる」

mit-pdos/xv6-riscv - GitHub

⚓ 『詳解UNIXプログラミング』

「以下は直接関係ありませんが貼ってみました」「お〜なつかしい、と思ったけど第3版は自分が読んだ頃と表紙が違う気がする…」「もっと色少なかったですよね」

「自分が読んだのはこれだ↓」「そうそう、これですよ」「どちらも筆でざっくり描いたようなところが共通点かも」

「他にもUnixプログラミングの定番の名著はいくつかあるんですけど、タイトルが似通っているものが多くて今うまく見つけられない…」「探しにくいですよね」「この種の本は現代の人が学ぶときに環境設定などで効率はよくない面はあるけど、やはり名著」

「システムコールは増えることはあっても減ることはないので、こういう本に書かれているものは今でも動くのがいいですよね」「そうそう」「ユーザーアプリケーションから見て下のレイヤにあるシステムコールでどんなことができるのかを知っておくと、たとえばアプリでこれをやろうとするとどうやってもボトルネックに引っかかる、みたいなことがわかってくるので、よいと思います」

「シェルのコマンドをつなぐときも、システムコールの種類や特性を知ったうえでやるといいですよね」「その一方で、さっきのepollみたいな新しいシステムコールに気づかないまま古いものを使ってしまうこともあったりしそうですけど😆」「Linuxは今でもシステムコール増えてますよね」

「Rubyのアップデートでも、新しいシステムコールが出たので使いました、ということがありますよ」「へ〜」「RubyのリリースノートやRailsウォッチを追っていると、そういう新しいシステムコールを知る機会にもなりますね」


後編は以上です。

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

週刊Railsウォッチ(20210112前編)Active Recordの範囲指定バリデーション改善、soleとfind_sole_byメソッド、AlgoliaとRailsほか

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

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

Publickey

publickey_banner_captured

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

$
0
0

更新情報:
2013/11/19: 初版公開
2021/01/08: 訳文見直し、追記

こんにちは、hachi8833です。今回は、自分が知りたかった、Active Recordモデルのリファクタリングに関する記事を翻訳いたしました。1年前の記事なのでRails 3が前提ですが、Rails 4以降でも基本的には変わらないと思います。リンクは可能なものについては日本語のものに置き換えています。

なお、ここでご紹介したオブジェクトは、app以下にそれぞれ以下のようにフォルダを追加してそこに配置します。

注記: 以下は使われそうなフォルダを列挙しただけであり、実際にはこの一部しか使いません。

refactor

  1. Value Object
  2. Service Object
  3. Form Object
  4. Query Object
  5. View Object
  6. Policy Object
  7. Decorator

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

元記事: 7 Patterns to Refactor Fat ActiveRecord Models
Posted by @brynary on Oct 17th, 2012 (Code Climate Blog)

Railsアプリケーションの品質を高めるためにチーム内でCode Climateを使用していれば、モデルの肥大化を自然と避けるようになるでしょう。モデルが肥大化(ファットモデル)すると、大規模アプリケーションのメンテナンスが困難になります。ファットモデルは、コントローラがドメインロジックで散らかってしまうよりは1段階だけましであるとはいえ、たいていの場合単一責任の原則 (Single Responsibility Principle: SRP)の適用に失敗した状態であると言えます。

SRPの適用は、元々難しいものではありません。ActiveRecordクラスは永続性と関連付けを扱うものであり、それ以外のものではありません。しかしクラスはじわじわ成長していきます。永続性について本質的に責任を持つオブジェクトは、やがて事実上ビジネスロジックも持つようになるのです。1年2年が経過すると、User クラスには500行ものコードがはびこり、パブリックなインターフェイスには数百ものメソッドが追加されることでしょう。それに続くのはコールバック地獄です。

アプリケーションに何か本質的に複雑な要素を追加したら、ちょうどケーキのタネをケーキ型に流し込むのと同じように、それらを小規模かつカプセル化されたオブジェクト群(あるいはより上位のモジュール)に整然と配置することが目標になります。ファットモデルは、さしずめタネをケーキ型に流し込むときに見つかるダマ(混ざらなかった粉の固まり)のようなものでしょう。これらのダマを砕いて、ロジックが等分に広がって配置されるようにしなければなりません。これを繰り返し、それらが最終的にシンプルできちんと定義されたインターフェイスを持つ一連のオブジェクトとなって、それらが見事に協調動作するようにしましょう。

そうは言っても、きっとこう思う人もいることでしょう。

“でもRailsでちゃんとOOPするのってめちゃくちゃ大変ぢゃなくね?!”

私も以前は同じことを思ってました。でも若干の調査と実践の結果、RailsというフレームワークはOOPを妨げてなどいないという結論に達しました。スケールできないでいるのはRailsのフレームワークではなく、従来のRailsの慣習・流儀の方です。より具体的に言えば、Active Recordパターンできちんと扱える範囲を超えるような複雑な要素を扱うための定番の手法がまだないのです。幸いにも、オブジェクト指向における一般的な原則とベストプラクティスというものがあるので、Railsに欠けている部分にこれらを適用することができます。

⚓ (その前に)ファットモデルからミックスインで展開しないこと

肥大化したActiveRecordクラスから単に一連のメソッドを切り出してconcernsやモジュールに移動するのはよくありません。移動したところで、後でまた1つのモデルの中でミックスインされてしまうのですから。いつだったか、こんなことを言っていた人がいました。

“app/concerns ディレクトリを使っているようなアプリケーションって、だいたい後から頭痛の種になる(=concerning)んだよね”

私もそう思います。ミックスインよりも、継承で構成する方がよいと思います継承よりコンポジションの方がよいと思います。このようなミックスインは、部屋に散らかっているガラクタを引き出しに押し込めてピシャリと閉めたのと変わりません。一見片付いているように見えても、引き出しの中はぐちゃぐちゃ、どこに何があるのかを調べるだけでも大変です。ドメインモデルを明らかにするために必要な分解と再構成を実装するのも並大抵ではありません。
これはもうリファクタリングするしかないでしょう。

⚓ 1. Value Objectに切り出す

Value Objectは、異なるオブジェクト同士であっても値が等しければ等しいと見なされる、シンプルなオブジェクトです。Value Objectは変更不可能であるのが普通です。Rubyの標準ライブラリにはDateURIPathname などのValue がありますが、Railsアプリケーションでもドメイン固有のValue Objectを定義できますし、そうすべきです。ActiveRecordからValue Objectへの展開は、すぐにもメリットの得られるリファクタリングです。

Railsでは、ロジックが関連付けられている属性が1つ以上ある場合にはValue Objectが有用です。単なるテキストフィールドやカウンタ以上の要素は、何でもValue Objectの候補になりえます。

ちょうど著者が仕事をしている某テキストメッセージングアプリケーションには、PhoneNumber というValue Objectがあります。そして某EコマースアプリケーションではMoneyクラスを必要としています。私たちのCode Climateには RatingというValue Objectがあり、受け取ったクラスやモジュールのランキングをAからFまでの段階で表します。ここではRuby のStringクラスのインスタンスを使うこともできます(実際使っていました)が、このRatingを使用すると以下のように振る舞いとデータを一体化することができます。

class Rating
  include Comparable

  def self.from_cost(cost)
    if cost <= 2
      new("A")
    elsif cost <= 4
      new("B")
    elsif cost <= 8
      new("C")
    elsif cost <= 16
      new("D")
    else
      new("F")
    end
  end

  def initialize(letter)
    @letter = letter
  end

  def better_than?(other)
    self > other
  end

  def <=>(other)
    other.to_s <=> to_s
  end

  def hash
    @letter.hash
  end

  def eql?(other)
    to_s == other.to_s
  end

  def to_s
    @letter.to_s
  end
end

次にすべてのConstantSnapshotRatingのインスタンスをパブリックなインターフェイスに公開します。

class ConstantSnapshot < ActiveRecord::Base
  # …

  def rating
    @rating ||= Rating.from_cost(cost)
  end
end

このパターンは、ConstantSnapshotがスリムになるだけでなく、他にも多くの利点があります。

  • #worse_than?メソッドと#better_than?メソッドは、レートを比較する場合には<>などのRubyの組み込み演算子よりも適切です。
  • #hash#eql?を定義しておけばRatingをハッシュキーとして使用できます。Code Climateではこれを用いて、定数をレートごとにEnumberable#group_byでグループ化しています。
  • #to_sメソッドを定義してあるので、Ratingを簡単に文字列やテンプレートに変換できます。
  • このクラス定義は、ファクトリーメソッドを導入する場合にも便利です。矯正コスト (=クラスの「臭い」を除去するのにかかる時間) に見合う正しい Rating を得られます。

⚓ 2. Service Objectに切り出す

アクションによってはService Objectを用いて操作をカプセル化できることもあります。著者の場合、以下の基準に1つ以上マッチすればService Objectの導入を検討します。

  • アクションが複雑になる場合 (決算期の終わりに帳簿をクローズする、など)
  • アクションが複数のモデルにわたって動作する場合 (eコマースの購入でOrder, CreditCard, Customer を使用する、など)
  • アクションから外部サービスとやりとりする場合 (SNSに投稿する、など)
  • アクションが背後のモデルの中核をなすものではない場合 (一定期間ごとに古くなったデータを消去する、など)
  • アクションの実行方法が多岐にわたる場合 (認証をアクセストークンやパスワードで行なう、など)。これはGoF (Gang of Four)の書籍で言うStrategyパターンです。

例として、User#authenticateメソッドを取り出して UserAuthenticatorに配置しましょう。

class UserAuthenticator
  def initialize(user)
    @user = user
  end

  def authenticate(unencrypted_password)
    return false unless @user

    if BCrypt::Password.new(@user.password_digest) == unencrypted_password
      @user
    else
      false
    end
  end
end

このとき、SessionsController は以下のような感じになります。

class SessionsController < ApplicationController
  def create
    user = User.where(email: params[:email]).first

    if UserAuthenticator.new(user).authenticate(params[:password])
      self.current_user = user
      redirect_to dashboard_path
    else
      flash[:alert] = "Login failed."
      render "new"
    end
  end
end

⚓ 3. Form Objectに切り出す

1つのフォーム送信で複数のActive Recordモデルを更新する場合、Form Objectを使用して集約することができます。Form Objectを使えば、(個人的には使用を避けたい) accepts_nested_attributes_forよりもずっときれいなコードになります。CompanyUserを同時に作成するユーザー登録フォームを例にとってみましょう。

class Signup
  include Virtus

  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attr_reader :user
  attr_reader :company

  attribute :name, String
  attribute :company_name, String
  attribute :email, String

  validates :email, presence: true
  # …その他のバリデーション …

  # フォームそのものは決して永続化しない
  def persisted?
    false
  end

  def save
    if valid?
      persist!
      true
    else
      false
    end
  end

private

  def persist!
    @company = Company.create!(name: company_name)
    @user = @company.users.create!(name: name, email: email)
  end
end

これらのオブジェクトではVirtus gemのActive Record的な属性機能を利用しています。Form ObjectはActive Recordと同様に振る舞うので、コントローラは通常と変わらないものになります。

class SignupsController < ApplicationController
  def create
    @signup = Signup.new(params[:signup])

    if @signup.save
      redirect_to dashboard_path
    else
      render "new"
    end
  end
end

Form Objectは、上のようなシンプルな例ではうまくいきますが、永続性のロジックが含まれていてフォームが複雑になるのであれば、Service Objectも併用するのがよいでしょう。

Form Objectを導入することでさらにボーナスが付きます。バリデーションのロジックはコンテキストに依存しがちですが、Active Record自身の中でバリデーションを走らせるという融通の効かない方法に代えて、バリデーションロジックを実際に必要な場所で定義できます。

⚓ 訳追記(2021/01/08)

Rails 5からはActive Recordでattributes APIも使えるようになりました。記事末尾の関連記事もどうぞ。

参考: Rails 5のActive Record attributes APIについて | 日々雑記

Rails 5.2からはActive Modelでもattributes APIを使えるようになりました。

参考: 【Rails】「ActiveModel::Attributes」が便利という話 - 日々の学びのアウトプットするブログ

⚓ 4. Query Objectに切り出す

スコープやクラスメソッドなどのActiveRecordサブクラスが乱雑に定義された、複雑なSQLクエリがある場合は、Query Objectに切り出すことを検討します。1つのQuery Objectは、ビジネスロジックに基づいた結果セットを1つだけ返す責務を担当します。

たとえば、誰も訪問していないお試しを検索するQuery Objectは以下のような感じになります。

class AbandonedTrialQuery
  def initialize(relation = Account.scoped)
    @relation = relation
  end

  def find_each(&block)
    @relation.
      where(plan: nil, invites_count: 0).
      find_each(&block)
  end
end

このオブジェクトを使ってバックグラウンドで以下のようにメールを送信します。

AbandonedTrialQuery.new.find_each do |account|
  account.send_offer_for_support
end

ActiveRecord::RelationインスタンスはRails 3によってファーストクラスオブジェクトとして扱われるため、Query Objectでも多くの機能を使えます。このおかげで、コンポジションを使ってクエリを結合できます。

old_accounts = Account.where("created_at < ?", 1.month.ago)
old_abandoned_trials = AbandonedTrialQuery.new(old_accounts)

この種のクラスは個別にテストする必要はありません。オブジェクトのテストとデータベースのテストは同時に行うようにし、それによって正しい行が正しい順序で返されることと、join(結合)やeager loadingがすべて動作する(N + 1クエリ問題などを回避できているなど)ことを確認します。

⚓ 5. View Objectを導入する

表示にしか使わないようなロジックが必要な場合、それはモデルに置くべきではありません。「仮にアプリケーションのUIががらりと変わったら(たとえば音声駆動UIになったとしたら)、その時にもこれをモデルに置く必要があるだろうか」と自問自答してみましょう。モデルに置く必要のない表示ロジックであることがわかったら、ヘルパーに置くか、できればなるべくView Objectに置くようにしましょう。

たとえば、Code ClimateではRails on Code Climateなどでコードベースのスナップショットに基づいたクラスのレート付けを行う円グラフを使用していますが、これらは次のようにView Objectにカプセル化できます。

class DonutChart
  def initialize(snapshot)
    @snapshot = snapshot
  end

  def cache_key
    @snapshot.id.to_s
  end

  def data
    # @snapshotからデータを取り出してJSON構造に変換するコードを置く
  end
end

ところで、私はビューとERBテンプレート(またはHamlやSlim)が一対一対応していることが多いのに気が付きました。そこで、Railsで使えるTwo Step Viewパターンを実装できないか調べ始めたのですが、今のところこれについては明快なソリューションを見つけられずにいます。

⚓ メモ

Railsコミュニティでよく使われている「Presenter」という用語についてですが、この用語が他の用法と重複したり誤解を招いたりする可能性があるため、著者はこの用語を避けるようにしています。Presenterという語は、本記事で言うところのForm Objectを説明するためにJay Fieldsによって導入されました。また、運の悪いことにRailsでは「View」という用語もいわゆる「(ビューの)テンプレート」を指すものとして使われています。曖昧さを避けるため、著者はView Objectを「Viewモデル」と書くことがあります。

⚓ 6. Policy Objectに切り出す

複雑な読み出し操作はそのオブジェクト自身で行なうのがふさわしいことがあります。私はこのような場合にPolicy Objectを検討します。Policy Objectを使うことで、本質的でないロジック (分析用にどのユーザーをアクティブとみなすか、など) を、中核となるドメインオブジェクトから切り離すことができます。以下は例です。

class ActiveUserPolicy
  def initialize(user)
    @user = user
  end

  def active?
    @user.email_confirmed? &&
    @user.last_login_at > 14.days.ago
  end
end

このPolicy Objectには1つのビジネスルールがカプセル化されています。このビジネスルールでは、emailが確認済みで、かつ2週間以内にログインしたことがあるユーザーをアクティブなユーザーとみなすようになっています。Policy Objectは、複数のビジネスルール (特定のデータへのアクセスを許可するAuthorizer など) をカプセル化することもできます。

Policy ObjectはService Objectと似ていますが、私は「Service Objectは書き込み操作用」「Policy Objectは読み出し操作用」と使い分けています。これらはQuery Objectとも似ていますが、Policy Objectはメモリに読み込み済みのドメインモデルについて操作を行なうのに対し、Query Objectは特定の結果セットを返す「SQLの実行」に特化している点が異なります。

⚓ 7. Decoratorに切り出す

Decoratorは既存の操作に関する機能を階層化することによって、コールバックとよく似た機能を果たします。Decoratorは、特定の環境でしか実行したくないコールバックロジックがある場合や、ある機能をモデルに含めるとモデルの責務が増え過ぎる(=モデルが肥大化する)場合に便利です。

あるブログ投稿にコメントが付くと誰かのFacebookウォールに自動的に投稿されるようにしたとします。この場合、このロジック自体をCommentクラスにハードコードしなければならないわけではありません。コールバックに負わせる責務が多すぎると、テストの実行が遅くなり、不安定になるという形で兆候が現れます。こうした副作用は、何の関連もないテストケースから取り除くべきでしょう。

Facebookへの投稿ロジックをDecoracorに展開する方法を以下に示します。

class FacebookCommentNotifier
  def initialize(comment)
    @comment = comment
  end

  def save
    @comment.save && post_to_wall
  end

private

  def post_to_wall
    Facebook.post(title: @comment.title, user: @comment.author)
  end
end

このDecoratorをコントローラで以下のように使います。

class CommentsController < ApplicationController
  def create
    @comment = FacebookCommentNotifier.new(Comment.new(params[:comment]))

    if @comment.save
      redirect_to blog_path, notice: "Your comment was posted."
    else
      render "new"
    end
  end
end

DecoratorはService Objectとは異なります。Service Objectは既存のインターフェイスに対する責務を階層化しますが、これをDecorator化すると、FacebookCommentNotifier インスタンスをあたかも単なるCommentであるかのように取り扱います。

Rubyは、メタプログラミングを使用してDecoratorを簡単に作成するための仕組みを標準ライブラリに多数備えています。

⚓ 最後に

複雑なモデル層をうまく取り扱うためのツールは、Railsアプリケーションにも多数存在します。これらのツールを使用するためにRailsを捨てる必要などありません。Active Recordsは素晴らしいライブラリですが、これだけに頼っていてはどんなパターンも失敗します。ActiveRecordsは、極力永続的な振る舞いにとどめておくようにしてください。本記事で紹介したテクニックを一部だけでも適用して、自分のアプリケーションのドメインモデルに固まっているロジックを分散させることができれば、アプリケーションのメンテナンスはずっと容易になるでしょう。

本記事で紹介したパターンの多くはシンプルです。これらのオブジェクトは、いずれも「昔ながらのPORO(Plain Old Ruby Object: シンプルなRubyオブジェクト)」であって、ただその使い方が異なるだけです。これはOOPの一部であり、OOPの美しさをなすものです。問題を解決するのにフレームワークやライブラリだけに頼る必要はありません。手法に適切な名前を付けることも重要な技法です。

本記事で紹介した7つの手法はいかがでしたでしょうか。お気に入りの手法は見つかりましたでしょうか。それはどんな理由でしょうか。皆さまのコメントをお待ちしています。

追伸: この記事を気に入っていただけましたら、元記事の下にあるフォームからCode Climateのニュースレターをぜひ購読してみてください。OOPやRailsアプリケーションのリファクタリングなど、今回の記事のようなトピックを扱っています。記事のボリュームは控えめにしています。

より詳しい情報

本記事をレビューしてくれた皆様に感謝します: Steven Bristol, Piotr Solnica, Don Morrison, Jason Roelofs, Giles Bowkett, Justin Ko, Ernie Miller, Steve Klabnik, Pat Maddox, Sergey Nartimov, Nick Gauthier

関連記事

Service Objectがアンチパターンである理由とよりよい代替手段(翻訳)

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


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

$
0
0

こんにちは、hachi8833です。

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

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

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

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


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

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

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

参考: FormData - Web API | MDN


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

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

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

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

参考: CRUD - Wikipedia

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

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

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

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

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

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

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


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

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

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

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


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

参考: INFO – Redis

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

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

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

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


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

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

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

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

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

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

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

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

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


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

参考: Rails TextHelperまとめ - Qiita

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


同PRより


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

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

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

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

⚓Rails

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


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

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

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

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

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

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

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

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

GeorgeKaraszi/ActiveRecordExtended - GitHub


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

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

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

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

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

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

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

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

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

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

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

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

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

# OR

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

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

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

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

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

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

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

      def merge
        merge_ctes!
        super
      end

      def merge_ctes!
        return unless other.with_values?

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

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

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

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

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

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

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

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

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

mperham/sidekiq - GitHub

bensheldon/good_job - GitHub

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


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

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

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

⚓ その他Rails


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

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


前編は以上です。

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

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

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

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

Rails公式ニュース

Ruby on Rails Discussions

Ruby Weekly

Hacklines

Hacklines

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

$
0
0

こんにちは、hachi8833です。

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

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

⚓Ruby

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


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

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

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

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

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

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

# 同記事より
int = 1

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

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

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

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

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

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

<expression> in <pattern>

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

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

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

ruby2js/ruby2js - GitHub


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

opal/opal - GitHub

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

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

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

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

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

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

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


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

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

tomykaira/rspec-parameterized - GitHub


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

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

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

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

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

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

⚓ その他Ruby

rbspy/rbspy - GitHub

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


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

docker/compose - GitHub

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

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

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

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


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

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

⚓DB

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

bibendi/activerecord-postgres_enum - GitHub


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

⚓ RailsとPostgreSQLのenum型

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

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

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

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

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


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

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

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

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

参考: Redash helps you make sense of your data

⚓その他

⚓ NandGame


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

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

参考: NANDゲート - Wikipedia

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


後編は以上です。

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

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

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

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

Ruby Weekly

Serverless Status

serverless_status_banner

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

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

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

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

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


概要

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

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

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

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

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

⚓ URLのみを指定する

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

⚓

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<%= render form %>

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

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

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

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

関連記事

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

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

$
0
0

こんにちは、hachi8833です。

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

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

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

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

⚓ type_for_attribute周りを修正

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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


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

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

        build_preloaders
      end

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

before:

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

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

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

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


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

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


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

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

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


github.com/rails/railsより

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

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

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


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

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

rails/rails - GitHub

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


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

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

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

⚓Rails

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


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

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

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

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

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

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


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

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

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

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

cucumber/cucumber - GitHub

jnicklas/turnip - GitHub

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

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

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

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

⚓ その他Rails


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

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

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

参考: PostgreSQL: Windows installers

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

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


前編は以上です。

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

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

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

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

Rails公式ニュース

Ruby Weekly

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

$
0
0

こんにちは、hachi8833です。

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

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

⚓Ruby

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

# 同記事より
require "functions_framework"

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

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

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

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

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


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

asdf-vm/asdf - GitHub

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

anyenv/anyenv - GitHub

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

⚓DB

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


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

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

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

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

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

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

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

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

参考: JavaScript Object Notation (JSON) - Wikipedia

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

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

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

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


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

参考: etcd | Home

tailscale/tailetc - GitHub

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

⚓JavaScript

⚓ 2020のJavaScript


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

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

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

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

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

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

参考: styled-components

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

⚓言語/ツール/OS/CPU

⚓ Reactの用語


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

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

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

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

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

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

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

⚓その他

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


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

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

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


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

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

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

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

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


後編は以上です。

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

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

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

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

Ruby Weekly

DB Weekly

db_weekly_banner

Viewing all 1100 articles
Browse latest View live