LoginSignup
13
11

More than 3 years have passed since last update.

Rails テンプレートにはマジコメ必要

Last updated at Posted at 2016-01-26

日本の Windows ユーザーはローカルで使う Rails テンプレートにマジックコメントを付けるべきと悟った記録

予備知識

知ってる人は飛ばしてくれ。

Rails テンプレート

rails new hoge したあとに毎回同じような Gemfile 修正とかファイル追加とかやるの面倒なので,そういった一連の作業をテンプレートファイルに記述しておくことができる。

ファイルの中身は Ruby スクリプト。

テンプレートファイルはファイルシステムに置いてもいいし,ウェブサーバーに載せてもいい。
使うときは -m オプションで

$ rails new hoge -m path/to/template.rb

とか

$ rails new hoge -m https://localhost/rails/template

のように指定する。

マジックコメント

Ruby スクリプトの第一行に

# encoding: utf-8

のようなコメントの形で書き,そのファイルが何の文字コードで書かれているかを指定するもの。「マジコメ」と略す。

Ruby 1.9 で導入されたが,Ruby 2.0 でデフォルトが UTF-8 になったので,スクリプトが UTF-8 で書かれているなら不要になった。

ことの経緯

鼻歌を歌いながら Rails テンプレートを書いた。

文字列リテラルに日本語が入ってるけど気にしなかった。

Ruby 2.1 なので当然のごとくマジコメは付けなかった。つか,既にマジコメの存在自体を忘れて久しい。

よしっ,テンプレートのテストだ。

$ rails new hoge -m path/to/template.rb
(略)invalid multibyte char (Windows-31J) (SyntaxError)(略)

し,しんたっくすえらあ? ぁあん?
なんか,UTF-8 で書いたテンプレートがなぜか Windows-31J(CP932)とみなされちゃったみたいだな。

えーっと,マジコメは要らないはずだけど,試しに付けてみるか。

$ rails new hoge -m path/to/template.rb

通った! 正常に完了した! なんでやねん? マジコメ要るのかよ?

マジコレ? (ハイ,笑うとこですョ)

はっ,そうだ! さっきウトウトしてたときに時空の裂け目から Ruby 1.9 の世界に迷い込んでしまったのかも?!

$ ruby -v
ruby 2.1.7p400 (2015-08-18 revision 51632) [i386-mingw32]

そ,そういうわけでもないようだな。

原因

原因はわりとすぐに分かった。

例外を吐いたのは thor というライブラリーの ここ

    def apply(path, config = {})
      verbose = config.fetch(:verbose, true)
      is_uri  = path =~ %r{^https?\://}
      path    = find_in_source_paths(path) unless is_uri

      say_status :apply, path, verbose
      shell.padding += 1 if verbose

      if is_uri
        contents = open(path, "Accept" => "application/x-thor-template") { |io| io.read }
      else
        contents = open(path) { |io| io.read }
      end

      instance_eval(contents, path)
      shell.padding -= 1 if verbose
    end

Rails テンプレートを IO#read で読み込んで contents に入れ,それを instance_eval してた。

読み込むときに encoding を指定してない。

Windows の日本語環境の場合,ロケール情報を元に Encoding.default_externalEncoding::Windows_31J にされるので,encoding を指定しないで IO#read すると,Windows 31J で読み込まれるのだ。

それを instance_eval しようとするからテンプレートファイルの中身によっては例外が発生することになる。

やれやれ。あちらで作られた gem はこの手の話が多いな。

もうちょっと調べてみた

ちょっと気になったのが,マジコメを付けたらどうして問題が無くなったのか,ということ。

マジコメを付けようが付けまいが,今の場合,contents に代入される文字列は Windows 31J である。これは間違いない。

ということは,instance_eval がマジコメを見てるってコト?

マジコレ? (もうええっちゅうに)

早速実験だ。instance_eval でも eval でも同じだろうから,次のようなことをやってみる。ファイルはすべて UTF-8 で書く。

まずはマジコメ無しで:

foo.rb
script = IO.read("bar.rb")
p script.encoding
eval script
bar.rb
puts "あ"
$ ruby foo.rb
#<Encoding:Windows-31J>
foo.rb:3:in `eval': (eval):3: invalid multibyte char (Windows-31J) (SyntaxError)

(eval):3: unterminated string meets end of file
        from foo.rb:3:in `<main>'

まあ,そうなるだろうな。

次は bar.rb にマジコメを付けてみる:

bar.rb
# encoding: utf-8

puts "あ"
$ ruby foo.rb
#<Encoding:Windows-31J>

おおー,やはり eval は文字列がマジコメで始まっていたら,その文字コードで解釈するのだ。

勘が当たった。がははは。と威張る。 (ハイ,笑うとこですョ)

いやー,知らなかった。やっぱりリファレンスマニュアルはちゃんと読まなきゃな。

か,書いてないよ。 orz

最後に

  • Ruby スクリプトは require で読み込まれるとは限らない。IO#read して eval されることもある。
  • require ならマジでマジコメ要らないのだと思う。
  • Linux や Mac OS X の人には関係の無い話だと思う。
  • default_external が UTF-8 になるような言語圏の人には関係の無い話だと思う。
  • テンプレートをウェブサーバーに置いた場合はたぶん Content-Type で文字コードが決まるので,サーバーの設定が正しくできていれば関係の無い話だと思う。

追記:環境変数による解決(2020-01-07)

コメント欄で @topstone さんが環境変数による解決法を書いてくださったので,記事に追記します。
私は試していませんが,環境変数 RUBYOPT-EUTF-8 を含めておけばよいのだそうです。

-Eruby コマンドに与えるオプションで,デフォルトの外部エンコーディング,内部エンコーディングを変更するものですね。
-EUTF-8 とすると,デフォルトの外部エンコーディングが UTF-8 になります。

環境変数 RUBYOPT の値は,ruby コマンドにオプションとして与えたのと同じように Ruby 処理系に渡されるので,この環境変数にセットしておけばいいわけですね。

13
11
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11