こんにちは、hachi8833です。Rails 7.2のRCはまだリリースされていません。
🔗Rails: 先週の改修(Rails公式ニュースより)
- 公式更新情報: Ruby on Rails — New database sharding methods, improved Active Record Migration Docs, caching improvements for ActiveStorage and more!
- 公式更新情報: Ruby on Rails — Immutable Rails Cache Header, Local Assigns in Strict Local Templates, Threaded Active Storage Mirror Service
🔗 シャーディング用メソッド.shard_keys
、.sharded?
、.connected_to_all_shards
が追加
動機/背景
現在は、モデルが単一のデータベースに接続しているか複数のシャードに接続しているかを知る(簡単な)方法がない。しかも、モデルのコネクションをループで回さない限り、モデルが接続可能なシャードのリストを返す簡単な方法はないと思う。
詳細
このコミットは、
@shard_keys
インスタンス変数を追加する。このインスタンス変数は.connects_to
が呼び出されるたびに必ず設定され、shards.keys
の結果に設定する。
.connects_to
のshards
は、デフォルトでは空のハッシュに設定されるため、connects_to database: {...}
を呼び出すと、@shard_keys
は空の配列に設定される。
@shard_keys
は、以下の行よりも前に設定される。if shards.empty? shards[:default] = database end
この条件は、唯一のシャード(
:default
)を、.connects_to
に渡すdatabase
の値に設定する。これにより、以下のようにデータベースだけに接続するよう設定されたモデルでもconnected_to(shard: :default)
を呼び出せるようになる。class UnshardedBase < ActiveRecord::Base self.abstract_class = true connects_to database: { writing: :primary } end class UnshardedModel < UnshardedBase end UnshardedBase.connected_to(shard: :default) { UnshardedBase.connection_pool.db_config.name } => primary
このモデルは結局「非シャード」のままなので、
@shard_keys
はこの条件より前のタイミングで設定される。新しい
@shard_keys
インスタンス変数では、Active Recordの抽象モデルの子孫が同じ値を返す方法が必要になる。そのため、既存の.connection_class_for_self
メソッドを活用する。このメソッドは、.connects_to
が呼び出されたモデルの先祖を返すか、コネクションクラスの場合はselfを返す。class UnshardedBase < ActiveRecord::Base self.abstract_class = true connects_to database: { writing: :primary } end class UnshardedModel < UnshardedBase end ActiveRecord::Base.connection_class_for_self => ActiveRecord::Base UnshardedBase.connection_class_for_self => UnshardedBase(abstract) UnshardedModel.connection_class_for_self => UnshardedBase(abstract)
新しい
.shard_keys
メソッドは、コネクションクラスから@shard_keys
の値か空配列を返すゲッターである。この空配列は、connects_to
が呼び出されなかった場合に必要。最後に、
.connected_to_all_shards
メソッドを追加した。これは.connected_to
のすべての引数(ただしshard
は除く)を受け取り、すべてのシャードキーをループで回してから、それ以外のすべてを.connected_to
に委譲する。各ブロックの結果をコレクションできるようにするため、.each
ではなく.map
を使った。
同PRより
つっつきボイス:「Active Recordのモデルオブジェクトがあったときに、シャーディングされているのかどうかを知るメソッドなどを追加したんですね」「もちろんクエリを投げればできますけど、クエリを投げずにモデルのオブジェクトでできるのはよさそう👍」「シャードのキー(shard_one:
やshard_two:
)を指定できるのがいいですね↓」
- シャード用に
.shard_keys
、.sharded?
、.connected_to_all_shards
メソッドを追加class ShardedBase < ActiveRecord::Base self.abstract_class = true connects_to shards: { shard_one: { writing: :shard_one }, shard_two: { writing: :shard_two } } end class ShardedModel < ShardedBase end ShardedModel.shard_keys => [:shard_one, :shard_two] ShardedModel.sharded? => true ShardedBase.connected_to_all_shards { ShardedModel.current_shard } => [:shard_one, :shard_two]
Nony Dutton
同Changelogより
参考: 7 自動シャード切り替えを有効にする -- Active Record の複数データベース対応 - Railsガイド
「なお、ここで扱っているシャーディングとは複数DBに分割してデータを保存する方式を指していて、1テーブル内で複数テーブルに分割するパーティショニングとは異なります: そのためシャードの単位はDB接続先の単位となります」
「シャーディングされているかどうかを途中で知りたいときってあるんでしょうか?」「シャーディングのアーキテクチャ次第ですね: シンプルなシステムならシャーディングのノード数が増減すると再構築が必要になるけど、オーバーレイネットワークのシステムにはシャーディングのノード数が増減しても再構築してくれるものも普通にあるし、AWS Auroraで使っているファイルシステムなんかもそういう感じになっていますね」「なるほど」
「そういえばソシャゲ全盛だった頃はシャーディングがソシャゲのデータベース設計の基礎みたいに言われてたりしましたけど、今のソシャゲは有名どころの顔ぶればかりで、低予算のソシャゲで一発当てるみたいな開発はほとんど見かけなくなった感ありますね」「そうかも」「昨今の覇権取れるレベルのユーザー数を前提とするようなソシャゲの場合、昔ながらの単純なシャーディングでは力不足で、GCPのSpannerとかTiDBみたいなもっと大規模なトラフィックに耐えられるものを金の弾丸で導入する、というような状況になっていると思います」
参考: TiDB: The Advanced Distributed SQL Database
🔗 Active Storageプロキシコントローラのエラーハンドリングを修正
修正: #51284
Active Storageに2つあるプロキシコントローラは、どちらもストリーミングより前のタイミングでヘッダーのキャッシュを設定する。
場合によっては(#51284を参照)、(ストレージサービスからActive Storageへの)ファイルダウンロードが、クライアントに最初のバイトを送信する前に(レスポンスがまだコミットされていない)ファイルのダウンロードが失敗する可能性がある。
この変更によって、そのような場合はキャッシュが無効になるので、より適切なレスポンスステータスを返してからストリームを閉じるようになる。
同PRより
つっつきボイス:「ダウンロードエラー時にFileNotFoundError
になったときはexpires_now
で強制的にサーバー側のヘッダーキャッシュを失効させるようにしたんですね↓」
# activestorage/app/controllers/concerns/active_storage/streaming.rb#L56
def send_blob_stream(blob, disposition: nil) # :doc:
send_stream(
filename: blob.filename.sanitized,
disposition: blob.forced_disposition_for_serving || disposition || DEFAULT_BLOB_STREAMING_DISPOSITION,
type: blob.content_type_for_serving) do |stream|
blob.download do |chunk|
stream.write chunk
end
+ rescue ActiveStorage::FileNotFoundError
+ expires_now
+ head :not_found
+ rescue
+ # Status and caching headers are already set, but not commited.
+ # Change the status to 500 manually.
+ expires_now
+ head :internal_server_error
+ raise
+ end
end
参考: Rails API expires_now
-- ActionController::ConditionalGet
🔗 minitestのアサーション失敗メッセージを遅延実行して高速化
関連: #52036
生成するオブジェクトが非常に大きい場合や、procのAST(抽象構文木)にアクセスするときに、生成コストが高くなる場合が生じている。
minitestはこのメッセージを呼び出し可能オブジェクトとして渡すことをサポートしているので、これらすべての計算を遅延実行できる。
同PRより
つっつきボイス:「やってることはアサーションのメッセージをlambda渡しで遅延生成するように変えるというシンプルなもの↓」「lambdaをメモ化するようにしたのね」「地味だけど、たしかに実行時評価にする方が速くなりますね👍」
# activesupport/lib/active_support/testing/assertions.rb#L21
def assert_not(object, message = nil)
- message ||= "Expected #{mu_pp(object)} to be nil or false"
+ message ||= -> { "Expected #{mu_pp(object)} to be nil or false" }
assert !object, message
end
🔗 RuboCop対応2件
フィールドが存在しない場合:
t.timestamps
より前のマイグレーションに空行が入らないようにする- コントローラの
create
やupdate
やAPI機能テストで、空ハッシュ内の前後にスペースが入らないようにする修正: #52158
この変更は7-2-stableにバックポートすべき。
同PRより
# activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt#L15
-<% if options[:timestamps] %>
+<% unless attributes.empty? -%>
+
+<% end -%>
+<% if options[:timestamps] -%>
t.timestamps
<% end -%>
end
# railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb.tt#L18
test "should create <%= singular_table_name %>" do
assert_difference("<%= class_name %>.count") do
- post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_string} }" %> }, as: :json
+ post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: #{attributes_string}" %> }, as: :json
end
- 配列要素の前後の余分なスペースを修正
- コードの新しいブロック間にのみ空行を置くよう修正
同PRより
つっつきボイス:「どちらもscaffoldで生成したコードがRuboCopに怒られたので修正」「あるあるですね😆」「こういう地味な修正の積み重ねが使い勝手に影響してきますよね」
# railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb.tt#L4
<% module_namespacing do -%>
class <%= class_name %>MailerTest < ActionMailer::TestCase
-<% actions.each do |action| -%>
+<% actions.each_with_index do |action, index| -%>
+<% if index != 0 -%>
+
+<% end -%>
test "<%= action %>" do
mail = <%= class_name %>Mailer.<%= action %>
assert_equal <%= action.to_s.humanize.inspect %>, mail.subject
- assert_equal ["to@example.org"], mail.to
- assert_equal ["from@example.com"], mail.from
+ assert_equal [ "to@example.org" ], mail.to
+ assert_equal [ "from@example.com" ], mail.from
assert_match "Hi", mail.body.encoded
end
<% end -%>
🔗 親クラスで定義されたメソッドをalias_attribute
がオーバーライドしないよう修正
修正: #52144
通常の属性を定義する場合、継承したメソッドはオーバーライドされないが、エイリアス属性を定義する場合は、継承されたメソッドのことが考慮されていなかった。
この振る舞いは議論の余地があるものの、#52118より前の振る舞いであるため、復元することにする。
同PRより
つっつきボイス:「alias_attribute
の場合に継承で親に既にある同名のメソッドがオーバーライドされていたのでActive Modelの方でoverride: false
を追加して修正したのね↓」
# activemodel/lib/active_model/attribute_methods.rb#L315
- def define_attribute_method_pattern(pattern, attr_name, owner:, as:) # :nodoc:
+ def define_attribute_method_pattern(pattern, attr_name, owner:, as:, override: false) # :nodoc:
canonical_method_name = pattern.method_name(attr_name)
public_method_name = pattern.method_name(as)
「テストコードを見ると、親クラスで属性ではなくメソッドとして定義されている場合に問題が起きていたということみたい↓」「あ、親クラスがメソッドで子クラスがalias_attribute
の場合なんですね」「属性とメソッドは、呼び出し側だけを見ていても属性かメソッドか区別できないことがあるけど、内部的には別モノというのがややこしい」
test "#alias_attribute override methods defined in parent models" do
parent_model = Class.new(ActiveRecord::Base) do
self.abstract_class = true
def subject
"Abstract Subject"
end
end
subclass = Class.new(parent_model) do
self.table_name = "topics"
alias_attribute :subject, :title
end
obj = subclass.new
obj.title = "hey"
assert_equal("hey", obj.subject)
end
🔗 キャッシュコントロールのimmutable
をサポート
- Cache-Controlで
immutable
ディレクティブをサポートexpires_in 1.minute, public: true, immutable: true # Cache-Control: public, max-age=60, immutable
heka1024
同Changelogより
つっつきボイス:「immutable
っていうのがあるんですね」
参考: immutable
-- Cache-Control - HTTP | MDN
Cache-Control
は HTTP のヘッダーフィールドで、 キャッシュをブラウザーや共有キャッシュ(プロキシーや CDN など)において制御するためのディレクティブ (指示) を、リクエストとレスポンスの両方で保持します。
(中略)
immutable
レスポンスディレクティブのimmutable
は、レスポンスが新鮮な間は更新されないことを示します。Cache-Control: public, max-age=604800, immutable
「ところで"最近のベストプラクティス"↓って公式ドキュメントらしくない書き方でちょっと面白い😆」「最近とは何ぞや😆」「それはともかく、ここでcache-bustingと呼んでいるパターンは実際よく使うので、Railsで使えるのはいい👍」
静的なリソースに対する最近のベストプラクティスは、バージョン/ハッシュをURLに含める一方で、リソースには決して手を加えず必要なときに、新しいバージョン番号/ハッシュを持つ新しいバージョンでリソースを更新し、URLも異なるものにすることです。これをcache-bustingパターンと呼びます。
immutable
-- Cache-Control - HTTP | MDNより
🔗 locals:
マジックコメントを使うとlocal_assigns
が未定義になる場合があったのを修正
クローズ: #52162
ローカル変数の1つがキーワードと衝突する場合(
class
が典型)。(デフォルト値が定義されている場合は
local_assigns
に反映されないのが紛らわしそう)cc: @bensheldon @rafaelfranca
同PRより
つっつきボイス:「この修正は、少し前に入った、テンプレートに渡していいローカル変数をマジックコメントで指定できる機能↓に関連しているようです(ウォッチ20220822)」「あぁ、Action Viewのlocals:
マジックコメントですね、これは手軽に使えるいい機能👍」
<%# issues/_card.html.erb %>
<%# locals: (title: "Default title", comment_count: 0) %>
<h2><%= title %></h2>
<span class="comment-count"><%= comment_count %></span>
「そのlocals:
マジックコメントでclass
のようなキーワードと同じものを指定したときにlocal_assigns
が未定義になっていたのが修正されたのね↓」「割とレアケースですね」
def test_rails_local_assigns_and_strict_locals
@template = new_template("<%# locals: (class: ) -%>\n<%= local_assigns[:class] %>")
assert_equal "some-class", render(class: "some-class", implicit_locals: %i[message])
end
上の修正に伴ってドキュメントも更新されました↓。
local_assigns
メソッドには、locals:
マジックコメントで指定されたデフォルト値は含まれません。
class
やif
のようにRubyの予約語と同じ名前のデフォルト値を持つローカル変数にアクセスするには、以下のようにbinding.local_variable_get
で値にアクセスできます。<%# locals: (class: "message") %> <div class="<%= binding.local_variable_get(:class) %>">...</div>
🔗 devcontainer生成を修正
動機/背景
現在の
devcontainer
コマンドはアダプタ名を設定するが、DevcontainerGenerator
でデータベース名が必要。rails/railties/lib/rails/generators/rails/devcontainer/devcontainer_generator.rb
class_option :database, enum: Database::DATABASES, type: :string, default: "sqlite3",
rails/railties/lib/rails/generators/database.rb
DATABASES = %w( mysql trilogy postgresql sqlite3 )
そのせいでMySQL用の設定が生成されていなかった。この修正によって正しい設定が生成されるようになる。
同PRより
つっつきボイス:「devcontainerがMySQLに対応していなかったのをシンプルに修正したものですね↓」
# railties/lib/rails/commands/devcontainer/devcontainer_command.rb#L22
def devcontainer_options
@devcontainer_options ||= {
app_name: Rails.application.railtie_name.chomp("_application"),
- database: !!defined?(ActiveRecord) && ActiveRecord::Base.connection_db_config.adapter,
+ database: !!defined?(ActiveRecord) && database,
active_storage: !!defined?(ActiveStorage),
redis: !!(defined?(ActionCable) || defined?(ActiveJob)),
system_test: File.exist?("test/application_system_test_case.rb"),
node: File.exist?(".node-version"),
}
end
+ def database
+ adapter = ActiveRecord::Base.connection_db_config.adapter
+ adapter == "mysql2" ? "mysql" : adapter
+ end
end
🔗 RDoc生成でinclude
が遅くなっていたのを修正
CIでは8時間かかった。自分のPCでは3時間経過したところでキャンセルした。
rdocのこのコメントが関連しているのではないかと疑っている(ここで詰まっていたので)。
include
されるものをいくつか削除したところ、やっとまともな時間で終わるようになった。なお、どれを削除するかは関係なさそう。この問題のきっかけになったのは#52185だった。
@vinistockへ: ドキュメントのこのコメントによってruby-lsp
がこれを無視するようになり、最初にそちらのプルリクで行ったことが基本的に取り消されるかどうか自分にはわからない(これがどう動くかについてこれまで考えたことがなかった)。この修正でよければ知らせて欲しい。これはおそらくrdocのどこかにあるバグではないか。しかし単に
include
件数が多いせいだとは思えないし、比較的起きやすいように思える。
同PRより
つっつきボイス:「ruby-lspによるメソッド名補完が効くようにするために#52185でモジュールを明示的にinclude
するようにしたら、どうやらそのせいでinclude
でRDocが生成されるようになってめちゃくちゃ遅くなってしまったのか」
▶#52185(クリックで展開)
動機/背景
コントローラのアクション内でメソッド補完が効かない(正確な提案を表示できるはずの状況であっても)という報告をRuby LSPのユーザーたちからいくつか受け取った。補完、定義ジャンプ、シグネチャのヘルプなどの機能が使えない理由は、すべてのモジュールが
ActionController::Base
に動的にinclude
されており、静的分析で先祖をキャプチャできなくなるため。Base
自体は実際には何も定義されていないので、静的分析で先祖を分析できなければ、その中で定義されているメソッド、定義、インスタンス変数を識別できなくなる。@rafaelfrancaとのチャットで、「ここで動的
include
を利用している理由は、MODULES
の除外可能なリストが常に同期されるためだが、他の方法で保証できるなら明示的なinclude
に変更してもよい」と説明を受けた。このプルリクでは、
include
されたモジュールのリストが、MODULES
の除外可能なリストと常に一致することを確認するための、簡単な単体テストを追加することを提案している。それと引き換えに、小さな不便が生じる。つまり、ActionController::Base
に新しいモジュールをinclude
する場合は、MODULES
のリストと明示的なinclude
の両方に追加しなければならなくなる。ただし、このクラスに
include
の項目を毎日のように追加する可能性は低いので、このトレードオフは許容可能だと考えている。メリットは、Ruby LSPのユーザー(および他の静的解析ツールのユーザー)がコントローラ内の機能を正確に取得可能になること(現在はそうなっていない)。詳細
MODULES
の配列をループで回して項目を動的にinclude
する代わりに、個別のinclude
項目を明示的なものに変更した。自分が追加したテストは、
base.rb
ファイルを読み込んで、すべてのinclude
項目を正規表現で探索し、MODULES
配列で期待されるものと一致することを確認する形で同期がずれないようにする。メモ
先祖チェックを追加しなかった理由:
単体テストを追加する方法の代わりに、モジュールの追加前と追加後で先祖がどう変わるかをチェックする形で実現を試みた。しかし、
MODULES
にあるモジュールには、このリストでinclude
されていない独自の先祖があるため、一致しない。
以下は自分が調べてみたアイデア:class Base # ... original_ancestors = ancestors include AbstractController::Rendering # ... # ここにincludeされるのは、現在MODULESリストに存在する項目だけではなく、 # 推移的な先祖(リスト内のモジュールによってincludeされるモジュール)もincludeされる # このリストを動的に生成する適切な方法を思いつかなかった MODULES = ancestors - original_ancestors end
同PRより
「修正そのものは:stopdoc:
と:startdoc:
でRDoc生成を抑制するというシンプルなものですね↓」「こんなニッチな原因を特定したのがすごい」
# actionpack/lib/action_controller/base.rb#L269
+ # Note: Documenting these severely degrates the performance of rdoc
+ # :stopdoc:
include AbstractController::Rendering
include AbstractController::Translation
include AbstractController::AssetPaths
include Helpers
include UrlFor
...
# Params wrapper should come before instrumentation so they are properly showed
# in logs
include ParamsWrapper
+ # :startdoc:
setup_renderer!
参考: library rdoc
(Ruby 3.3 リファレンスマニュアル)
🔗 ActiveStorage::Service::MirrorService
のパフォーマンスを改善
動機/背景
このプルリクを作成した理由は、
ActiveStorage::Service::MirrorService
のFIXME
コメントを削除するため。詳細
このプルリクは、
ActiveStorage::Service::MirrorService
のdelete
とdelete_prefixed
をパラレル化するためにスレッドプールを利用する。追加情報
この修正で複雑になるだけでパフォーマンスが向上しないのであれば、単にコメントを削除してはどうだろう。
同PRより
つっつきボイス:「concurrent-rubyのThreadPoolExecutor
を使うようにしたんですね↓」「単にeach_service.collect
で回すよりも、こうする方が特にダウンロードに時間がかかる場合のパフォーマンスがよくなるでしょうね👍」「なるほど」「特にこういうミラーリングサービスで別リージョンにバックアップすると通常より遅くなりがちなのでパラレル化が効きそう」
# activestorage/lib/active_storage/service/mirror_service.rb#L31
def initialize(primary:, mirrors:)
@primary, @mirrors = primary, mirrors
+ @executor = Concurrent::ThreadPoolExecutor.new(
+ min_threads: 1,
+ max_threads: mirrors.size,
+ max_queue: 0,
+ fallback_policy: :caller_runs,
+ idle_time: 60
+ )
end
...
def perform_across_services(method, *args)
- # FIXME: Convert to be threaded
- each_service.collect do |service|
- service.public_send method, *args
+ tasks = each_service.collect do |service|
+ Concurrent::Promise.execute(executor: @executor) do
+ service.public_send method, *args
+ end
end
+ tasks.each(&:value!)
end
end
🔗 inspect
で余分な情報が漏出しないようカスタム定義
- PR: Add condensed #inspect for Pool, Adapter, Config by skipkayhil · Pull Request #50405 · rails/rails
動機/背景
修正前は、ConnectionPoolや個別のコネクション(
Adapters
)でinspect
を呼び出したときにエラーになると、production環境でデータベースのパスワードが誤って漏洩してしまいやすかった。これは、Pools
やAdapters
の#inspect
がデフォルトで出力するものが不必要に大きく、現在はそこにパスワードが含まれているため(Pool
のDatabaseConfig
やAdapter
の内部コンフィグ経由で)。詳細
このコミットは、
ConnectionPool
、AbstractAdapter
、DatabaseConfig
に#inspect
をカスタム定義することで問題に対処する。スリムになった#inspect
には有用なフィールドが数個しか含まれなくなり、すべての内部フィールドを含まなくなったので、巨大な出力やパスワードが含まれなくなった。
同PRより
つっつきボイス:「コネクションプールやアダプタにはいろんな情報がぎっしり詰まっているから、inspect
をオーバーライドして出力をスリム化したんですね」「たしかにinspect
は思わぬものが出力される可能性もあるので、こういう修正はありがたい👍」
🔗 Rakeタスクの一部をThorに移行
現在の
bin/rails
ではThorとRakeが両方使われているが、最終的にはすべての組み込みタスクをThorコマンドに昇格させたいと考えている。これにより、stats
タスクがThorに移行する。
同PRより
つっつきボイス:「Railsのrakeタスクを長期的にthorに置き換えようとしているみたいですね」「たしかにrakeは実行が遅いし」「ヘルプ表示も使いにくいし」「rakeが特に使いにくいのは、Railsとかなり違うコンテキストで動作すること: rakeで書くよりRailsランナーとして書く方がずっと使いやすいですね」「そうそう」
参考: 2.5 bin/rails runner
-- コマンドラインツール - Railsガイド
前編は以上です。
バックナンバー(2024年度第2四半期)
- 20240625前編 Active Recordにstrict_loading_mode追加、to_time_preserves_timezoneの扱いほか
- 20240620後編 Ruby on Jets 6.0がRailsをサポートほか
- 20240619前編 Rails 8からPropshaftがアセットパイプラインのデフォルトにほか
- 20240529 Rails 8でKamalがデフォルトのデプロイツールになるほか
- 20240514後編 Ruby/Railsのアップグレード情報をscrapboxに集約ほか
- 20240513前編 Railsコンソールが最新のIRB APIに移行、assertionless_tests_behaviorほか
- 20240426後編 Prismの歴史と現況を振り返る、Steepの"narrowing"実装の内部ドキュメントほか
- 20240425前編 RailsからOpenStructを削除、Playwrightベストプラクティスほか
- 20240423後編 Kamalはゲームチェンジャーになるか、Solid Queueで使われているfugitほか
- 20240416前編 ジョブのエンキューをトランザクション完了時まで自動先延ばしほか
- 20240410後編 SeleniumでRubyの全クラスとモジュールにRBSが追加ほか
- 20240409前編 Rails公式の"rails-new"ツールでRailsプロジェクトをセットアップほか
- 20240402 solid_queueとmission_control-jobsが正式にRailsのgemに、Rubyの"チルド"文字列ほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)