- Ruby / Rails関連
週刊Railsウォッチ(20191111前編)Active Recordモデルをprivateで封じ込める、心折れないRailsスキーマ管理、Railsセッションをクロスドメイン共有ほか
こんにちは、hachi8833です。Rails 6.0.1が先週リリースされましたね🎉。
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください
⚓お知らせ: 週刊Railsウォッチ「第16回公開つっつき会」(無料)
第16回目公開つっつき会は、来週11月14日(木)19:30〜にBPS会議スペースにて開催されます。今回は月初ではありませんのでご注意ください。
週刊Railsウォッチの記事にいち早く触れられるチャンス!発言・質問も自由です。引き続き皆さまのお気軽なご参加をお待ちしております🙇。
⚓Rails: 先週の改修(Rails公式ニュースより)
公式情報を中心に見繕いました。
⚓マルチDBのマイグレーション後に同じデータベースに再接続するよう修正
標準的なマルチDBセットアップで、2番目のデータベースがレプリカでなく、独立したテーブルセットを持っている状態で以下の非常にシンプルなタスクがあり、
rails db:migrate foo
を実行したとする。
task foo: :environment do
puts User.last # or some model with a table that only exists in the primary db
end
実際の振る舞い:
establish_connection
がマイグレーションタスクごとに実行されるため、プライマリではなく直前のマイグレーション対象データベースに対してクエリが実行される。
期待される振る舞い: マイグレーションタスクがクリーンアップされ、実行後プライマリ・データベースに再接続される。
#37578より大意
つっつきボイス:「Rails 6のマルチDBがらみの修正ですね」「お、rakeタスクですか」「コネクションをoriginal_config
に保存しておいて、終わったらそれを復元しているんですね↓」「コネクションが1つだったら起きなかった問題っぽい」「本番で知らずに切り替わってたらびっくりして目を疑っちゃいそう😇」「実際に使わないと見つけにくそうなバグですね☺️」
# activerecord/lib/active_record/railties/databases.rake#L81
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
task migrate: :load_config do
+ original_config = ActiveRecord::Base.connection_config
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config)
ActiveRecord::Tasks::DatabaseTasks.migrate
end
db_namespace["_dump"].invoke
+ ensure
+ ActiveRecord::Base.establish_connection(original_config)
end
⚓ローカルキャッシュを改変するときのバグを修正
このテストケースがバグをある意味正しく示してくれると思う。手短に言うと、戻り値の改変からは既に保護されていたにもかかわらず、元の値の改変から保護されていなかった。
同PRより大意
# 同PRより
my_string = "foo"
cache.write('key', my_string)
my_string << "bar"
cache.read('key') # => "foobar"
つっつきボイス:「上のコード例を見る方が早いと思うんですけど、キャッシュの値に入れた元の値を改変するとキャッシュの値まで変わっちゃってるという😳」「これはアカンやつ〜😆」「dupしないでそのまんまキャッシュに入っちゃってたか😇」「それを防ぐためにdup_value!
を追加したんですね」
# activesupport/lib/active_support/cache/strategy/local_cache.rb#L60
- def write_entry(key, value, **options)
- @data[key] = value
+ def write_entry(key, entry, **options)
+ entry.dup_value!
+ @data[key] = entry
true
end
「これに限らずhashやarrayで割とよくあるバグですね☺️」「これはあるある」「単純に@data[key] = value
したら、valueが変わるとキャッシュの値まで変わっちゃう😇」「誰も書き換えなければ問題ないんですけど、キャッシュだから書き換えあるでしょうし😆」「よけてたはずなのにどして?ってなったり😆」「ぱっと見正しそうなだけに見落としそう😅」
「まあ誰しもやりそうなバグですから☺️」「でもやったらアカン😆」「キャッシュに入れたmy_string
を後生大事に使い回すのがそもそもよくなかったという考え方もあるかも😆」「修正でdup入ったから微妙に遅くなるでしょうね😆」
「#37587の場合は同じKeyに対してread / write処理を書いて、かつ元のオブジェクトが参照できるときにしか発生しないので、『同一リクエスト内で同じオブジェクトをwrite / readした』『クラス変数などのリクエストをまたいでメモリにデータを保持する変数でwrite / readした』とかのケースぐらいで、割とレアな気はしますね☺️」「おぉ」
「こういうバグが起きにくい言語仕様ってあるのかなって、ついそっちを考えちゃいます😅」「全部値渡しにしたらデカいオブジェクトのコピーが半端ないコストになりますよ😆」「参照で渡したいときとコピーしたいときとありますからね〜☺️」「C言語知ってる人にならRubyは基本的にポインタ渡しだよって説明できますけど」「若い人だとポインタ知らなさそう😆」
参考: ポインタ (プログラミング) - Wikipedia
なおdup_value!
はActiveSupport::Cache::Entryにありますが、なぜかapi.rubyonrails.orgで出てこなかったのでAPIdockを貼ります。
参考: dup_value!
(ActiveSupport::Cache::Entry) - APIdock
⚓インラインジョブを別スレッドで実行できるようになった
# activejob/lib/active_job/queue_adapters/inline_adapter.rb#L13
class InlineAdapter
def enqueue(job) #:nodoc:
- Base.execute(job.serialize)
+ Thread.new { Base.execute(job.serialize) }.join
end
def enqueue_at(*) #:nodoc:
raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at https://guides.rubyonrails.org/active_job_basics.html"
end
end
つっつきボイス:「インラインジョブを別スレッドで実行できるようにしたそうです」「今までは別スレッドにできなかったと😳」「Thread.new
でジョブを実行してからjoin
してますね」「enqueue
だし、もしかするとjob.serialize
が重いのかも🤔」「join
するということはジョブの完了を待つってことなのかな?」「issueの方を見るとよさそう↓」
「これはThread.new
した中でexecuteさせることで直ちにenqueueした処理を解放させて、次のenqueue 待ちをなるべくゼロにしたい、ということだと思います」「おぉ」「これはOSの割り込みハンドラによるコンテキストスイッチ実装とかでもよくある実装方針で、割り込みハンドラはなるべく限界まで小さくする、というやつですね☺️(割り込みハンドラの処理中は他の割り込みハンドラを受けられなくなってしまうので、その時間は最小限にするために、割り込みハンドラでは即queueに処理イベントを積むだけ積んでハンドラを脱出する)」「なるほど!」「多分、micro jobが大量に実行されるようなユースケースになってくると、ジョブのスループットに影響が出てくるんだと思われます」
「あ、issueに例のCurrentAttributes
↓が登場してます😆」「憎っくきCurrentAttributes
😆」「これってグローバルステートだからスレッドからこちょこちょするときに気を付けないといけないんでしょうね☺️」「スレッド周り難しくてよくわかんないけど、上の修正は何となくworkaroundっぽい雰囲気🤔」「この修正で切り抜けられるならまあいいのかなと☺️」「スレッド生成してもコストはそんなに変わらなさそうではある🤔」
「上はDHHが入れたCurrentAttributes
に反対してた人の記事を翻訳したもので、CurrentAttributes
はやりすぎだという主張ですね」「そもそもグローバルステートですし😆」「記事の人はどちらかというと設計として好きになれないみたいです」「わかる😆」「一応CurrentAttributes
にはスレッドローカルな変数もあるみたいですけど、それが回り回って今回のissueにつながったんだとしたら何となくわかる気がする☺️」「グローバルステートならスレッドセーフであって欲しいですよね」
「こういうCurrentAttributes
的な機能って、わかってて使う人にはとっても有用なんですよ😆」「あ〜それはある意味難しい問題😅」「そしてよくわかってない人が飛びつくと詰む、みたいな害の方が大きくなったりしがち😆」「共有情報をスレッドに乗せるみたいなコードをJavaで見たことはあるので、CurrentAttributes
がまったくナンセンスということはないんじゃないかとは思いますね☺️」「使う人を選ぶ機能😆」
⚓GitHub Actionsに対応
- PR: Cache gems for GitHub Actions by yahonda · Pull Request #37612 · rails/rails
- PR: Enabled GitHub Actions to run the latest RuboCop 0.76.0 by yahonda · Pull Request #37582 · rails/rails
# .github/workflows/rubocop.yml
name: RuboCop
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Ruby 2.6
uses: actions/setup-ruby@v1
with:
ruby-version: 2.6.x
- name: Install required package
run: |
sudo apt-get install libmysqlclient-dev libpq-dev libsqlite3-dev libncurses5-dev
+ - name: Cache gems
+ uses: actions/cache@preview
+ with:
+ path: vendor/bundle
+ key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-gem-
- name: Build and run RuboCop
run: |
+ bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
bundle exec rubocop --parallel
つっつきボイス:「これはGitHub向けの改修ですね」「gemのキャッシュと最新のRuboCopに対応」「GitHubがRailsでできてるのは有名ですよね😋」「GitHub Actionsはウォッチでも何度か取り上げましたけど(ウォッチ20190925)結構期待できそう😍」
「rubocop --parallel
って知らなかった😳」「お、これは欲しいかも😋」「Rubocopちゃんで大量修正が必要なプロジェクトなんかでも、1ファイルごとにやれればいいからparallelにすれば速くなりますね😍」「どうせRSpecの方が断然遅いからまあ別にって感じですけど🤣」「🤣」
参考: rubocop/basic_usage.md at master · rubocop-hq/rubocop
参考: Rubocop を使っているアナタがするべき2つのこと - Qiita
⚓ActiveStorage blobからrequire_dependency
を排除
# activestorage/app/models/active_storage/blob.rb#L17
class ActiveStorage::Blob < ActiveRecord::Base
- require_dependency "active_storage/blob/analyzable"
- require_dependency "active_storage/blob/identifiable"
- require_dependency "active_storage/blob/representable"
+ unless Rails.autoloaders.zeitwerk_enabled?
+ require_dependency "active_storage/blob/analyzable"
+ require_dependency "active_storage/blob/identifiable"
+ require_dependency "active_storage/blob/representable"
end
つっつきボイス:「ZeitwerkがRails 6で殺したrequire_dependency
がActiveStorage::Blobにちょっぴり残ってたので排除したという小さい修正です」「サーチアンドデストロイ💀」「一応Zeitwerkなしでもやれるようにしないといけませんし☺️」「これで殺戮完了したのかな?」「どうでしょう😆」
参考: 定数の自動読み込みと再読み込み (Classic) - Rails ガイド
「require_dependency
はRailsで独自に作ったメソッドでしたか〜」「以前のウォッチでも名前空間地獄の話題でrequire_dependency
の話になりましたね(ウォッチ20181022)」「Zeitwerkになってrequire_dependency
が不要になったというかむしろ完全排除される方向に」
⚓ActiveRecord::Baseから不要なrequire
を削除
# activerecord/lib/active_record/base.rb#L
-require "yaml"
require "active_support/benchmarkable"
require "active_support/dependencies"
require "active_support/descendants_tracker"
require "active_support/time"
-require "active_support/core_ext/module/attribute_accessors"
-require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/hash/deep_merge"
-require "active_support/core_ext/hash/slice"
-require "active_support/core_ext/string/behavior"
-require "active_support/core_ext/kernel/singleton_class"
-require "active_support/core_ext/module/introspection"
require "active_support/core_ext/class/subclasses"
require "active_record/attribute_decorators"
require "active_record/define_callbacks"
require "active_record/log_subscriber"
require "active_record/explain_subscriber"
require "active_record/relation/delegation"
require "active_record/attributes"
require "active_record/type_caster"
require "active_record/database_configurations"
つっつきボイス:「Active Record::Baseに要らないrequire
が結構残ってたのが削除されてました」「Baseから消えるってなかなかスゴい😆」「core extensionあたりのrequire
はいつの間にか冗長になってたんだろうな〜」「あ、require
しなくてもautoload
でモジュールが自動読み込みされるようになってたのか☺️」「なるほどね!」
# b2c9ce3より
# activerecord/lib/active_record.rb#58
autoload :Base
autoload :Callbacks
+ autoload :Core
autoload :CounterCache
autoload :DynamicMatchers
autoload :DynamicFinderMatch
参考: module function Kernel.#autoload
(Ruby 2.6.0)
「require
って雑に増えていきがちだからコワくてなかなか消せないのが大変😆」「いつかは消さないといけないんでしょうけど」「かぶっててもfalseが返るだけで害はないのでなかなか消されなさそう😆」「このたびfalseになることが確定したんでしょうね☺️」
⚓Rails
⚓Active Recordモデルをprivateにして大人しくさせてやった(Ruby Weeklyより)
つっつきボイス:「tameは『飼いならす』ですね😆」「モチベの説明が長いので後で追ってみます」
「Account.public_methods.size
でメソッドが685個出てきた😆」「むちゃくちゃや😆」「これは死にたくなる😇」
「これがコンセプトみたいです↓」「むむむ...?Account
クラスを複数形のAccounts
モジュールにして、Accounts::Model
でnew
してからテーブル名だけ指定し、そしてprivate_constant :Model
でモデルをprivateにした...だと..?うはぁ〜!」「ウケた😆、喜びですか驚きですかあきれてますか?」「いや〜、これは面白い!!🎉」
# 同記事より
module Accounts
# Some important stuff up here, which will get to in a bit
class Model < ApplicationRecord
self.table_name = 'accounts'
end
private_constant :Model
# Some important stuff down here, which will get to in a bit
end
「これは最近ActiveModel絡みでよく話している、永続化層切り離しですね〜❤️」「おぉ」「上のように書くことでActiveRecordのメソッドのほとんどを殺すことができる: そして以下みたいに欲しいメソッドだけself.fetch
とかself.create
みたいに書いて単にモデルにdelegateして、かつそのモデルを返している」「いわゆる委譲ですね」
「Model
モデルはprivateなのでAccounts
モジュールの中なら見えるけど外部からはシャットアウトされる」「Accounts::Model
で外からいじるんじゃねーぞ、と😆」「これ確かに面白〜い!😋」「Rails wayじゃありませんけどね😆」
# 同記事より
# app/models/accounts.rb
module Accounts
def self.fetch(id:)
Model.find(id)
end
def self.create(name:)
Model.create!(name: name)
end
class Model < ApplicationRecord
self.table_name = 'accounts'
end
private_constant :Model
end
「オレが委譲で許した以外の方法でモデルにアクセスするなよと😆」「返すものがモデルじゃなくてリレーションになるとまたちょっと微妙な話になるんですけど☺️」
「その発展型がこれか↓」「お、最後のAccount
は普通のPORO(Pure Old Ruby Object)で、fetch
やcreate
が今度はModel
じゃなくてカスタムのAccount
クラスを返すようにしたと、ほほぉ〜これはたぶんwhere
みたいなリレーションを相手にしたくないと言ってそう😋」
# 同記事より
# app/models/accounts.rb
module Accounts
# --- Public APIs
def self.fetch(id:)
db_object = Model.find(id)
Account.new(
id: db_object.id,
name: db_object.name,
)
end
def self.create(name:)
db_object = Model.create!(name: name)
Account.new(
id: db_object.id,
name: db_object.name,
)
end
# --- Private ActiveRecord model
class Model < ApplicationRecord
self.table_name = 'accounts'
end
private_constant :Model
# --- Entity for the outside world
class Account
attr_reader :id, :name
def initialize(id:, name:)
@id = id
@name = name
end
end
end
「さらにトランザクションもこの形↓でやってるし😳」「業務コードらしくなってきた😋」
module Accounts
def add_seat(id:)
Model.transaction do
db_object = Model.find(id)
db_object.number_of_licenses += 1
db.object.save!
if db_object.number_of_licenses == 5
SalesNotification.create!(account_id: id)
end
end
end
class SalesNotification
belongs_to :account
end
private_constant :SalesNotification
# Rest of implementation...
end
「まあこのパターンを既存のRailsアプリでいきなりやるのは無理あるのでそれはおいとくとして、今後はこういうふうに作ってみいやという感じかな〜😆」「自分には、ある意味カプセル化の基本に立ち返ったように見えますね☺️」「そう!ちゃんとカプセル化してる」「これを実際にやるかどうかは別としても、ちょっと新鮮ですね😍」
「一応記事の末尾にもいろいろ書いてますね: これはRailsのデフォルトのパターンじゃないし、どのActive Recordモデルに適用できるとも限らないと」「そりゃそうだ😆」「機が熟すまでこの設計には飛びつかない方がいいということみたいですね☺️」「最初からこう書いていれば無駄なメソッドが600個も生えてこなくて済むでしょうけど😆」
「元々Active Recordが継承でやるように作られちゃってるからなんでしょうけど😢」「まあそれはあるかも☺️」「この記事を書いた人は、たぶんデータベースに直接触らせたくないマン😆」
「このパターンでやれそうな例として『Account
とUser
をいつも同時に変更しているなら、同じモジュールに入れるべきじゃね?』と思えたときが挙げられてますね」「あ〜わかる!離れているモデルをいつも同時に扱ってわけわからなくなるぐらいだったらモジュールに閉じ込めてprivateにしちまえと😆」
「いわゆるPoEAA↓のActiveRecordパターンからきちんとビジネスオブジェクトを切り離す前段階としては悪くなさそうですね: テストコードがあればとりあえず不用意にActiveRecordの呼ばれたくないメソッドを隠蔽できるのはまあ悪くないのかも?(つらそうだけど)」
「それにしても面白いパターンだわ〜😋」「カプセル化としてはとてもキレイではある☺️」「これが実際にうまく当てはまる場合って何だろう?ん〜とん〜と🤔」(以下延々)
以下は記事冒頭の「モチベーション」より:
システムが大きくなったらカプセル化を強化すべきである。私たちはマイクロサービスや何ちゃらRailsエンジンでやりたいのではなく、Rubyの基本機能を少々用いることで実現する。
(中略)
そういうわけでモデリングにおいて防衛的なアプローチを始めた。つまりサポートできるものだけをpublicにしようということだ。これによってモデルの表面積が小さくなってサポートしやすくなるし、用途が絞られることで内部変更もしやすくなる。
(中略)
最後に、ROMやSequelのようにData Accessパターンでこれに近いことをやれるライブラリはいろいろあるものの、それらに完全に乗り換えるのは簡単ではない。おそらく皆さんのアプリは最初からActive Recordを使っているだろう。本記事では、そうした技術への乗り換えが困難なまでに育ったRailsアプリを前提としている。
「データベースはインターフェイスじゃないんだけど!」とつぶやいてる人にはもしかするとこのパターンが向いているかもしれない。
同記事より大意
⚓Railsのセッションをクロスドメインで共有(RubyFlowより)
- 元記事1: Cross domain session sharing in Rails - Part 1
- 元記事2: Cross domain session sharing in Rails - Part 2
つっつきボイス:「1本目はcookieとセッションの基本的な解説で、2本目が本題のようです」「クロスドメインでセッションを共有ぅ〜?」「無茶な😆」
「どうやらこの人たちはapp.kittens.io
とdev.kittens.io
という2つのドメインで認証を共有したいらしいです」「なるほどそっちですか😆」「cookieの仕様でこういうのってやれるんだったかな?🤔」「この記事ではセッションストアをRedisにしてるので、それならやれそう☺️」
「お、このconfig↓でdomain: :all
にするのがポイントらしい😳」「これでドメインが変わってもcookieをよしなに扱えるってこと?」「へぇ〜😳」
# 同記事2より
Rails.application.config.session_store :redis_session_store,
key: '_kittens_session',
serializer: :json,
domain: :all,
redis: {
expire_after: 1.week,
key_prefix: 'kittens:session:',
url: ENV['REDIS_SESSIONS_URL']
}
「この記事みたいにサブドメインの違う複数サーバーでセッションを共有したいことって結構あるんでしょうか?」「サブドメインがwwwとかliveとかloginみたいに分かれてて、loginで認証したら他のサブドメインも見られるようにする、なんてのは普通にやりますね☺️」「SSO↓でやると大げさになっちゃうんでしょうか?」「まあそのためだけにSSOは使わないでしょう😆」「これでやれるならサブドメインを気軽に作れますし☺️」「昔セッション共有やるべきかどうかについて議論になった気がするけど思い出せない😆」
追いかけボイス: 「web書くならにcookieのdomain指定は把握しておいてほしいなあ...大昔にこんなのを書いていました↓」「おぉありがとうございます😂」「まあ今はさらに色々ありますが😆」
⚓Active StorageはRails 6でどう変わったか
つっつきボイス:「記事はActive StorageがRails 6でどう変わったかというまとめで、このSaeloun Blogは最近グロスで翻訳の許可ももらえました😋」
「お、mini_magickが置き換わった?」「そういえばウォッチでもimage_processingというgem↓に置き換わったのを扱ってた覚えが(ウォッチ20180511)」「画像の向きも自動で修正してくれるとか、image_processingよさげ😍」
「次は画像のvariantのサポート」「variantはサイズ違いの画像ですね」「carrierwaveとかでやってたのがActive Storageでもできるようになった☺️」
carrerwave↓のgemspecを見るとimage_processingが入ってますね😋。mini_magickもまだありますが。
「最後がhas_many_attached
」「これだけで書けるのはアツい❤️」「そういえばRails 6で挙動が変更されたんでした(ウォッチ20190729)」「前はattach
してupdate
すると追加されてたのが、Rails 6で更新されるようになって他と挙動を合わせたと」「count
の結果↓違う〜😨」「breaking changeなのでオプションで選べるようになったんでした」
# 同記事より
# Rails 5まで
blog = ActiveStorage::Blob.create_after_upload!(filename: "updated_pic.jpg")
user.update(images: [blog])
user.images.count
=> 2
# Rails 6
blog = ActiveStorage::Blob.create_after_upload!(filename: "updated_pic.jpg")
user.update(images: [blog])
user.images.count
=> 1
⚓心が折れないRailsスキーマ管理
つっつきボイス:「Railsスキーマ管理で心が折れないための方法😆」「冒頭で早速例の『マイグレーションでActive Recordモデルを参照するな』が出てきてますね」「morimorihogeさんも口を酸っぱくして言ってるヤツ(ウォッチ20190415)」「babaさんも昔記事書いてました↓」
「次は使い捨てのスクリプトでデータをインポート」「one-offは『使い捨ての』という意味ですね」「捨てスクリプトを全環境で実行したらもうgitにも登録するなと」「それわかる〜😋」「基本的には残す意味ないヤツ😆」「one-off migrationスクリプトを歴史としてコミット履歴に残すというのは別に悪くはない気もしますね: Wikiとかに書いてもいいんだけど『いつのコードで動かすことを想定していたか』を明らかにするという点ではコミットに挟まっててrevertした履歴があるというのも歴史管理としては一つの戦略だとは思う(これがベストだとは思いませんが)」「おぉ」
「次はどの環境のスキーマを『正』にするかみたいな話」「productionが正に決まっとる😆」「病欠でいない人がスクリプトをローカルで走らせてなくて、しかもスクリプトがもう消されたという状況になったら、production->staging->developmentの順で最新にすると」「考えたくない状況😅」「むか〜しスキーマのインデックス周りが環境ごとにちょっぴりずれてて修復したの思い出した😭」「マイグレーションの定番を押さえるのによさそうな記事ですね😋」
見出しより:
- マイグレーションはスキーマだけを変更する
- seedやデータインポートを使い捨てスクリプトでやる
- pruneを徹底して環境を同期する
- おまけ: いらないマイグレーションファイルを消す
- 上のやり方から離れるべき場合
⚓その他Rails
- イベント: 銀座Rails#15 @リンクアンドモチベーション - connpass -- 今回も出張Railsウォッチ by morimorihogeあります
前編は以上です。
バックナンバー(2019年度第4四半期)
週刊Railsウォッチ(20191106後編)holiday_japan gemで日本の祝日判定、小さい関数が有害になるとき、Gitブランチのファジー検索ほか
- 20191105前編 Rails 6のデフォルト設定解説、DHHも消したいaccepts_nested_attributes_for、スライド『実践Railsアプリケーション設計』ほか
- 20191029後編 Ruby 2.7.0-preview2、tapping_device gemとhumanize gem、平成Ruby会議ほか
- 20191028前編 RailsにSTI用メソッドsti_class_forとpolymorphic_class_forが追加、RuboCopを変更箇所だけにかけるgem、strftime書式生成サイトほか
- 20191021 Rails 6でhas_many関連の修正やSprockets 4.0対応、Shrine 3.0がリリース、Minitestスタイルガイドほか
- 20191015 スライド「Rails Performance issues and Solutions」を見る、dirtyに*_previously_was が追加、Sidekiq 6.0.1ほか
- 20191008後編 Ruby 2.7のInteger#[]でバイナリチェック、rubyzip gemは強力、13KBのJavaScriptゲームほか
- 20191001後編 RedisとRubyをつなぐredis-object gem、Fullstaq Rubyの新バージョン、COUNT(*)とCOUNT(1)の速度ほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp Slackなど)です。