仮想サーバで bin/rails コマンドとかがクッソ遅い件
Vagrant+VirtualBox を使って構築した仮想サーバ (CentOS 6.5) で、/vagrant 共有ディレクトリでを rails プロジェクトのルートディレクトリにしていて、
$ bin/rails s
とか
$ bin/rake about
とかコマンドを実行すると、すごい遅い! bin/rake about でなんと30秒以上かかりました。ホンマに Spring 効いているのかしらん?と思ったけど、最早そんなレベルじゃないよね。
なんでだろ〜??と思ってたら、どうやら gem をプロジェクト内にインストールしていたのが原因だったみたい
$ bin/bundle install --path=vender/bundle
とかってやっていたんだけど、試しに .bundle ファイルを消して、改めて
$ bin/bundle install
と gem をグローバルにインストールし直したら、普通のスピードで動きました!
gem のインストール先が /vagrant 以下の共有ディレクトリだったってところが時間がかかってしまっていた理由なのかも。別にグローバルにインストールしなくても、共有ディレクトリ外にインストールすればよいみたい。
ってゆーか、そもそも開発用の仮想サーバ上で複数のプロジェクトを扱う必要もないので、仮想サーバ上では gem をグローバルにインストールってことにしておきます。
一度インストールしてしまった vender/bundle 以下の Gem たちはごそっと消しておきました。
$ rm -rf vendor/bundle
ちなみに本番サーバで bundle install する際は --deployment オプションをつけると、プロジェクト内 (デフォルトは vendor/bundle ディレクトリ) にインストールしてくれます。
$ bin/bundle install --deployment
仮想サーバ上の WEBrick にローカルマシンから接続できない?!
しばらく Rails と離れていたのですが、久々に戻って参りました。気がつけば Rails5 がもう目の前!時が経つのは早いものです。
世の中の流れから置いていかれている感が半端ないので、また一から勉強っつぅ事で、つまづいたり調べたりした事をボチボチと書いていこうと思ってます。
仮想サーバの WEBrick にローカルマシンから繋がらない!
さて、ここんとこ巷で流行っている Vagrant + VirtualBox を使って仮想サーバに CentOS をインストールして、そこに開発環境を作るぜー!とやってみています。
とりあえず、chruby と ruby-install を使って ruby を入れて、rails も入れて、rails new して、bin/rake db:create して... さぁ準備OK!ってところで
$ bin/rails s
とやって WEBrick の Web サーバを起動したのですが、ローカルマシンから繋がらない...??
ちなみに Vagrantfile では、下記のようにポートフォワードの設定をしているので
config.vm.network "forwarded_port", guest: 3000, host: 4000
ローカルマシンのWebブラウザで http://localhost:4000 を叩いてみるも返事なし...
仮想サーバに ssh ログインした状態で、
$ curl -v 'http://localhost:3000/'
ってコマンド打ってみると、ちゃんと html 形式のレスポンスが帰ってきたので、WEBrick は動いている模様。
むむぅ、もしかして iptables 辺りが原因か?と疑ったが、特に設定してなかった...。
ググってみると、どうやら WEBrick が listen する際のアクセス元IPアドレスが、Rails 4.1 までのデフォルトが 0.0.0.0 だったのに対し、Rails 4.2 からは 127.0.0.1 に変更になったそうです。
ちなみに、0.0.0.0 というのは任意のIPアドレス(any)という意味だそうで、どこからでもアクセスできますよ、という状況だったのを 127.0.0.1 (localhost) からしか、つまり仮想サーバ自身からしか繋がらないよというように変更したということですね。
ま、開発用のWebサーバに、あちこちのマシンからアクセスできるのは、ちょっとイケテナイねということなのでしょう。仕方ありません。
解決策1. WEBrick 起動時にオプション指定
WEBrick を起動する際に、オプションでIPアドレスとして 0.0.0.0 を指定すれば良いとのこと。
$ bin/rails s --bind=0.0.0.0
または
$ bin/rails s -b 0.0.0.0
無事、ローカルマシンのWebブラウザから http://localhost:4000 で繋げました。
仮想サーバ上で netstat コマンドを叩いてみると
$ netstat -an Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:3000 0.0.0.0:* LISTEN
ってなってますが、--bind オプション無しで netstat を叩くと
$ netstat -an Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN
ってなってます。この状態だと、ローカルマシンからアクセスできないってことですね。
解決策2. WEBrick のデフォルトの設定を変える
WEBrick を起動するたびに --bind オプションをくっつけるのはチト面倒くさい。
ということで、デフォルトで 0.0.0.0 をバインドするように変更します。
config/boot.rb の末尾に、以下を追加して Rails::Server のデフォルトオプションを追加すると良いみたい
require 'rails/commands/server' module Rails class Server def default_options super.merge(Host: '0.0.0.0', Port: 3000) end end end
これで
$ bin/rails s
を叩くだけで、0.0.0.0 をバインドしてくれます。めでたし、めでたし。
プルダウンメニュー(select_tag)の中身をデータベースに用意する
プルダウンメニュー(select_tag)の選択肢を決め打ちでやらず、マスタとしてDB内のテーブルに用意する場合のやり方です。collection_select を使います。
ここでは例として、都道府県名を格納するために prefecture テーブルと Prefecture モデルを作成します。
|id |name | | 1|北海道 | | 2|青森県 | | 3|岩手県 | ...(以下省略)
このテーブルの中身をプルダウンメニューの選択肢として利用するには以下のように書きます。
<%= form_for @user do |f| %> <%= f.label :prefecture_id %> <%= f.collection_select :prefecture_id, Prefecture.all, :id, :name %> <%- end -%>
文法としてはこんな感じで、それぞれの引数の意味は以下の通りです。
<%= f.collection_select (1), (2), (3) , (4) [, オプション] %>
(1) | 属性名 | フォームの対象モデルの中の該当する属性名(ここでは User.prefecture_id) |
(2) | 配列データ | プルダウンメニューの選択肢に使用するデータ配列 各要素は連想配列やオブジェクトなど |
(3) | 値カラム名 | プルダウンメニューで値として扱うカラムの名前 |
(4) | 表示名カラム名 | プルダウンメニューで表示名として扱うカラムの名前 |
ちなみに同じことを select タグでやるとこんな感じでできます。
<%= f.select :prefecture_id, Prefecture.all.map{|pf| [pf.name, pf.id]} %>
あんまり変わらないやんって思った方、正解です(^^)
オプションあれこれ
プルダウンメニューの先頭に空白行を追加する(:include_blank オプション)
プルダウンメニューの選択肢の先頭に空白の選択肢を追加することができます。一旦どれかを選択しても、未選択の状態に戻せるということです。なお、空白行の value は空文字列が指定されます。
<%= f.collection_select :prefecture_id, Prefecture.all, :id, :name, :include_blank => true %>
include_blank オプションの値を true/false ではなく文字列にすると、その文字列が使用されます。
<%= f.collection_select :prefecture_id, Prefecture.all, :id, :name, :prompt => "選択を解除" %>
値が未設定の時にプロンプトを表示(:prompt オプション)
prompt オプションを使うと、プルダウンメニューが選択されていない時(=初期値が設定されなかったとき)に「選択して下さい」といった文字列の行が先頭に追加されます。
<%= f.collection_select :prefecture_id, Prefecture.all, :id, :name, :prompt => true %>
表示される文字列は I18n を使っている場合、ロケ―ルファイルで指定された文字列が使用されます。
ja: helpers: select: prompt: "選択してね♪"
promptオプションの値を true/false ではなく文字列にすると、それがプロンプトとして使用されます。
<%= f.collection_select :prefecture_id, Prefecture.all, :id, :name, :prompt => "好きなものを選んで" %>
include_blank オプションと prompt オプションはどちらも先頭行に追加しますが、動作がちょっとだけ違います。
:include_blank | 常に先頭行を追加 |
---|---|
:prompt | 選択行の指定がない時だけ先頭行を追加 |
デフォルトの選択を指定する(:selected オプション)
やりかたは通常の f.select と同じで
<%= f.collection_select :prefecture_id, Prefecture.all, :id, :name, :selected => 13 %>
と書けます。
が、あまりこのオプションはあまり使わないでしょう。
selected を指定しなくても、@user.prefecture_id に値が既に入っている場合は自動的にその値にセットされます。逆に selected を使うと、常に selected で指示されたものが選択されることになります。初期値として入れたい場合は、controller の new メソッド内で初期値を設定した方がいいでしょう。
class UserController < ApplicationController def new @user = User.new(:prefecture_id => 13) end end
+5分で更にわかるアセットパイプライン(Assets Pipeline)
前回の記事からの続きです。
プリコンパイルの対象ファイル
assets/ ディレクトリ下の全てのファイルがプリコンパイルされるわけではありません。javascript、stylesheet ファイルは "マニフェストファイル" で指定されたファイルだけが対象となります。画像ファイルは全て対象となるようです(正確には、javascript、stylesheet ファイル以外は全て対象)。
《書きかけです》
5分でわかる!? アセットパイプライン(Assets Pipeline)
アセットパイプラインとは
Rails のアセットパイプライン(Assets Pipeline)は JavaScript や CSS、画像ファイルといった HTML に付随する細々としたファイル達(アセットファイル)を連結したり圧縮することで効率的にアクセスできるようにする仕組み(フレームワーク)です。
アセットパイプラインの役割
アセットパイプラインは以下のような役割をもっています。
1 | 複数の JavaScript と CSS をそれぞれ1つのファイルに連結する ⇒ ブラウザからのリクエスト数を減らす |
---|---|
2 | JavaScript と CSS を圧縮してサイズを減らす ⇒ 文法上必要でない空白を削除したり、変数名を1文字にしたり |
3 | アセットファイル名にハッシュ値(MD5)を付加する ⇒ ブラウザがファイルのキャッシュ/読み直しを正しく行える |
4 | ERB、CoffeeScript、SCSS などを使って効率的で柔軟な方法で記載できる |
アセットパイプラインの動きの基本
productionモードでの動きが最終的な目的となる動きなので、それを中心に説明します。
アセットファイルの置き場所
自分で作成するアセットファイルは app/assets/ 下のファイルの種類ごとのサブディレクトリに格納します。
ファイル種 | 保存する場所 | 拡張子 |
---|---|---|
javascript | app/assets/javascripts | js / js.coffee など |
stylesheet | app/assets/stylesheets | css / css.scss など |
画像 | app/assets/images | jpeg / png / ico など |
coffee スクリプトファイルや scss ファイルを置くことができます。直接 js ファイルや css ファイルを置いてもOKです。
アセットファイルへのアクセス
アセットファイルへは assets ディレクトリ下のファイルとしてアクセスします。ファイル種ごとのサブディレクトリ名は不要です。
http://RailsアプリのURL/assets/ファイル名
また coffee スクリプトや scss は、js ファイルや css ファイルとして解釈されたファイルにアクセスします。
元のファイル | URLでのアクセス |
---|---|
assets/javascripts/application.js.coffee | http://******/assets/application.js |
assets/stylesheets/application.css.scss | http://******/assets/application.css |
assets/images/logo.png | http://******/assets/logo.png |
プリコンパイル
アセットファイルが上のようにURLでアクセスするにはプリコンパイルをする必要があります。プリコンパイルは次のような働きをします。
1 | app/assets/、lib/assets/、vendor/assets/ 等から対象ファイルを探索 |
---|---|
2 | ERB、CoffeeScript、SCSS などのエンジンを使って JavaScript、CSS ファイルに展開 |
3 | 複数の JavaScript や CSS をそれぞれ1つのファイルに連結 |
4 | JavaScript と CSS を圧縮 |
5 | アセットファイル名にハッシュ値(MD5)を付加 |
6 | できあがったアセットファイルを public/assets/ ディレクトリに保存 対象ファイル:JavaScript、CSS、画像ファイル |
プリコンパイルの仕方
デプロイの前にプリコンパイルをしておきましょう。コマンドラインから実行します。これをやっておかないと、アクセス時に「ファイルがプリコンパイルされていないよ」と怒られます。
$ rake assets:precompile
アセットパイプラインの特徴を生かすためには事前にプリコンパイルをしておくべき。でも、プリコンパイルされていないファイルを動的にプリコンパイルするよう設定しておくこともできます。
config.assets.compile = true
ファイル名にハッシュ値がつくということは、内容が少しでも変わる度に別のファイル名で保存されるということです。以前にプリコンパイルされた古いアセットファイルが public/assets/ ディレクトリにずっと残ってしまいますので、毎回削除してからプリコンパイルした方がいいでしょう。
$ rm -rf public/assets $ rake assets:precompile
ちなみに、アセットファイル名にハッシュをつけないように設定することも可能です。
config.assets.digest = false
今回は以上です。次回はプリコンパイルの対象となるファイルについて書きます。
axlsx (gem) で xlsx ファイルをダウンロード
axlsx v1.3.4
いい加減そろそろ Excel2003 形式 (.xls) で出力し続ける理由がなくなってきてしまったので(^_^;、ようやく重い腰をあげて Excel2007 形式 (.xlsx) で出力に移行することにしました。使えそうなライブラリはないかと gem を探してみました。とりあえず目についた以下の3つをさわってみました。
3つを試してみた印象
roo は xls と xlsx の両対応という触れ込みで、No.1候補か!?と思ったんですが、xls部は spreadsheet に丸投げのようで、xls部とxlsx部とで書き方の作法がまるで違って、別々のものを使っても大して変わらない… さらに独自に作成されたxlsx部は、cell, row, column といったクラスがなく常に番地(行番号・列番号)でアクセスするという具合で、なんかかっちょ悪い感じがしました。(使用者の感想です)
他の2つ、acts_as_xlsx と axlsx は同じ作者によるライブラリのようで rails で使うなら acts_to_xlsx がおすすめと書いてありました。たとえば
Posts.where(created_at > Time.now-30.days).to_xlsx
みたいに、モデルからfindした結果をそのまま xlsx 化する場合には便利なようです。
ただ今回の自分の用途としては、表のヘッダー(表頭・表側)に色をつけたり複数のセルの結合 (merge) をしたりしたかったので axlsx でいくことにしました。
※ グラフを書いたりピボットテーブルを作ったりといったこともできるみたいです。
axlsx で xlsx ファイルを書き出す
基本的な使い方は、
- 新しいpackageを作り
- packageの中のworkbookに新しいworksheetを追加
- worksheetに1行ずつ追加
- package.to_stream.readで得られるデータをsend_dataで書き出す
といった感じです。
pkg = Axlsx::Package.new pkg.workbook do |wb| wb.add_worksheet(:name => 'シート名') do |ws| # シート名の指定は省略可 ws.add_row ['a', 1] ws.add_row ['b', 2] end end send_data(pkg.to_stream.read, :type => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", :filename => "test.xlsx")
実際にダウンロードさせる際にはMIMEタイプの設定(config/initializers/mime_types.rb)も必要ですので忘れずに。
Mime::Type.register 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :xlsx
もうちょっと細かく出力データを調整する
書式を指定する
書式の指定は、シート毎に add_style でスタイルを登録して、add_row する際に登録したスタイルを一緒に指定します。追加する行まとめて1つの書式を指定することもできますし、配列で1セルずつ指定してセル毎に書式を変えることも可能です。書式を指定しないか nil を指定するとデフォルトの書式になります。
my_style = ws.styles.add_style :fg_color=> "FF00000", :bg_color => "FFF0E6BE", :b => true, :sz => 14, :border => {:style => :thin, :color => "FF333333"}, :alignment => {:horizontal => :center, :vertical => :center} # 1行まとめて書式設定 ws.add_row ['a', 'b', 'c'], :style => my_style # セルごとに書式設定 ws.add_row ['d', 'e', 'f'], :style => [my_style, nil, nil]
色の指定について
色は16進数でのRGB表示の頭に "FF" をつけて指定する。赤(#FF0000) ⇒ "FFFF0000"、黒: #000000 ⇒ "FF000000" といった具合。ちなみにRGBの各値が同じ場合は2桁の数字で省略できる。
my_style = ws.styles.add_style :fg_color=> "FF000000", :border => {:style => :thin, :color => "33"} # "FF333333"と同じ
0パディングを維持する(セルの型を設定する)
セルの型は何も指定しないとデフォルトで Excel の「標準」型になります。基本的にはデータの値にあわせて自動的に型を判別してくれます。
date_format = ws.styles.add_style :format_code => 'YYYY-MM-DD' time_format = ws.styles.add_style :format_code => 'hh:mm:ss' ws.add_row ["Date", "Time", "String", "Boolean", "Float", "Integer"] ws.add_row [Date.today, Time.now, "value", true, 0.1, 1], :style => [date_format, time_format]
ただし、"0016" など0でパディングされた数字列は数字として解釈されてしまい、そのまま add_row すると 16 となってしまいます。そんなときは add_row をするときに types を一緒に設定すればOKです。
ws.add_row ['0016'] # ws.rows.last.cells[0].value = 16 ws.add_row ['0016'], types => [:string] # ws.rows.last.cells[0].value = "0016"
types は styles とは別に指定します。
date_format = ws.styles.add_style :format_code => 'YYYY-MM-DD' ws.add_row ['2013/03/20'], styles => date_format, types => :date
型として使用できる値は :date、:time、:float、:integer、:string、:boolean の6つです。
modelクラスの中でのselfの使い方
modelクラスの中でのselfの使い方です。railsのルールというよりはrubyのルールですが、modelの中でselfを使うときに時々不安になるのでメモっておきます。
クラスメソッドとインスタンスメソッド
modelクラスの中でメソッドを定義する際に、メソッド名の頭にself.をつけるとクラスメソッド、つけないとインスタンスメソッドになります。
class User < ActiveRecord::Base def hoge #インスタンスメソッド end def self.hoge #クラスメソッド (def User.hoge とやっても同じ意味) end end
ちなみに使い分けですが、インスタンスメソッドはそれぞれのインスタンスに対して参照・更新するようなメソッドとして使います。一方クラスメソッドは、modelクラスのレコードを検索するとか、作成されたインスタンスの数をカウントするとか、クラスの新しいインスタンスを作る等、個々のインスタンスには紐づけずクラスに対して働きかけるメソッドになります。
メソッド内でselfを使う場合
クラスメソッド内でselfを使うとクラスを指し、インスタンスメソッド内でselfはそのインスタンスになります。リーズナブルですね。
class User < ActiveRecord::Base def hoge #インスタンスメソッド end def self.hoge #クラスメソッド end def page #インスタンスメソッド self.hoge #インスタンスメソッドのhogeが呼ばれる hoge #インスタンスメソッドのhogeが呼ばれる self.class.hoge #こうするとクラスメソッドのhogeを呼べる end def self.page #クラスメソッド self.hoge #クラスメソッドのhogeが呼ばれる hoge #クラスメソッドのhogeが呼ばれる end end
ちなみにクラスメソッドを class << self の中で定義することもできるので、クラスメソッドとインスタンスメソッドがいくつも入り乱れるときは、下のように書く方が見通しがいいかもしれません。
class User < ActiveRecord::Base def hoge #インスタンスメソッド end def page #インスタンスメソッド end class << self def page #クラスメソッド end def hoge #クラスメソッド end end end