こんにちは、hachi8833です。7年近く肌身離さず使い続けたMacbook Pro 2013 LateのSSDのメインパーティションが、先週金曜日に天に召されました。
- 各記事冒頭にはでパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です
- 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください
お知らせ2件
「出張Railsウォッチ in 銀座Rails」のお知らせ
morimorihogeからのお知らせ:
7/24開催予定の銀座Rails#11 @リンクアンドモチベーションにRailsウォッチが出張予定です。
都合により後半からの参加予定ですが、懇親会枠には間に合いますのでよろしければお声かけください。
明後日24日(水)の銀座Railsにて、弊社技術Blog TechRachoで連載している「週刊Railsウォッチ」出張版の時間をいただきました。僕の予定の都合で後半合流なのですが、Railsウォッチに対するご意見・感想などありましたらぜひ話しかけてください :) https://t.co/TjLDj49Ihz
— Masato Mori (@morimorihoge) July 22, 2019
週刊Railsウォッチ「公開つっつき会」第13回のお知らせ(無料)
1年目を越えた第13回目公開つっつき会は、8月1日(木)19:30〜にBPS会議スペースにて開催されます。皆さまのお気軽なご参加をお待ちしております。
Rails: 先週の改修(Rails公式ニュースより)
database_exists?
をadapterに追加
- PR: Add database_exists? method to connection adapters by itsWill · Pull Request #36471 · rails/rails
# activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L267
+ # Does the database for this adapter exist?
+ def self.database_exists?(config)
+ raise NotImplementedError
+ end
# activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L45
+ def self.database_exists?(config)
+ !!ActiveRecord::Base.mysql2_connection(config)
+ rescue ActiveRecord::NoDatabaseError
+ false
+ end
あえてpublicメソッドにしたそうです。
つっつきボイス:「まああれば使うかな」「MySQL向けかな?」「abstract_adapterに足されてるから全部のadapterに足されますね」「あ、そうか」
「これもマルチプルDB対応でしょうか?」「どうだろう、シングルDBなら最初に接続したときに落ちていればすぐわかるけど、マルチDBだと途中でconnection_adaptersでアクセスしているときに落ちる可能性もあるから、もしかするとこういうので対応するようにしたのかなあ」「とりあえずあっていい機能だと思いますね」「マルチDB環境で、複数の異なる種類のDBに接続するケースの対応を意図しているのかも?: MySQLAdapterとPostgresqlAdapterをそれぞれ接続した場合に、ちゃんとそれぞれのdatabase_exists?
実装で動いて欲しい、的な」
SQLiteは接続時にデータベースが存在しないと、こっそりデータベースを作成する。この振る舞いはアダプタ間で異なるので他の問題を引き起こす。このコミットは
database_exists?
メソッドを追加して、データベースを作成せずにデータベースをチェックできるようにした。問題解決の第一歩としてこれを行った。
同コミットより大意
コネクション間で引用符付きカラムとテーブル名のキャッシュを共有
# activerecord/lib/active_record/connection_adapters/mysql/quoting.rb#L5
module MySQL
module Quoting # :nodoc:
def quote_column_name(name)
- @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
+ self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
end
def quote_table_name(name)
- @quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
+ self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
end
# activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb#L
def quote_table_name(name) # :nodoc:
- @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
+ self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
end
...
def quote_column_name(name) # :nodoc:
- @quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
+ self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
end
つっつきボイス:「quote_column_name
なんてあるんですね」「ありますあります、MySQLだとバッククォート`
でやります」「バッククォートで?!」「ぽすぐれはダブルクォートだったかな、データベースによってこのあたりが微妙に違っているという」「PostgreSQLはUtils.extract_schema_qualified_name
というメソッドが生えてる」
「この改修は、こういうquoted columnsをコネクション間で共有する方がいいよみたいなヤツなのかな」「名前をキャッシュするということ?」「定義は基本的に変わらないものですし」「だからメモ化しようぜと」「コネクションアダプタは複数になってもここは変わらないでしょと」「SQL Standard的にはダブルクォートがカラムのクオート文字↓」
参考: sql - What is the difference between single quotes and double quotes in PostgreSQL? - Stack Overflow
コネクション共有時のクエリキャッシュを修正
# activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L428
def connection
- @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
end
# Returns true if there is an open connection being used for the current thread.
#
# This method only works for connections that have been obtained through
# #connection or #with_connection methods. Connections obtained through
# #checkout will not be detected by #active_connection?
def active_connection?
- @thread_cached_conns[connection_cache_key(Thread.current)]
+ @thread_cached_conns[connection_cache_key(current_thread)]
end
...
+ def current_thread
+ @lock_thread || Thread.current
+ end
# activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L35
def enable_query_cache!
- @query_cache_enabled[connection_cache_key(Thread.current)] = true
+ @query_cache_enabled[connection_cache_key(current_thread)] = true
connection.enable_query_cache! if active_connection?
end
def disable_query_cache!
- @query_cache_enabled.delete connection_cache_key(Thread.current)
+ @query_cache_enabled.delete connection_cache_key(current_thread)
connection.disable_query_cache! if active_connection?
end
def query_cache_enabled
- @query_cache_enabled[connection_cache_key(Thread.current)]
+ @query_cache_enabled[connection_cache_key(current_thread)]
end
end
スレッドをまたがる共有コネクションが有効な場合にクエリキャッシュが正しいコネクションで利くようにした。
同PRより大意
つっつきボイス:「これもコネクションの共有周りか」「スレッドになってる」「current_thread
にまとめたと」「これで詰まったりしないのかな?」「クエリキャッシュだし、そんなに遅くなったりはしなさそうだけど」
relation_exists?
を修正
# activerecord/lib/active_record/relation/finder_methods.rb#L353
def construct_relation_for_exists(conditions)
conditions = sanitize_forbidden_attributes(conditions)
if distinct_value && offset_value
- relation = limit(1)
+ relation = except(:order).limit(1)
else
relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
end
case conditions
when Array, Hash
relation.where!(conditions) unless conditions.empty?
else
relation.where!(primary_key => conditions) unless conditions == :none
end
relation
end
つっつきボイス:「JOINしたテーブルでdistinct
とoffset
とorder
を指定するとrelation.exists?
でエラーになったと」「except
というのは?」「:order
のスコープをあえてexcept
で外していますね」「PostgreSQLでorder
がつくとエラーになってたのか」「えぇ〜」「ぽすぐれだと『使ってないカラムがあるよ』みたいなエラーがよく起きるからそれかも?」「あ〜」「このややこしい組み合わせのときに起きるエラーっぽい」
issue: ActionController::Base
のdeprecation warningが紛らわしい
- issue: Misleading deprecation warning when referencing ActionController::Base · Issue #36546 · rails/rails
DEPRECATION WARNING: Initialization autoloaded the constants ActionText::ContentHelper and ActionText::TagHelper.
Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.
Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload ActionText::ContentHelper, for example,
the expected changes won't be reflected in that stale Module object.
These autoloaded constants have been unloaded.
Please, check the "Autoloading and Reloading Constants" guide for solutions.
(called from <main> at /Users/
USER/Projects/rails-app-test/config/environment.rb:5)
つっつきボイス:「こちらはPRじゃなくてissueです」「出なくていいwarningが出るのね」「warningが鳴ったら気にするけど、あまりに多かったりすると見てられなくなったりしますし」
Rails
Rails 6のエラー表示を目にして思ったこと(Ruby Weeklyより)
つっつきボイス:「Rails 6のエラー画面の嬉しい点と残念な点と見苦しい点はこれとこれ、みたいな記事です」「Rails 6のエラー画面って地道に改良されてますね」「エラーがハイライトされるようになって、トレースもフィルタできるようになって、コンテキスト情報もリッチになって」「でもオートサジェスチョンがうまくいかないことがあるとか」「あとこれ↓が出てもうれしくないとか」「Railsあるある」「Railsやってればどこでも出てきますし」
直接関係ありませんが、記事タイトルの「The Good, The Bad, The Ugly」といえばエンニオ・モリコーネ作曲の一度聴いたら忘れられないこのテーマソング。
その名はRack::CORS
(Ruby Weeklyより)
# 同記事より
config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'example.com', 'localhost:3000'
resource '/publicStuff/*', headers: :any, methods: [:get, :post]
resource '/myStuff/*', headers: :any, methods: :any
end
end
つっつきボイス:「Rack::CORS
、取り上げたことがありそうでなかったので」「Cross-Origin Resource Sharing」「割と細かく設定できそうなgem」
参考: オリジン間リソース共有 (CORS) - HTTP | MDN
「Rack::CORS
は★も2600とかなり多いですけど、どんなときに使うんでしょう?」「APIサーバーとかやるときはこういうのをやらないと動きません」「あ〜」「クロスドメインのリクエストをJavaScriptで投げようとすると、そのままでは制約に引っかかりますね」
「Chromeだったかな、リクエストはかろうじて渡せたけど中身が空っぽになってたときはえぇ〜?!って気持ちになりましたし」「」「むしろ動かずにエラー表示して欲しいんだけど〜」「CORSの話はちょいちょい出てきますね」
参考: Ruby on Rails 5 アプリにあとから API 機能(JWT, CORS 対応)を追加する - 無印吉澤
Rails 6のActive Jobリトライ/discardフック(Ruby Weeklyより)
# 同記事より
class Container::DeleteJob < ActiveJob::Base
retry_on Timeout::Error, wait: 2.seconds, attempts: 3
def perform(container_id)
Container::DeleteService(container_id).process
# Will raise Timeout::Error when the remote API is not responding.
end
end
つっつきボイス:「Rails 6の新機能なんですけど、おそらく今まで取り上げてなかった」「Active Jobのretry
とdiscard
か」「へぇ〜!」「やったことあった気もするけど?」「とりあえず見当たりませんでした…」
「必要ですね」「必要」「discard
とか特に欲しいし」「ね」「ゴミファイルが無限にたまらないようにしたりとか」「むしろほっとくとオートクリーンされちゃうファイルを追いかけるときとか」「たしかにたしかに」
ビューでbyebugを使うコツ(Hacklinesより)
つっつきボイス:「めっちゃ短い記事です」「<%# a = 1; byebug; b = 2; %>
とか書くとbyebugがビューで動くと」「こういう適当な代入文ではさまないとbyebugがするっと通り抜けちゃうらしいです」「そうなんだ!」
「ちょうど今日のWebチームミーティングでRailsのビュー周りの発表がありましたけど、ビューだとプリコンパイルしてメソッド化されてからみたいなことをやってたりするから、そういうのもbyebugに影響したりするのかな?」「そんな予感はしますね」「自分はビューだとbyebugよりも適当にraise
して調べますけどっ」「私はprintデバッグしかしてないけどっ」
Railsでunscoped
しない方法
「unscoped
はしない方がいいっすね〜: マジ事故るから」「そういえば前に翻訳した記事でもそんな主張してました」「自分がデータベースVIEW↓をおすすめする理由がまさにそれ: データベースVIEWならunscoped
してもデフォルトスコープが外れても大丈夫ですし」
Rackミドルウェアで使われているデザインパターンを探る(Ruby Weeklyより)
つっつきボイス:「Rackミドルウェアのデザインパターンか」「この辺のcall
メソッド↓とかはよく言及されますね」「call
メソッドさえ実装すればRackアプリになれるとか」
# 同記事より
class MyApp
def call(request)
[ 200, {"Content-type" => "text/plain"}, ["hello world"]]
end
end
app = MyApp.new
参考: Rails と Rack - Rails ガイド
参考: 10分でわかる Rack ミドルウェアの作り方 | MMMブログ
「なんだかLISPっぽいコードが、と思ったらClojureだった」
(defn wrap-content-type [handler content-type]
(fn [request]
;; call the next handler, then add a header to the response
;; before returning it to our caller
(let [response (handler request)]
(assoc-in response [:headers "Content-Type"] content-type))))
参考: Concepts · ring-clojure/ring Wiki
参考: Clojure - Wikipedia
「たしかにRackはChain of Responsibilityパターンだなぁ」「Chain of ResponsibilityパターンってGoFでしたっけ?」「マルチスレッド編にあったかな?」「(内職中顔を上げて)GoFにあるっ、たしか基本の23パターンにあったと思うし」
参考: Chain of Responsibility パターン - Wikipedia
「Chain of Responsibilityパターンということは、ミドルウェア間のinとoutで示し合わせてチェインできるようにするとそういうヤツでしたっけ」「そうそう、自分が処理できるなら自分のcall
メソッドを使うけど、自分で処理できないならそのまんま次のチェインにリクエストごと渡すと」「後は任せたっと」「もうぽいっと投げる」「で、チェインの最後に何でも食べるヤツを番兵的に置いて引き取ってもらうと」
参考: 番兵 - Wikipedia
「記事にはDecoratorパターンもあるし」「RackはPipelineパターンも使ってるとあるけど聞いたことない…」「それ知らね〜」「少なくともGoFではない」
参考: Decorator パターン - Wikipedia
参考: The Pipeline Pattern — for fun and profit - Aaron Weatherall - Medium
「この辺↓とかたしかにパイプラインっぽいし: 戻ってこないけど」「パイプラインなら戻らなくてもよさそうだけど」「またしかに」
Tools::Pipeline.new .add_step(Tools::GetPolicyHistory)
.add_step(Tools::WithLoggedOutcome, log)
.add_step(Tools::RejectIfUnsupportedCheckPolicyHistory)
.add_step(Tools::FlagWithinRetentionIfEL)
.add_step(Tools::RejectIfNotFlaggedWithinRetention)
.add_step(Tools::RejectIfActiveChainNotYetMigrated)
.add_step(Tools::GetCustomerFromRails, rolodex_api)
.add_step(Tools::MakeChain, pimms_api)
.add_step(Tools::AssociateCustomerWithChain, rolodex_api)
.add_step(Tools::MarkCompletedAfterwards, log, pimms_api)
.add_step(Tools::MigratePoliciesAndNotes, log, pimms_api, notes_api)
.call(old_policy_number: old_policy_number)
VCR gemの使いこなし10(Ruby Weeklyより)
# 同記事より
VCR.configure do |c|
vcr_mode = ENV['VCR_MODE'] =~ /rec/i ? :all : :once
c.default_cassette_options = {
record: vcr_mode,
match_requests_on: %i[method uri body]
}
end
つっつきボイス:「先週ちらっと触れたVCR gem(ウォッチ20190708)の使いこなし記事です」「use_cassette
とかありますね」「やっぱりVCRだけにカセットなんですね」
「VCRはハードウェアのテストとかやるのに向いてる感じですね」「あんまりスタブ化されてないサービスなんかをカセットにするといいかも」「そういえばFaradayしか使えないこととかあった」「Faradayはちょい重いんだよな〜」
参考: lostisland/faraday: Simple, but flexible HTTP client library, with support for multiple backends.
「そのときはVCRでカセットに記録してそれでテストしましたし」「そっちの方が結局テスト書きやすいですし」「ぼくは雑だからスタブ使っちゃいますが」「スタブって個人的に何となく信用しきれないところがあって、生リクエスト作りたくなっちゃうな〜」「自分もそうしますけど」「スタブは軽いしテスト範囲を限定できるから、いいといえばいいんですが」
RailsアプリのDockerコンテナをビルドしてみた(Hacklinesより)
つっつきボイス:「Dockerfileとdocker-composeでRailsというみんなやってそうな記事ですが、どうやってるのかなと思って」「RailsでDockerを使ってる人って、どの人も微妙〜に使い方違うんですよね」「nodejsをapt-getでインストールしてるあたり↓とかも人によって違うし」「何が入ってくるのかわかりにくいヤツ」
FROM ruby:2.6.2
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
「本当に小さいコンテナを作りたければ本チャンのWebサーバーにはnodeはいらないはずだし: Webpackerをコンパイルするときだけあればいいんじゃね?って」
「おや、この記事のdocker-composeはvolumeの下にcached
がついてないじゃないですか」「そうそう、DockerfileもRUN apt-get update
した後にaptのキャッシュを消してないし」「マサカリ飛んできたゾ」
「ちょっと前にdocker-composeのvolumeにcached
とつけると速くなることを学びましたよ〜」「最近入ってきた機能ですよね」「たしかこれやらないと、特にDocker for Macで遅くなる」「付ける分にはどのDockerでもいいんでしょうか?」「今はどれでも使えると思います」
参考: Docker for Mac のボリュームの遅さを cached オプションで解消 - Qiita
「この記事の人ももしかするとLinux環境なのかもしれないし」「そこは情状酌量で」
後でDocker公式ドキュメントのベストプラクティスをmorimorihogeさんが見つけてくれました。
参考: Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント
以下は よく練られた
RUN
命令であり、推奨するapt-get
の使い方の全てを示すものです。
# docs.docker.jpより
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
lxc=1.0* \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
付け加えると、apt キャッシュをクリーンにし、 /var/lib/apt/lits を削除することで、イメージのサイズを減らします。
RUN
命令はapt-get update
から開始されるので、apt-get install
でインストールされるパッケージは、常に新鮮なものです。
docs.docker.jpより
その他Rails
MySQL の DateTime 型でカッコイイ内部表現を持っているっていうのを説明する URL を引っ張り出すために「mysql datetime kamipo」で検索した https://t.co/FRxLpu4dMF
— Takafumi ONAKA (@onk) July 16, 2019
つっつきボイス:「おっ、MySQLの内部表現か」
「これ↓は…なんだbeforeか」
- YEAR
- 1バイトinteger
- DATE
- 3バイトintegerをYYYY×16×32 + MM×32 + DDをパック
- TIME
- 3バイトintegerをDD×24×3600 + HH×3600 + MM×60 + SSをパック
- TIMESTAMP
- 4バイトinteger、epoch (‘1970-01-01 00:00:00’ UTC)以降のUTC秒を表す
- DATETIME
- 8バイト、うち4バイトintegerはYYYY×10000 + MM×100 + DDをパックしたdate、 4バイトはHH×10000 + MM×100 + SSをパックしたtime
「そしてafterはこれ↓」「hourが10ビット」「minuteとsecondはそれぞれ6ビットに」「DATETIMEはyear*13+month、あ、month表現でyearを持つってことか」「へぇ〜」
# TIME
1 bit sign (1= non-negative, 0= negative)
1 bit unused (reserved for future extensions)
10 bits hour (0-838)
6 bits minute (0-59)
6 bits second (0-59)
---------------------
24 bits = 3 bytes
# TIMESTAMP: エンディアンがlittleからbigになった他は同じ
# DATETIME
1 bit sign (1= non-negative, 0= negative)
17 bits year*13+month (year 0-9999, month 0-12)
5 bits day (0-31)
5 bits hour (0-23)
6 bits minute (0-59)
6 bits second (0-59)
---------------------------
40 bits = 5 bytes
「これはきれいに寄せたな〜」「きつきつのパンツみたくなりましたね」
Ruby
time_calc: 新しい日時算出ライブラリ(Ruby Weeklyより)
# 同リポジトリより
require 'time_calc'
TC = TimeCalc
t = Time.parse('2019-03-14 08:06:15')
TC.(t).+(3, :hours)
# => 2019-03-14 11:06:15 +0200
TC.(t).round(:week)
# => 2019-03-11 00:00:00 +0200
# TimeCalc.call(Time.now) shortcut:
TC.now.floor(:day)
# => beginning of the today
「モンキーパッチなし」「マジックなし」「チェイン可能」だそうです。
つっつきボイス:「zverokさんが新たに日時算出ライブラリを作ったそうなんですが、どの辺がうれしいかなと思って」「ああ、日時のround
(四捨五入)とかfloor
(切り下げ)とかceil
(切り上げ)ってたしかに欲しいときある!」「おお!」「floor
やceil
まであるって」
「こういうのやりたくなったりしません?週単位の切り上げとか、月単位の切り捨てとか」「Dateでround
の場合って、どう丸めるんだろう?」「水曜日のある時点を越えたときだったりして」「その辺をカスタマイズできたりすると業務的にかなりうれしいかも」「こういうの割と自力で書いたりしてますもんね」「step
したりチェインしたりできるし」
# 同リポジトリより
TC.(t).step(2, :weeks)
# => #<TimeCalc::Sequence (2019-03-14 08:06:15 +0200 - ...):step(2 weeks)>
TC.(t).step(2, :weeks).first(3)
# => [2019-03-14 08:06:15 +0200, 2019-03-28 08:06:15 +0200, 2019-04-11 09:06:15 +0300]
TC.(t).to(Time.parse('2019-04-30 16:30')).step(3, :weeks).to_a
# => [2019-03-14 08:06:15 +0200, 2019-04-04 09:06:15 +0300, 2019-04-25 09:06:15 +0300]
TC.(t).for(3, :months).step(4, :weeks).to_a
# => [2019-03-14 08:06:15 +0200, 2019-04-11 09:06:15 +0300, 2019-05-09 09:06:15 +0300, 2019-06-06 09:06:15 +0300]
「Ruby本家とかActive Supportに影響を与えそうな気がちょっとしてきました」「Active Supportに入っててもおかしくない機能かも」「この実装をそのまま使うかどうかはわからないけど」
その他Ruby
つっつきボイス:「bundle exec
を入力したくなくてやってみた記事だそうです」「binスタブ使うとかじゃなくて?」「記事ではzshでやってますね」「Oh My Zsh↓を入れるといい感じにできるぞ!みたいな」「そんなのあったとは」
「いっとき、Railsコマンドを実行するのにbundle exec
をいちいち入力したくない人たちがいろいろやって不具合出したりしてましたね」「be
あたりをbashのエイリアスにすればいいんじゃね?」「私もそれで十分だと思います」
Quoraより
つっつきボイス:「『お金があればやれる』という結論が沁みました」「インタプリタだしな〜しょうがないかな〜」
つっつきボイス:「Matzの回答でとても勇気が湧きました」「集中力15分!」「よくて30分」「普通8時間も集中力とか続かないってホント」
「8時間も集中を持続するなんて、せいぜい数年に一度、それも爆発炎上したときぐらいだし」「それも明け方とかに」
前編は以上です。
バックナンバー(2019年度第3四半期)
週刊Railsウォッチ(20190604-2/2後編)Cloudflare Workers KVの可能性、PostgreSQL 12 Beta 1、Bootstrap 5でjQuery廃止ほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSなど)です。