モンモンブログ

Ruby, Python, PHP, JavaScript/jQuery などなど気分に応じて

「ActionDispatch::Static がないよ」エラーは rails_12factor gem 入れて解決

環境

% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G29
% ruby --version
ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16]
% bundle exec rails --version
Rails 5.1.4

本題

production 環境で rails console や rails server がエラって起動しませんでした。

% RAILS_ENV=production be rails console
/Users/monmon/projects/hoehoe/vendor/bundle/ruby/2.4.0/gems/actionpack-5.1.4/lib/action_dispatch/middleware/stack.rb:106:in `assert_index': No such middleware to insert before: ActionDispatch::Static (RuntimeError)
        from /Users/monmon/projects/hoehoe/vendor/bundle/ruby/2.4.0/gems/actionpack-5.1.4/lib/action_dispatch/middleware/stack.rb:73:in `insert'
        from /Users/monmon/projects/hoehoe/vendor/bundle/ruby/2.4.0/gems/railties-5.1.4/lib/rails/configuration.rb:69:in `block in merge_into'
        from /Users/monmon/projects/hoehoe/vendor/bundle/ruby/2.4.0/gems/railties-5.1.4/lib/rails/configuration.rb:68:in `each'
        from /Users/monmon/projects/hoehoe/vendor/bundle/ruby/2.4.0/gems/railties-5.1.4/lib/rails/configuration.rb:68:in `merge_into'
(略)

No such middleware to insert before: ActionDispatch::Static と怒られます。ActionDispatch::Static って actionpack に含まれるミドルウェアみたいだけどなんでだろな。

ググってみたところ、GitHub Issue に解決法を見つけました。

No such middleware to insert before: ActionDispatch::Static · Issue #221 · AssetSync/asset_sync

rfroetscher さんのコメント。

Make sure you have gem 'rails_12factor', group: :production.
That solved the problem for me.

このコメント通りに rails_12factor を Gemfile に追加するだけで解決しました。

group :production do
  gem 'heroku-deflater'
  gem 'rails_12factor' # 追加
end

全然深掘りしてないですが、動いたのでよしとします٩( 'ω' )و

蛇足

ついでにもう1つ、同 Issue の別コメントに「heroku-deflater gem を Gemfile から削除すると直る」という情報もあり、僕の場合はこれでも確かに動いたのですが、 heroku-deflater は削除したくないので前述の方法で解決しましたとさ。

(ていうか多分この heroku-deflater がそもそもの原因な気がする…)

Rails5 の or クエリがバグを誘発しそうで超怖い

Rails5 になって ActiveRecordor クエリが導入されました。早速、既存のコードを書き換えようとしたんですが、かなり慎重に使わないと結合順序のワナに嵌ってバグりそうで怖いです。

このような(間の抜けた)コードがあったとします。

Country.
  where(id: 1).
  where("id = ? OR id = ?", 1, 2).
  pluck(:id)

生成される SQL はこうで、

SELECT "countries"."id" FROM "countries" WHERE
"countries"."id" = $1 AND (id = 1 OR id = 2)  [["id", 1]]

実行結果はこう。

[
    [0] 1
]

これを、or クエリを用いて素直に書き換えてみます。

Country.
  where(id: 1).
  where(id: 1).or(Country.where(id: 2)).
  pluck(:id)

生成される SQL はこうで、

SELECT "countries"."id" FROM "countries" WHERE
("countries"."id" = $1 AND "countries"."id" = $2 OR "countries"."id" = $3)  [["id", 1], ["id", 1], ["id", 2]]

実行結果はこう。て、さっきと違うやないかい!?

[
    [0] 1,
    [1] 2
]

最初のケースでは A AND (B OR C) のように OR 句のまわりが明示的にカッコで囲まれていたのに対し、 or クエリを使ったケースでは A AND B OR C とカッコがなく、結果的に (A AND B) OR C と評価されたためです。

最初のケースと同様の結果を得るには、or クエリを使った行を最初に持ってくるといいようです。

Country.
  where(id: 1).or(Country.where(id: 2)). # この行と
  where(id: 1).                          # この行を入れ替えた
  pluck(:id)

生成される SQL はこうなり、

SELECT "countries"."id" FROM "countries" WHERE
("countries"."id" = $1 OR "countries"."id" = $2) AND "countries"."id" = $3  [["id", 1], ["id", 2], ["id", 1]]

実行結果は最初と等しくなりました。

[
    [0] 1
]

(B OR C) AND A のように OR 句のまわりがカッコで囲まれて、最初のケースの A AND (B OR C) と等価になっています。

or クエリ、怖いですねえ。

この挙動をしっかり理解した上でコードを書いたとしても、チームの別のエンジニアが(あるいは将来の自分が)無自覚に where 句を追加したり順番を入れ替えたりして、予期せぬバグが生まれたりしたら…。

慎重に使ったほうがよさそうです。

DelayedJob による非同期処理の失敗を exception_notification でエラー通知させる

delayed_jobs による非同期処理時に、exception_notification によるエラー通知メール送信を行う - Qiita

こちらの記事を参考に DelayedJob のエラー通知を実装していたんですが、 Rails5 で alias_method_chain が Deprecated になってしまったので Module.prepend を使って書き直しました。

config/initializers/delayed_job.rb

module NotifyWhenDelayedJobFailed
  def handle_failed_job(job, error)
    # オリジナルの Delayed::Worker#handle_failed_job を実行
    super
    # 追加処理としてメール通知を行う
    ExceptionNotifier.notify_exception(error)
  end
end

class Delayed::Worker
  # 定義したモジュールを prepend
  prepend NotifyWhenDelayedJobFailed
end

Module.prepend については

» Ruby2.0のModule#prependは如何にしてalias_method_chainを撲滅するのか!? TECHSCORE BLOG

こちらが分かりやすかったです。