- Ruby / Rails関連
週刊Railsウォッチ(20201012前編)Railsの隠し機能routing visualizer、action_args gem、N+1用goldiloader gemほか
こんにちは、hachi8833です。Kaigi on Railsのスライドをまとめてくださった記事を見つけました🙇。
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇
⚓Rails: 先週の改修(Rails公式ニュースより)
久しぶりにRails公式の更新情報が出ていました↓。
つっつきボイス:「ちなみに上のほとんどを先週のウォッチで先取りしていてちょっと嬉しいです」「ホントだ、バックグラウンドジョブもinterval
データ型もActive Storageコンフィグも先週やってるし」「こうやって毎週追っていると新しい機能とかの話がしやすくなるのがいいですよね」
以下のコミットリストのChangelogを中心に見繕いました。
- コミットリスト: Comparing @{2020-10-01}...master@{2020-10-08} · rails/rails
- 6.1マイルストーン: 6.1.0 Milestone -- 26件
「6.1のリリースはそう遠くないのかな?」「6.1はだいぶ機能が増えているのでproductionで使う前に検証しておきたいですね」
「そういえば今週は@amatsudaさんが集中的にコミットしていて何事かと思ったらsend
をpublic_send
に変更していました↓」「たしかに、可能ならsend
よりpublic_send
を使うべき」
参考: When to use send
and public_send methods
in ruby? - Stack Overflow
⚓ Active Storage、Action Text、Action MailboxのActiveRecord::Base
をRecord
に切り出した
つっつきボイス:「ActiveStorage
とActionText
とActionMailbox
で使われているActiveRecord::Base
をRecord
に置き換えたのね」「それぞれActiveStorage::Record
とActionText::Record
とActionMailbox::Record
という形になってる」
# actionmailbox/app/models/action_mailbox/inbound_email.rb#L27
- class InboundEmail < ActiveRecord::Base
+ class InboundEmail < Record
self.table_name = "action_mailbox_inbound_emails"
include Incineratable, MessageId, Routable
has_one_attached :raw_email
enum status: %i[ pending processing delivered failed bounced ]
def mail
@mail ||= Mail.from_source(source)
end
def source
@source ||= raw_email.download
end
def processed?
delivered? || failed? || bounced?
end
end
end
# actionmailbox/app/models/action_mailbox/record.rb
+# frozen_string_literal: true
+
+module ActionMailbox
+ class Record < ActiveRecord::Base #:nodoc:
+ self.abstract_class = true
+ end
+end
+
+ActiveSupport.run_load_hooks :action_mailbox_record, ActionMailbox::Record
「Active RecordにおけるApplicationRecord
↓と同じような形に設計を整理したということでしょうね」
# models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
⚓cache-control
ヘッダーでno-store
を指定したときに他の設定を含まないよう修正
- PR: allow for only no-store in cache-control header by montdidier · Pull Request #39461 · rails/rails
Railsアプリケーションをデフォルトで一切キャッシュしないよう設定することが望ましいケース(PCI DSSなどへのコンプライアンス)では、キャッシュなしをシンプルに設定できるとありがたい。
最も手っ取り早くやれそうに思えるのは、config/application.rbの設定を以下のように変更する方法。
config.action_dispatch.default_headers.merge!('Cache-Control' => 'no-store', 'Pragma' => 'no-cache')
しかしこうすると
Cache-Control: private, no-store
のような結果になって微妙にうまくいかない。
このユースケースにおけるprivate
キーワードは、no-store
と併用されると少なくとも余計であり、最悪の場合は危険ですらある(ブラウザが何らかの形で解釈に混乱をきたした場合)。自分はMDNのリファレンスを参照してこの結論に達した。
このプルリクは、デフォルトの
Cache-Control
ヘッダーで単にno-store
ディレクティブを設定したときはprivate
を含まないようにする。
同PRより大意
つっつきボイス:「そういえばちょうどこの間Cache-Control
ヘッダー周りを調べたんですよ↓」「ホントだ、no-store
も載ってますね」「キャッシュディレクティブってこんなにいっぱいあるんですか!」
# リクエスト時のキャッシュディレクティブ
Cache-Control: max-age=<seconds>
Cache-Control: max-stale[=<seconds>]
Cache-Control: min-fresh=<seconds>
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: only-if-cached
# レスポンス時のキャッシュディレクティブ
Cache-Control: must-revalidate
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: public
Cache-Control: private
Cache-Control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-Control: s-maxage=<seconds>
参考: Cache-Control - HTTP | MDN
no-store
レスポンスをキャッシュに保存することはできません。他のディレクティブを設定することもできますが、最近のブラウザーではレスポンスがキャッシュされることを防ぐために必要なディレクティブはこれだけです。
max-age=0
が暗黙で含まれます。must-revalidate
は意味を持ちません。再検証を行うにはレスポンスがキャッシュに格納されている必要がありますが、no-store
はこれを抑止するからです。
private
レスポンスが通常はキャッシュ可能でなくても、ブラウザーのキャッシュにのみ格納することができます。レスポンスがどのキャッシュにも保存されないようにするには、代わりに
no-store
を使用してください。このディレクティブにはレスポンスがキャッシュに保存されないようにする効果はありません。
developer.mozilla.orgより
「プルリクメッセージを見ると、修正前はCache-Control: private, no-store
と両方が設定されていたけど、MDN↑にも書かれているように、サーバーサイドでno-store
を指定するときにそれ以外の指定が重複するとCache-Control
ヘッダの意図としては矛盾してしまうので、no-store
とprivate
が同時に指定される状況は好ましくないということだと思います」「ブラウザは恐らく、よりセキュアで強い指定であるno-store
指定を優先して、private指定を無視して動作するでしょうけど、念のためサーバーからのレスポンスにprivate
設定を含めないようにしたんでしょうね」
「修正は、:no_store
が設定されていれば_cache_control
をNO_STORE
で上書きして、それ以外の場合はその時の設定値に合わせて複数ディレクティブを設定するように変更されている↓」「なるほど!」「ちょうど最近この辺を調べたばかりでよかった: こうやって思い出すことでより頭に残りやすくなりますし✨」
# actionpack/lib/action_dispatch/http/cache.rb#L197
- if control[:no_cache]
+ if control[:no_store]
+ self._cache_control = NO_STORE
+ elsif control[:no_cache]
options = []
options << PUBLIC if control[:public]
options << NO_CACHE
options.concat(control[:extras]) if control[:extras]
「ちなみにPragma: no-cache
という記述はHTTP/1.0の古い仕様との互換性用です↓」
メモ:
Pragma
は HTTP レスポンスには指定されていないため、リクエストのCache-Control
ヘッダーフィールドが省略されている場合はCache-Control: no-cache
と同じように動作しますが、一般的な HTTP/1.1Cache-Control
ヘッダーの代わりに信頼できるものではありません。Pragma
は HTTP/1.0 クライアントとの下位互換性のためにのみ使用してください。
developer.mozilla.orgより
⚓fill_in_rich_text_area
を<label>
テキストで探索
つっつきボイス:「Action Textか」「久しぶりに機能が追加されたようです」「HTML5のfor
属性を<label>
要素で指定した場合にTrixエディタが対応したのね↓」
# actiontext/lib/action_text/system_test_helper.rb#L3
module ActionText
module SystemTestHelper
# Locates a Trix editor and fills it in with the given HTML.
#
# The editor can be found by:
# * its +id+
# * its +placeholder+
# * the text from its +label+ element
# * its +aria-label+
+ # * the +name+ of its input
#
# Examples:
#
# # <trix-editor id="message_content" ...></trix-editor>
# fill_in_rich_text_area "message_content", with: "Hello <em>world!</em>"
#
# # <trix-editor placeholder="Your message here" ...></trix-editor>
# fill_in_rich_text_area "Your message here", with: "Hello <em>world!</em>"
#
+ # # <label for="message_content">Message content</label>
+ # # <trix-editor id="message_content" ...></trix-editor>
+ # fill_in_rich_text_area "Message content", with: "Hello <em>world!</em>"
+ #
# # <trix-editor aria-label="Message content" ...></trix-editor>
# fill_in_rich_text_area "Message content", with: "Hello <em>world!</em>"
参考: for属性 ≪ label要素 ≪ メタデータ ≪ 要素 ≪ HTML5入門
<!-- html5.cyberlab.info より -->
<label for="sampleId">チェックボックス: </label>
<input type="checkbox" name="sampleName" value="sampleValue" id="sampleId">
このコミットは、アクセシビリティを意識する形で
rich_text_area
への呼び出しをテストする方法を改善することにフォーカスするという点で#38551と調和する。
適切なaria-label
属性を持つ<trix-editor>
要素を検索する他に、対応する<label>
のテキストとマッチする要素の探索もサポートする。
basecamp/trix#829がマージおよびリリースされたことで、<trix-editor>
要素を参照する<label>
要素をクリックするとその<trix-editor>
要素にフォーカスが移動する。
アクセシビリティを改善可能なラベルテキストは他にもいくつかあるが、手始めにfill_in_rich_text_area
を拡張して<label for="...">
要素を扱えるようにするのがよいだろう。
同PRより大意
⚓uniquenessバリデーターのconditions:
オプションにレコードを渡せるようになった
uniquenessバリデーターの
conditions:
オプションにレコードを渡せるようサポート。
これによって、レコードの属性に基づいて条件をビルドできるようになる。
例: 出版期間内でユニークでなければならないslug
をバリデーションする場合は以下のようになる。
class Article < ApplicationRecord
validates_uniqueness_of :slug,
conditions: ->(record) {
where(published_at: record.published_at.beginning_of_day..record.published_at.end_of_day)
}
この機能追加を歓迎してもらえるのであればもう少し手を加えてテストカバレッジも追加する。
同PRより大意
つっつきボイス:「上みたいな書き方って今までできなかったんですか?」「たぶんカスタムバリデーターを書けば今までもできたと思いますけど、そうしなくてもできるようになったんでしょうね」「なるほど、必要だったら自分で書いちゃうかも」「あると嬉しい機能👍」
「diffを見ると↓今までもuniquenessの条件をlambdaで渡せたみたいだけど、lambdaの中で当該レコード自身を参照できなかったので、ブロック引数で受けられるようにしたのがポイントかな」「なるほど」
# activerecord/lib/active_record/validations/uniqueness.rb#L19
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, attribute, value)
if record.persisted?
if finder_class.primary_key
relation = relation.where.not(finder_class.primary_key => record.id_in_database)
else
raise UnknownPrimaryKey.new(finder_class, "Cannot validate uniqueness for persisted record without primary key.")
end
end
relation = scope_relation(record, relation)
- relation = relation.merge(options[:conditions]) if options[:conditions]
+
+ if options[:conditions]
+ conditions = options[:conditions]
+
+ relation = if conditions.arity.zero?
+ relation.instance_exec(&conditions)
+ else
+ relation.instance_exec(record, &conditions)
+ end
+ end
if relation.exists?
error_options = options.except(:case_sensitive, :scope, :conditions)
error_options[:value] = value
record.errors.add(attribute, :taken, **error_options)
end
end
「たしかにpublished_at: record.published_at.beginning_of_day..record.published_at.end_of_day
っていう条件を渡すならrecordも渡さないとできませんよね」「recordに対する相対的なuniquenessをチェックするというのはuniquenessの判定方法としてはやや特殊な気はしますけど」
⚓ バッチのupdate_all
やdelete_all
が非バッチ版と同様に行数を返すよう修正
#40287を修正する。
従来はnil
という戻り値がドキュメントに書かれておらず、バッチでないバージョンのメソッドと整合していなかった。
また、each
でバッチを作成できるようにし、BatchEnumerator
のupdate_all
やdelete_all
やdestroy_all
にAPIドキュメントを追加した。
同PRより大意
つっつきボイス:「#40287↓を見ると、in_batches.update_all
でnil
が返されていたらしい」
「SQL的な作法としては、UPDATE文やDELETE文はレスポンスとして表を返さない代わりに『何行処理したか』という行数を返すものなんですけど、その『何行処理したか』が返されていなかったのを返すようにしたようです」「ああ、MySQLとかならxx rows affected
のように確実に数値が返されますけど、今までの実装ではそこでnil
を返していたのが問題だったんですね」「たしかにSQL的な作法としては欲しい」
「batchedでないときはちゃんと件数返していたようです」「なるほど、update_all
やdelete_all
で取れてなかったのはin_batches
の場合なのね↓」「あまり使われてなさそうなケースだから気づきにくいかも」「これは直ってよかった🎉」
# #40287より
class BugTest < Minitest::Test
def test_update_all
assert_equal 3, User.update_all('id = id') # 成功する
end
def test_update_all_in_batches
assert_equal 3, User.in_batches.update_all('id = id') # 失敗する(nilが変える)
end
end
「in_batches
って何をするんだろうと思って調べてみると、デフォルトで1000件ずつ分割して処理するのか↓」
# api.rubyonrails.orgより
Person.where("age > 21").in_batches do |relation|
relation.delete_all
sleep(10) # Throttle the delete queries
end
⚓Rails
⚓ delegated_type
を使ってみた
つっつきボイス:「前にも話題に出たRailsのdelegated_type
記事です(ウォッチ20200601)」「STIではないpolymorphic associationsですね」
「Railsでpolymorphic associationsというとSTIという印象が強いんですけど、STI以外にも実装方法があってdelegated_type
はそのひとつ」「polymorphic associationsでググってみるとほとんどがRailsとSTIの記事ですね😆」
「この図の赤で囲まれている部分↓はSTIだとひとつのテーブルになりますけど、記事ではこれをdelegated_type
で実装したんですね」「記事長い...これ後でちゃんと読みます」
「なお、そーだいさんの書籍『失敗から学ぶRDBの正しい歩き方』↓の『第7章:隠された状態』には、STIではないpolymorphic associationsについて解説が載っているので読むとよいと思います」
⚓goldiloader: N+1を回避するgem
以下の記事で知りました。
つっつきボイス:「ゴルディローダー?」「前からあるみたいですが初めて知りました」「コードを変えずに自動的にeager loadingしてくれるgemみたい」「fully_load: true
のようにヒントも付けられるのね」
# 同記事より: goldiloaderなしの場合
> blogs = Blogs.limit(5).to_a
# SELECT * FROM blogs LIMIT 5
> blogs.each { |blog| blog.posts.to_a }
# SELECT * FROM posts WHERE blog_id = 1
# SELECT * FROM posts WHERE blog_id = 2
# SELECT * FROM posts WHERE blog_id = 3
# SELECT * FROM posts WHERE blog_id = 4
# SELECT * FROM posts WHERE blog_id = 5
# 同リポジトリより: goldiloaderありの場合
> blogs = Blogs.limit(5).to_a
# SELECT * FROM blogs LIMIT 5
> blogs.each { |blog| blog.posts.to_a }
# SELECT * FROM posts WHERE blog_id IN (1,2,3,4,5)
「ちゃんと動くならよさそう😋」「最近のActive Recordは@kamipoさんたちが多くのチューニングをかけているので、もしかすると既に似たようなことをある程度内部でやってるかもしれないという気がしますね」「あ〜やってそう!」
「goldiloaderは以下の翻訳記事を見ると、ぱっと見bulletのオルタナかなと思ったのですが...」「bulletはN+1を検出するgemだけどgoldiloaderはN+1を自動回避するgemなので、オルタナではなくてN+1対応の別アプローチと考える方がいいでしょうね」「なるほど!」
N+1を今後も解決するためにBullet gemを導入します。私はBulletが大好きです❤️。次の3つの理由からGoldiloaderよりもBulletが好みです。
同記事より
「goldiloaderは、N+1を解決するという最終目的だけに着目すればbulletのオルタナと言えなくもありませんけど、方法としてはオルタナではないでしょうね」「たしかに」「bulletはN+1を人間が解決するために検出して教えてくれるけど、goldiloaderはN+1をいい感じに自動解決する代わりにN+1を隠蔽するので」「翻訳記事は隠蔽より解決を志向してbulletを選んだんでしょうね」
⚓ Railsルーターの隠し機能
routes一個多かった
— 子機の上 (@onthekoki) October 3, 2020
つっつきボイス:「そうそう、Kaigi on Rails冒頭のキーノートスピーチで@tenderloveさんが発表していたこれはスゴい💪」「あのときその場で動かそうとして四苦八苦しました😅」「当時Twitterにも貼りましたけど、これを動かすにはgraphvizが必要なんですよ↓」「あ〜、それで動かなかったのか...」
routes visualizer、`devise_for :users` みたいなDSL処理される系のroutingでもちゃんと展開された。めっちゃ実用的では #kaigionrails pic.twitter.com/LBBKPcE4D4
— Masato Mori (@morimorihoge) October 3, 2020
「これなかなか優秀な機能で、Railsのルーティングエンジンに入ったものをこうやってビジュアル表示してくれるんですよ、しかもdevise_for
のようなDSL処理されているルーティングも表示できますし」「おおスゴい!ちょうどこういうのを使いたいプロジェクトがあるんですよ😂」「rails routes
で表示されるルーティングだとややフラットに表示されるんですけど、特にルーティングがめちゃくちゃ大きいときはツリーで見たいですよね」「同意です!」「こういう隠し機能をさらりと紹介する@tenderloveさんさすが」
「Railsのtransition_table.rbにあるvisualizer
、思ったよりコードがシンプルかも↓」
# actionpack/lib/action_dispatch/journey/gtg/transition_table.rb#78
def to_svg
svg = IO.popen("dot -Tsvg", "w+") { |f|
f.write(to_dot)
f.close_write
f.readlines
}
3.times { svg.shift }
svg.join.sub(/width="[^"]*"/, "").sub(/height="[^"]*"/, "")
end
def visualizer(paths, title = "FSM")
viz_dir = File.join __dir__, "..", "visualizer"
fsm_js = File.read File.join(viz_dir, "fsm.js")
fsm_css = File.read File.join(viz_dir, "fsm.css")
erb = File.read File.join(viz_dir, "index.html.erb")
states = "function tt() { return #{to_json}; }"
fun_routes = paths.sample(3).map do |ast|
ast.map { |n|
case n
when Nodes::Symbol
case n.left
when ":id" then rand(100).to_s
when ":format" then %w{ xml json }.sample
else
"omg"
end
when Nodes::Terminal then n.symbol
else
nil
end
}.compact.join
end
後でMac環境でやってみました。brew install graphviz
でGraphvizをインストールし、以下を実行するとout.htmlが生成され、ボックスにルーティングを入力してsimulateボタンをクリックするとルーティングが緑色にハイライトされました。
$ bin/rails r 'File.binwrite "out.html", <アプリ名>::Application.routes.router.visualizer'
⚓ action_args gem
つっつきボイス:「この間の感想戦締めくくりのキーノートスピーチ↓でamatsudaさんが触れていたaction_args gemが気になってピックアップしてみました」「action_args gemは自分も結構好きです」
「このときのメモを読み返すと『action_argsはRailsのコントローラをMerb風にするgem』とあります」「Merbは昔Railsと統合されたフレームワークですね↓」
参考: MerbがRails 3に統合、人気Rubyフレームワークが合体へ - ITmedia エンタープライズ
「amatsudaさんが『この構文を選ばなかったのはDHHの選択ミスだったと思っている』とお話しされてたのが気になりました↑」「これは自分も共感する部分がありますね: 現在のRailsのようなaction methodとStrong Parametersが分離している書き方よりも、こういうaction_args的な書き方の方が本来あるべき姿だったのかもしれないという気持ち」
# 同リポジトリより
class UsersController < ApplicationController
permits :name, :age, :email
# GET /users
def index
@users = User.all
end
# GET /users/1
def show(id)
@user = User.find(id)
end
# GET /users/new
def new
@user = User.new
end
# GET /users/1/edit
def edit(id)
@user = User.find(id)
end
# POST /users
def create(user)
@user = User.new(user)
if @user.save
redirect_to @user, notice: 'User was successfully created.'
else
render :new
end
end
# PUT /users/1
def update(id, user)
@user = User.find(id)
if @user.update(user)
redirect_to @user, notice: 'User was successfully updated.'
else
render :edit
end
end
# DELETE /users/1
def destroy(id)
@user = User.find(id)
@user.destroy
redirect_to users_url, notice: 'User was successfully destroyed.'
end
end
「上のshow(id)
みたいにメソッド定義にパラメーターが示される方がparams経由の参照に比べるとメソッドとして読みやすいと思います。現在のRailsのStrong Parametersのようなメソッド定義のパラメーターに書かれていないグローバル的な変数をactionのコード内で参照するのはあんまりキレイじゃないかもという気持ちがあります」「ふむふむ」「とは言えWebリクエストのパラメーターはすぐネストが深くなるので、show(id)
のようなaction_args的な書き方ですべてうまくいくとは限らないのもわかりますし、悩ましいところではあります」「う〜む」「パラメーターの個数なりフォーマットなりが固定されているのであれば、個人的にはaction_args的な書き方の方がキレイだと思います」
「それにaction_args的な書き方なら必須パラメーターもこういうふうにRubyの構文で自然に書けますし↓」「たしかにキーワード引数とかデフォルト値とかを普通に書けますね」
# 同リポジトリより
class CommentsController < ApplicationController
def create(post_id:, comment:)
post = Post.find post_id
if post.create comment
...
end
end
「現在のRailsの*_params
というprivate methodを使ったStrong Parametersによるパラメーターアクセスは、アクション間で定義を共有できるメリットがあるとも言えますし、開発者もそれに慣れていますけど、プログラムとして自然な書き方という点ではこのaction_args的な書き方に惹かれるところはあります」「言われてみれば、今の*_params
なメソッドって毎回書いていますけど、パラメータ数が増えたりネストしたパラメータを許可したりしていると、この書き方で本当にいいんだろうかと思うことがありますね」「ある意味スキーマレスになっていくというか」「それそれ!」「まあStrong Parametersでフィルタすることで多少スキーマを通している感じにはなりますけどね」
「ただ、スキーマ的なものを通せる前提があるんだったら生のparams
自体はアクションのメソッド内から直接参照できなくてもいいんじゃないの?って思うわけですよ: 実際action_argsをうまく使えば以下のようにparams
が出現しないように書けますし↓、個人的にもその方がキレイだと思うんですけど」「リクエストされたパラメータがフィルタされずに入っているparams
を参照するようなコードを書くのが微妙に居心地悪い感じがするというのは何だかわかります」
# 同リポジトリより(コメントは本記事にて加筆)
# action_argsのconvensionとして、`#{name}`、または`#{name}_params`という引数名で受けると
# params[:#{name}]相当の`#{name}`のmodel objectが渡されたかのように自動展開できる
# 以下は同じ挙動になる
# without _params
def create(user)
@user = User.new(user)
...
end
# with _params
def create(user_params)
@user = User.new(user_params)
...
end
# 同リポジトリより
# Strong Parameters相当のパラメータフィルタは
# Controllerに`permits`で記述できる
class UsersController < ApplicationController
# allow-lists User model's attributes
permits :name, :age
# the given `user` parameter would be automatically permitted by action_args
def create(user)
@user = User.new(user)
end
end
「params
って、PHPにおける$_POST
みたいな感じで、クライアントからのリクエストがそのまま入っていて直接参照するのが危険なイメージがあるんですよね😆」「それもわかります!」「Strong Parametersでフィルタした*_params
の方はフィルタされた結果なので、原則こちらしか触らないようにしたい」「コントローラークラスならたしかにparams
を参照できる必要のあるユースケースはあるんですけど、だからといってコントローラのアクションがカジュアルにこれらを参照していいんだろうかという気持ちはあります」
⚓ その他Rails
PHPやDBも勉強しましょう。しかし、Rails Tutorialをお勧めするのは、テスト駆動開発、githubでのソースコード管理、Herokuへのデプロイなど「ウェブ開発における開発者の常識」が学習の流れで体験できるからです。 https://t.co/m5duyVtyeR
— 徳丸 浩 (@ockeghem) October 1, 2020
つっつきボイス:「Rails以外の人にもRailsチュートリアルをすすめていたので」「徳丸先生も書いているように、RailsチュートリアルのいいところはWeb開発で常識的に必要なものがとりあえず全部入っていることですね👍」「たしかに!」「Web開発についてここまでまとまっている資料って他にあまりありませんし」
前編は以上です。
バックナンバー(2020年度第4四半期)
週刊Railsウォッチ(20201006後編)Rubyの`defined?`キーワード、Ractorベースのジョブスケジューラ、Caddy Webサーバーほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。