- 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 dbend実際の振る舞い:
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は基本的にポインタ渡しだよって説明できますけど」「若い人だとポインタ知らなさそう😆」
なお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.ymlname: RuboCopon: [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#L17class 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 functionKernel.#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 bitend「これは最近ActiveModel絡みでよく話している、永続化層切り離しですね〜❤️」「おぉ」「上のように書くことでActiveRecordのメソッドのほとんどを殺すことができる: そして以下みたいに欲しいメソッドだけself.fetchとかself.createみたいに書いて単にモデルにdelegateして、かつそのモデルを返している」「いわゆる委譲ですね」
「ModelモデルはprivateなのでAccountsモジュールの中なら見えるけど外部からはシャットアウトされる」「Accounts::Modelで外からいじるんじゃねーぞ、と😆」「これ確かに面白〜い!😋」「Rails wayじゃありませんけどね😆」
# 同記事より# app/models/accounts.rbmodule 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 :Modelend「オレが委譲で許した以外の方法でモデルにアクセスするなよと😆」「返すものがモデルじゃなくてリレーションになるとまたちょっと微妙な話になるんですけど☺️」
「その発展型がこれか↓」「お、最後のAccountは普通のPORO(Pure Old Ruby Object)で、fetchやcreateが今度はModelじゃなくてカスタムのAccountクラスを返すようにしたと、ほほぉ〜これはたぶんwhereみたいなリレーションを相手にしたくないと言ってそう😋」
# 同記事より# app/models/accounts.rbmodule 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 endend「さらにトランザクションもこの形↓でやってるし😳」「業務コードらしくなってきた😋」
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 6blog = 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ウォッチ bymorimorihogeあります
前編は以上です。
バックナンバー(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書式生成サイトほか
- 20191021Rails 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など)です。
Rails公式ニュース
Ruby Weekly
RubyFlow
hachi8833
X:@hachi8833GitHub:@hachi8833コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。これまでにRuby on Rails チュートリアル第2版のコンテンツ監修、Railsガイドのコンテンツ作成を担当。かと思うと、正規表現の粋を尽くした日本語エラーチェックサービスenno.jpを運営。Claude Codeに夢中になりすぎないための方法を模索中。ブログ:note.com/hachi8833、Amazonウィッシュリスト:https://bit.ly/32aAmiI
hachi8833の書いた記事一覧へ
本記事の内容へのお問い合せはTwitterで@techrachoへMentionまたはDMにてご連絡頂くか、運営会社であるBPS株式会社のお問い合せフォームよりお問い合せ下さい。

















