Rails つまみぐい

Rails つまみぐい

Ruby on Rails 初学者による行き当たりばったりなメモ

仮想サーバで bin/rails コマンドとかがクッソ遅い件

VagrantVirtualBox を使って構築した仮想サーバ (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)は JavaScriptCSS、画像ファイルといった HTML に付随する細々としたファイル達(アセットファイル)を連結したり圧縮することで効率的にアクセスできるようにする仕組み(フレームワーク)です。

アセットパイプラインの役割

アセットパイプラインは以下のような役割をもっています。

1 複数の JavaScriptCSS をそれぞれ1つのファイルに連結する
⇒ ブラウザからのリクエスト数を減らす
2 JavaScriptCSS を圧縮してサイズを減らす
⇒ 文法上必要でない空白を削除したり、変数名を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 などのエンジンを使って JavaScriptCSS ファイルに展開
3 複数の JavaScriptCSS をそれぞれ1つのファイルに連結
4 JavaScriptCSS を圧縮
5 アセットファイル名にハッシュ値MD5)を付加
例)application.css ⇒ application-908e25f4bf641868d8683022a5b62f54.css
6 できあがったアセットファイルを public/assets/ ディレクトリに保存
対象ファイル:JavaScriptCSS、画像ファイル


プリコンパイルの仕方

デプロイの前にプリコンパイルをしておきましょう。コマンドラインから実行します。これをやっておかないと、アクセス時に「ファイルがプリコンパイルされていないよ」と怒られます。

$ rake assets:precompile


アセットパイプラインの特徴を生かすためには事前にプリコンパイルをしておくべき。でも、プリコンパイルされていないファイルを動的にプリコンパイルするよう設定しておくこともできます。

config.assets.compile = true


ファイル名にハッシュ値がつくということは、内容が少しでも変わる度に別のファイル名で保存されるということです。以前にプリコンパイルされた古いアセットファイルが public/assets/ ディレクトリにずっと残ってしまいますので、毎回削除してからプリコンパイルした方がいいでしょう。

public/assets/ 内に直接ファイルを作成しないように気をつけてください。

$ rm -rf public/assets
$ rake assets:precompile


ちなみに、アセットファイル名にハッシュをつけないように設定することも可能です。

config.assets.digest = false


今回は以上です。次回はプリコンパイルの対象となるファイルについて書きます。

axlsx (gem) で xlsx ファイルをダウンロード

Ruby on Rails v3.2.11
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 でいくことにしました。

※ xlsx ファイルの書き出しのみで、現時点では読み込みには対応していない模様。
※ グラフを書いたりピボットテーブルを作ったりといったこともできるみたいです。

axlsx で xlsx ファイルを書き出す

基本的な使い方は、

  1. 新しいpackageを作り
  2. packageの中のworkbookに新しいworksheetを追加
  3. worksheetに1行ずつ追加
  4. 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

(style の中にも type という別のプロパティがあるので注意)

型として使用できる値は :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