ActionMailerのメール本文がログに出力されるのを抑制
メール本文がログに出力されるとログ容量を圧迫するので(あとウザいので)出力を抑制しました。
環境
$ ruby -v ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin13] $ rails -v Rails 4.2.5
事象
メーラーでメールを送信すると、デフォルトでメール本文がログに記録されます。
... Rendered notifier/order_received.jp.html.erb within layouts/mailer (3.9ms) Rendered notifier/order_received.jp.text.erb within layouts/mailer (31.2ms) Notifier#order_received: processed outbound mail in 69.6ms Sent mail to hoehoe@hoe.com (148.1ms) Date: Thu, 28 Jan 2016 04:25:52 +0000 From: noreply@skimatalk.com To: hoehoe@hoe.com Message-ID: <56a99850628a8_e3fe3d690d13056190@812fa40a-c82a-4968-83bf-a9b4df09e732.mail> Subject: hoehoe Mime-Version: 1.0 Content-Type: multipart/alternative; boundary="--==_mimepart_56a998505d1bb_e3fe3d690d130560ea"; charset=UTF-8 Content-Transfer-Encoding: 7bit ----==_mimepart_56a998505d1bb_e3fe3d690d130560ea Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: base64 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ...
例では XXXX...
で表現してますが、base64 エンコードされたメール本文がこのように長々と出力されます。こんなのログに残っても対して役に立たない上にうっとうしいです。
解決策1
メーラーのロガーを nil
に設定するだけでもOK。
environments/production.rb
config.action_mailer.logger = nil
ただしこれだと、メーラーのログが一切出なくなり、それもちょっとどうかなあという感じ。(メールのテンプレートレンダリングのログは残ります。先ほどの例でいうと Rendered notifier/order_received.jp.html.erb within layouts/mailer (3.9ms)
などの行。ERB のレンダリングはメーラーの仕事じゃないからででしょうか。)
解決策2
メーラーのロガーを設定して、ロガーのログレベルを INFO レベルにしてやります。
environments/production.rb
config.action_mailer.logger = Logger.new(config.paths["log"].first) config.action_mailer.logger.level = Logger::INFO
ログはこんな感じになりました。
... Rendered admin_mailer/test.jp.text.erb (0.9ms) Rendered admin_mailer/test.jp.html.erb (1.7ms) I, [2016-01-28T07:14:51.945455 #17] INFO -- : Completed 302 Found in 670ms (ActiveRecord: 0.0ms) Sent mail to hoehoe@hoe.com (367.3ms) ← ...
Sent mail to hoehoe@hoe.com (367.3ms)
というメーラーのログは残しつつ、うっとうしいメール本文は消すことが出来ました。
ActiveRecordのscope,validatorでの意図せぬキャッシュに要注意
クリティカルなバグの原因になりえます(なりました)。
環境
少々古いです。activerecord の最近のバージョンでは未確認です。
ruby 1.9.3p547 (2014-05-14 revision 45962) [x86_64-darwin13.3.0] rails-3.2.11 activerecord-3.2.11 MacOSX Mavericks
現象
ActieRecord の named scope の条件文や、 validator のエラーメッセージなどは自動でキャッシュされちゃいます。そのため、
scope :upcoming, where('start_at > ?', Time.now)
のように scope の where 句で時刻比較したり、
validates :start_at, presence: { message: I18n.t(:start_at_is_required) }
のように validation のエラーメッセージを I18n で国際化したりすると、1回目に実行した時点での時刻やロケール文字列がキャッシュされてしまい、
- 時間が経過しても古い時刻での検索結果が返されたり
- 日本語ロケールを指定してるのに英語でメッセージが出力されたり
しちゃいます。怖いですね。
対策
Proc で囲むことで回避できます。lambda でも可。
named scope ならこう。
# クエリ全体を Proc.new で囲む scope :upcoming, Proc.new { where('start_at > ?', Time.now) }
validator ならこう。
# I18n 呼んでる部分を Proc.new で囲む validates :start_at, presence: { message: Proc.new{ I18n.t(:start_at_is_required) } } end
named scope から別の named scope を使ってる場合は更に注意が必要です。
時刻比較等の動的な操作を行う named scope (scope A) を、別の named scope (scope B) から使う場合、scope A のみならず scope B も Proc.new で囲む必要があるんです。
# (scope A) 時刻比較する "upcoming" scope。Proc.new で囲んで対策済み scope :upcoming, Proc.new { where('start_at > ?', Time.now) } # (scope B) "upcoming" scope を利用。Proc.new で囲んでない scope :available, upcoming.where(status: 1)
これはアウト。"available" scope の評価結果がキャッシュされ、時刻が変化しません。
# (scope A) こちらを Proc.new で囲む必要があるのはもちろん、 scope :upcoming, Proc.new { where('start_at > ?', Time.now) } # (scope B) "upcoming" scope を利用するこちらも Proc.new で囲まないといけない scope :available, Proc.new { upcoming.where(state: :open) }
両方 Proc.new で囲みました。これでセーフ。
気をつけましょう。
Action Mailerのマルチパートメールでファイル形式の優先順位が変わっちゃう件
Action Mailer でマルチパートメールを送るようにしていて、かつ mail
メソッドにブロックを渡す場合は注意が必要です。html 版、text 版の優先順位が意図せず変わってしまう場合があります。
環境
MacOSX Mavericks ruby 1.9.3p547 (2014-05-14 revision 45962) [x86_64-darwin13.3.0] rails-3.2.11 actionmailer-3.2.11
そもそもマルチパートメールって?
1通のメール中に複数の形式のメール本文を含められる機能です。
例えば html 形式と text 形式、両方のメール本文を含めれば、html 対応のメール表示ソフトではリッチな html 形式で表示させ、そうでなければ text 形式で表示させる、てことが可能です。
マルチパートメール on Rails
rails でマルチパートメールを送るのは超簡単。mailer のテンプレートとして複数の形式のファイルを用意しておくだけです。
app/views/user_mailer/password_reset.html.erb # html版 app/views/user_mailer/password_reset.text.erb # text版
あとは普通にメールを送信するだけ。
UserMailer.password_reset(user).deliver
送信されるメールの内容はこんな感じ。1本のメールに複数の形式が含まれてるのが分かると思います。
Sent mail to xxxx@skimatalk.com (1786ms) # メールヘッダ Date: Mon, 02 Feb 2015 11:09:00 +0900 From: noreply@skimatalk.com To: xxxx@skimatalk.com Message-ID: <xxxx> Subject: xxxx Mime-Version: 1.0 Content-Type: multipart/alternative; # Content Type でマルチパート指定 boundary="--==_mimepart_54cedc31b283f"; # 各パートの境界となる文字列を定義 charset=UTF-8 Content-Transfer-Encoding: 7bit ----==_mimepart_54cedc31b283f # パート境界1 (text版ここから) Date: Mon, 02 Feb 2015 11:09:00 +0900 # text版メールヘッダ Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: base64 Content-ID: <xxxx> 44G744GS44G744GS44G744GS44G744GS44G744GS44G7 # text版メール本文 (base64 encoded) 44GS44G744GS44G744GS44G744GS44G744GS44G744GS 44G744GS44G744GS44G744GS44G744GS44G744GS44G7 44GS44G744GS44G744GS44G744GS44G744GS44G744GS ----==_mimepart_54cedc31b283f # パート境界2 (html版ここから) Date: Mon, 02 Feb 2015 11:09:00 +0900 # html版メールヘッダ Mime-Version: 1.0 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: base64 Content-ID: <xxxx> 44G744GS44G744GS44G744GS44G744GS44G744GS44G7 # html版メール本文 (base64 encoded) 44GS44G744GS44G744GS44G744GS44G744GS44G744GS 44G744GS44G744GS44G744GS44G744GS44G744GS44G7 44GS44G744GS44G744GS44G744GS44G744GS44G744GS ----==_mimepart_54cedc31b283f-- # パート境界3 (メール末尾)
各形式の優先順位は ActionMailer::Base
の default
メソッドで parts_order
の値をセットすることで指定出来ます。この値はデフォルトで ["text/plain", "text/enriched", "text/html"] となってます(参考)。各形式の内容はこの順番で出力されます(先ほどの例も然り)。
class UserMailer < ActionMailer::Base default parts_order: ["text/plain", "text/enriched", "text/html"] def password_reset(user) ... end end
この順番だと、一番上に出力される text/plain
形式の内容が最優先で表示されそうですが、違います。
RFCに、表示の際の優先順位は逆順とするように定められており、この場合、一番下の text/html
が最優先となります。
この理由もRFCにありました。もっともシンプルな形式を一番上に出力するようにしておけば、MIME非対応のメール表示ソフトであっても読みやすいよね、ということのようです。なるほどちゃん。
placing the plainest alternative first is the friendliest possible option when mutlipart/alternative entities are viewed using a non-MIME- compliant mail reader.
mail メソッドにブロックを渡すと parts_order 指定が無視されちゃう
mail メソッドにはブロックを渡すことが出来ます。下記のように、ファイル形式ごとにレイアウトを指定出来たりします。
class UserMailer < ActionMailer::Base default parts_order: ["text/plain", "text/enriched", "text/html"] def password_reset(user) mail to: 'xx@skimatalk.com', subject: 'hello!' do |format| format.text { render layout: 'mailer' } format.html { render layout: 'mailer' } end end end
これで、
app/views/layouts/mailer.text.erb app/views/layouts/mailer.html.erb
がメールのレイアウトとして使われるって寸法。
が、ここに落とし穴があります。
format.text { render layout: 'mailer' } format.html { render layout: 'mailer' } # html形式が後
この順番を逆にして
format.html { render layout: 'mailer' } format.text { render layout: 'mailer' } # text形式が後
ってしちゃうと、parts_order
での指定を無視して、html 形式が先、text 形式が後に出力されます。
すると RFC のルール通り、text 版の方が優先して表示されちゃいます。html 版は決して表示されなくなります。なんと。
ActionMailer::Base のドキュメントを読んでもそんなこと書いてないですねえ。(該当箇所は "If you want to explicitly render only certain templates..." らへんから)
参考
Rails + Grape 構成で Grape API ファイルを自動再読み込みさせるには
RESTful な API が楽ちんに書ける grape。 rails との組み合わせで使う場合も README に従うだけで簡単に出来ますが、なぜか自動再読み込み (auto reloading) されないって問題にぶち当たります。
development 環境だと model や controller への修正はブラウザをリロードするだけで即反映されますよね。でも grape の api ファイルはなぜか再読み込みされない。いちいち rails サーバを再起動しないといけなくて、非常に困っちゃんです。
(よく理解してませんが rails 上で別の rack フレームワーク(この場合 grape)を動かそうとするとrails の自動再読み込みの仕組みがうまく動かないみたいです)
で、解決方法ですが、grape api ファイルの更新を検知して、無理やり再読み込みさせてやります。
環境
古くてごめんなさい
MacOSX Mavericks ruby 1.9.3p547 (2014-05-14 revision 45962) [x86_64-darwin13.3.0] rails-3.2.11 grape-0.9.0
セットアップ
今回は既存の rails アプリに grape を追加しました。手順をざくっと書いておきます。
Gemfile に追記して、
gem 'grape'
インストール
bundle install
grape api ファイルを置くディレクトリを作って
mkdir app/api
Hello World なやつを書いた。(app/api/skimatalk_api.rb)
require 'grape' # この行は grape 単体で動かす場合に必要。rails との組み合わせでは不要 class SkimatalkApi < Grape::API format :json resource :ehehe do desc 'this is a test' get :hoe do {a: 1, b: 2} end end end
rails と組み合わせて実験する前に grape 単体で動かしてみたかったので、同じディレクトリにテスト用の config.ru を作成 (app/api/config.ru)
# coding: utf-8 require './skimatalk_api.rb' run SkimatalkApi
起動
$ cd app/api $ rackup Thin web server (v1.6.2 codename Doc Brown) Maximum connections set to 1024 Listening on 0.0.0.0:9292, CTRL+C to stop 127.0.0.1 - - [22/Nov/2014 19:54:05] "GET /ehehe/hoe HTTP/1.1" 200 13 0.0971
http://localhost:9292/ehehe/hoe にアクセスすると JSON が表示された。わーい。
(さっき作った config.ru はもう不要なので削除してよいです)
今度はこれを rails 上で動かします。 config/routes.rb に以下を追加して /api/* へのリクエストを grape へ渡すようにします
mount SkimatalkApi => '/api'
サーバ再起動してから、
rails s
http://localhost:3000/api/ehehe/hoe にアクセス。JSON が表示された。やったね。
でも(さっきも言ったとおり)app/api/skimatalk_api.rb
を修正してリロードしても変更が反映されません。困ったね。
解決方法
ここからが本題。
config/initializers/reload_api.rb
を作って以下のように書きます
if Rails.env.development? api_reloader = ActiveSupport::FileUpdateChecker.new(Dir['app/api/*']) do # app/api/* は環境にあわせて変更すること Rails.application.reload_routes! # or do something better here end ActionDispatch::Callbacks.to_prepare do api_reloader.execute_if_updated end end
これだけで、 app/api/* への修正を検知して自動再読み込みしてくれるようになりました。
仕組みは読めばなんとなく分かるよね。分かんなくてもいいよね。
参考
正直このエントリは stackoverflow のこれ↓を解説しただけです。ググって見つけるのに苦労したので書きました…
Ruby on Rails 3 - Reload lib directory for each request - Stack Overflow
jQuery プラグインを bower のレジストリに登録してみた
表題の通り、jQuery プラグイン jquery.narrows を bower のレジストリに登録してみた時の記録です。
bower とは?
こちらが非常に分かりやすかったです。
基礎編の冒頭から(勝手に)引用させて頂きますと、bower とは
Twitter社が作ったフロントエンド用のパッケージマネージャです。 Java で言う Maven、 Ruby で言う gem、 Perl で言う cpan のようなものです。 Node.jsには npm と呼ばれるパッケージマネージャがありますが、それに強く影響を受けています。
ということです。
bower のパッケージは bower レジストリってところで管理されています。 登録済みパッケージの一覧は Bower components ってページで見られます。検索もできます。
この bower レジストリにおいらの jQuery プラグインをいっちょ登録したるぜ!と思い立ちました。
bower レジストリに登録すると何が嬉しいの?
ここに登録しておけば、
bower search
で検索したり("sinon" は javascript のテスト用ライブラリです)
$ bower search sinon Search results: sinon git://github.com/cjohansen/Sinon.JS.git sinon-chai git://github.com/domenic/sinon-chai.git sinonjs git://github.com/blittle/sinon.js.git (以下略)
bower info
でインストール可能なバージョンを調べたり
$ bower info sinon bower sinon#* not-cached git://github.com/cjohansen/Sinon.JS.git#* bower sinon#* resolve git://github.com/cjohansen/Sinon.JS.git#* bower sinon#* download https://github.com/cjohansen/Sinon.JS/archive/v1.7.3.tar.gz bower sinon#* extract archive.tar.gz bower sinon#* resolved git://github.com/cjohansen/Sinon.JS.git#1.7.3 { name: 'sinon', homepage: 'https://github.com/cjohansen/Sinon.JS', version: '1.7.3' } Available versions: - 1.7.3 - 1.7.1 - 1.7.0 (中略) - 0.6.0 - 0.5.0 - 0.2.3 You can request info for a specific version with 'bower info sinon#<version>'
bower install
で指定したバージョンをダウンロードしたり出来ます。
$ bower install sinon#1.7.3 bower sinon#1.7.3 cached git://github.com/cjohansen/Sinon.JS.git#1.7.3 bower sinon#1.7.3 validate 1.7.3 against git://github.com/cjohansen/Sinon.JS.git#1.7.3 bower sinon#1.7.3 install sinon#1.7.3 sinon#1.7.3 bower_components/sinon
デフォルトで bower_components
ディレクトリ以下にダウンロードされます。
$ ls bower_components/sinon AUTHORS LICENSE build* lib/ release.sh* Changelog.txt README.md jsl.conf package.json test/
JavaScript ライブラリがコマンドラインだけでダウンロード出来ちゃうのがステキです。 いちいちググって、ダウンロードやら git clone やらして、って手間が不要です。
(あと npm の package.json の dependency や devDependency と同様の機能も備えますがここでは触れません)
bower レジストリ登録への道のり
という感じ。簡単。順に説明します。
0. bower をインストール
npm でインストール。
$ npm install -g bower $ bower -v 1.2.8
1. bower.json を作成
bower init
コマンドを叩いて質問に答えていくと bower.json の雛形が生成できます。
$ bower init [?] name: jquery.narrows [?] version: 0.3.1 [?] description: jQuery Hierselect Plugin [?] main file: jquery.narrows.js [?] keywords: jQuery,select,hierselect [?] authors: simon <xxxxx@gmail.com> [?] license: MIT [?] homepage: https://github.com/monmonmon/jquery.narrows [?] set currently installed components as dependencies? Yes [?] add commonly ignored files to ignore list? Yes [?] would you like to mark this package as private which prevents it from being accidentally published to the registry? [?] would you like to mark this package as private which prevents it from being accidentally published to the registry? No { name: 'jquery.narrows', main: 'jquery.narrows.js', version: '0.3.1', homepage: 'https://github.com/monmonmon/jquery.narrows', authors: [ 'simon <xxxxx@gmail.com>' ], description: 'jQuery Hierselect Plugin', keywords: [ 'jQuery', 'select', 'hierselect' ], license: 'MIT', ignore: [ '**/.*', 'node_modules', 'bower_components', 'test', 'tests' ] } [?] Looks good? Yes
それを手直しして、コミット。
$ vim bower.json $ git add bower.json $ git commit $ git push
ちなみに手直しした結果はこんな感じ。
{ "name": "jquery.narrows.js", "version": "0.3.1", // <-gitでのバージョン番号を記述 "description": "jQuery Hierselect Plugin", "license": "MIT", "main": "jquery.narrows.js", // <-パッケージのメインとなるファイル "ignore": [ // <-bower install した時にダウンロードさせたくないファイルのリスト "**/.*", "**/*.html", "Gruntfile.coffee", "node_modules", "README.md", "package.json", "lib", "spec" ] }
さっきのBower入門(応用編)にも書かれていますが、
"main" と "ignore" をキチンと書かないと bower install
した時に余計なものまでダウンロードしちゃって鬱陶しいので、気をつけて記述します。
(例えば Gruntfile.coffee
はライブラリ利用者には不要なので "ignore" に含めてます。)
あと "version" にも要注意。ここに書いたバージョン番号を、次で git tag でタグ付けします。
2. リリースバージョン番号をタグ付け
bower.json に書いたバージョン番号をタグ付けしてやります。 bower はこのバージョン番号に基いてパッケージを管理します。
$ git tag 0.3.1 $ git push --tags Total 0 (delta 0), reused 0 (delta 0) To git@github.com:monmonmon/jquery.narrows.git * [new tag] 0.3.1 -> 0.3.1
バージョン 0.3.1 がリリースされました。
3. bower register
さていよいよ、ライブラリをレジストリへ登録します。コマンドの書式は
bower register <my-package-name> <git-endpoint>
です。
$ bower register jquery.narrows git@github.com:monmonmon/jquery.narrows.git bower convert Converted git@github.com:monmonmon/jquery.narrows.git to git://github.com/monmonmon/jquery.narrows.git bower jquery.narrows#* resolve git://github.com/monmonmon/jquery.narrows.git#* bower jquery.narrows#* download https://github.com/monmonmon/jquery.narrows/archive/0.3.1.tar.gz bower jquery.narrows#* extract archive.tar.gz bower jquery.narrows#* resolved git://github.com/monmonmon/jquery.narrows.git#0.3.1 [?] Registering a package will make it installable via the registry (https://bower.herokuapp.com), continue? (Y/n) bower jquery.narrows register git://github.com/monmonmon/jquery.narrows.git Package jquery.narrows registered successfully! All valid semver tags on git://github.com/monmonmon/jquery.narrows.git will be available as versions. To publish a new version, just release a valid semver tag. Run bower info jquery.narrows to list the available versions.
ほんとに登録されたのか確認してみましょう。
$ bower info jquery.narrows bower jquery.narrows#* cached git://github.com/monmonmon/jquery.narrows.git#0.3.1 bower jquery.narrows#* validate 0.3.1 against git://github.com/monmonmon/jquery.narrows.git#* { name: 'jquery.narrows.js', version: '0.3.1', main: 'jquery.narrows.js', description: 'jQuery Hierselect Plugin', license: 'MIT', ignore: [ '**/.*', '**/*.html', 'Gruntfile.coffee', 'node_modules', 'README.md', 'package.json', 'lib', 'spec' ], homepage: 'https://github.com/monmonmon/jquery.narrows' } Available versions: - 0.3.1 You can request info for a specific version with 'bower info jquery.narrows#<version>'
出た出た!
Bower components でも、検索フィールドに "jquery.narrows" って入力するとちゃんと表示されました。
試しに bower install してみる
今登録したライブラリがちゃんと bower install
出来るかも実験してみましょう。
$ bower install jquery.narrows#0.3.1 bower jquery.narrows#0.3.1 cached git://github.com/monmonmon/jquery.narrows.git#0.3.1 bower jquery.narrows#0.3.1 validate 0.3.1 against git://github.com/monmonmon/jquery.narrows.git#0.3.1 bower jquery.narrows#0.3.1 install jquery.narrows#0.3.1 jquery.narrows#0.3.1 bower_components/jquery.narrows
そしたらホラホラ!
$ ls -l bower_components/jquery.narrows bower.json jquery.narrows.js jquery.narrows.min.js
ひゃっほい。
bowser.json の "ignore" をちゃんと書いたおかげで、余計なファイルを(ほぼ)含まずに js ファイルだけダウンロードすることが出来ました。(bower.json 自身だけ "ignore" に入れるのを忘れてたけど…笑)
というわけで
快適な bower ライフをお楽しみ下さい(とってつけた)