homebrewでMySQLがインストールされた環境でmysql2 gemをbundle installする方法

毎回ハマるのでメモしておきます。

まずMySQLがインストールされている場所を特定します。

brew info mysql55

そうするとそれっぽいディレクトリ情報が表示されるので、次にRailsプロジェクト配下の.bundle/configに以下のように追記します。

BUNDLE_BUILD__MYSQL2: '--with-mysql-lib=/usr/local/opt/mysql55/lib --with-mysql-dir=/usr/local/opt/mysql55 --with-mysql-config=/usr/local/opt/mysql55/bin/mysql_config --with-mysql-include=/usr/local/opt/mysql55/include/mysql'

上記の設定値は環境によって違うと思うので、brew infoした結果で書き換えてください。

ハマったのはwith-mysql-includeオプションのところ。includeディレクトリの下にもう一階層(ここではmysqlディレクトリ)があったことに気づかず、30分以上悩みました。。

Rails – CookieOverflowが発生したときの対処法

ActionDispatch::Cookies::CookieOverflowエラー(例外)はセッションに大きすぎるデータ(4KB以上のデータ)を格納しようとしたときに発生します。flashもセッションにデータを格納する仕組みですので、大量のエラーメッセージをflashに格納する場合などは注意が必要です。

このエラーの解決方法は2つあります。1つは極力セッションを使わないこと、または使ったとしても最小限のデータを格納するのにとどめることです。この解決方法が最もスマートだと思います。セッションに大量のデータを格納するような設計には問題があります。

ただ、どうしてもセッションに大量のデータを格納しておきたい、でもCookieOverflowが発生してしまう。そんなときに2つ目の方法としてセッションデータをActiveRecord上に格納する方法があります。このメモではこの方法について解説します。

 

セッションをActiveRecordに格納する方法

まず、以下のコマンドでセッション上のデータを格納するテーブルを作成します。

rake db:sessions:create
rake db:migrate

次に config/initializers/session_store.rb の設定を以下のように変更します。

[プロジェクト名]::Application.config.session_store :active_record_store

最後にRailsサーバーを再起動して完了です。

 

環境

Rails 3.2.9

 

 

 

Rails – 親子テーブルを一度に更新する方法

parent_and_child_model_insert上記のような一画面に基本情報(宛先、住所)と複数の明細情報(物品名、数量)がある画面で入力された情報を親子関係にある基本テーブル(発送テーブル)と明細テーブル(物品テーブル)に同時に登録したい場合が業務アプリではけっこうあると思います。

Railsではこのようなケースに簡単に対応するための便利機能が用意されています。具体的にはモデルでaccepts_nested_attributes_forという設定を、ビューでfield_forメソッドを利用します。この機能を利用すると以下の2つメリットがあるので是非活用することをオススメします。

  1. ソースコードが短くてすむ
    ⇒ 後述しますが、特にコントローラー側の記述量は激減します。
  2. 明細データへのバリデーション結果が画面にキレイに表示できる
    ⇒ 親子関係をRailsに認識させることで明細データのバリデーション結果を表示するロジックを個別に実装する必要がなくなります

以下、モデル、コントローラー、ビューの順に実装方法を載せていきます。

 

モデル

まずモデルですが、物品の発送指示に対応するShippingモデル(親)と、その発送指示の明細にあたるGoodモデル(子)があるとします。それぞれのソースは以下のようになります。

# shipping.rb
class Shipping < ActiveRecord::Base

  has_many :goods
  accepts_nested_attributes_for :goods

  attr_accessible :to_name, :address

  # 子モデルの要素にもアクセスできるようにする
  attr_accessible :goods_attributes

  validates :to_name, :presence => true
  validates :address, :presence => true
end
# good.rb
class Good < ActiveRecord::Base

  belongs_to :shipping

  attr_accessible :name, :quantity

  validates :name, :presence => true
  validates :quantity, :presence => true
end

has_many、belongs_toでモデル間の親子関係を記述するのはおなじみですね。それ以外に重要なポイントが親モデルのShippingに2つあります。 accepts_nested_attributes_for :goods という記述でGoodモデルの要素をShippingモデルの子要素として認識させます。加えて、attr_accessible :goods_attributes という記述でGoodモデルへのアクセスを許可しています。

 

コントローラー

通常、親子関係のデータを処理する場合はコントローラーがごちゃごちゃになってしまうのですが、モデルに上記のような設定をしたおかげで以下のように記述量が恐ろしく少なくなります。(newメソッドとcreateメソッドを抜粋して載せます)

  def new
    @shipping = Shipping.new

    # 明細を3行分用意する(このあたりのロジックは要件によって変えてください)
    3.times do |
      good = Good.new
      @shipping.goods << good
    end
  end

  def create
    @shipping = Shipping.new(params[:shipping])

    if @shipping.save
      redirect_to @shipping
    else
      render :action => 'new'
    end
  end

ポイントは1つだけ。 @shipping.goods << good としてShippingオブジェクトの子要素のインスタンスを設定しています。

 

ビュー

ポイントはform_forの変数からfield_forメソッドを呼び出して子要素のフィールドを表現しているところです。

<h1>発送指示</h1>

<% if @shipping.errors.any? %>
<div id="error_explanation">
  <ul>
    <% @shipping.errors.full_messages.each do |msg| %>
    <li><%= msg %></li>
    <% end %>
  </ul>
</div>
<% end %>

<%= form_for(@shipping) do |shipping_form| %>
  <%= shipping_form.label :to_name %>
  <%= shipping_form.text_field :to_name %>

  <%= shipping_form.label :address %>
  <%= shipping_form.text_field :address %>

  <table>
    <tr>
      <th>物品名</th>
      <th>数量</th>
    </tr>
    <% @shipping.goods.each do |good| %>
      <%= shipping_form.fields_for :goods, good do |good_field| %>
      <tr>
        <td><%= good_field.text_field :name %></td>
        <td><%= good_field.text_field :quantity %></td>
      </tr>
    <% end %>
  </table>

<% end %>

ビューの前半にバリデーション結果を表示するためのロジックがありますが、ここをよくみてみると、 @shippingオブジェクトだけにアクセスしています。このオブジェクトには子オブジェクトであるgoodオブジェクトのバリデーション結果も含まれているため、わざわざ@shipping.goodとして子オブジェクトのバリデーション結果を取り出さずとも済みます。これがメリットの2番目として挙げたものです。

 

環境

Rails 3.2.9

参考文献

Ruby on Rails Documentation の field_forの箇所

やっぱり公式のドキュメントは鉄板ですね。

 

Rails – テーブルなしのモデルを利用する方法

RailsではモデルとDBのテーブルが1対1の関係になっていることが多いですが、対応するテーブルはないけどモデルを利用したい、というシーンがあると思います。

特に画面の入力内容が1つのテーブルに対応していない場合によく使われるとおもいます。例えば、CSVファイルの内容をインポートして各テーブルにデータを登録する場合のファイルアップロード画面用でテーブルなしモデルを使うと便利です。

このメモではこの「テーブルなしモデル」する方法を紹介します。

 

なぜ「テーブルなしモデル」を使うか

テーブルなしモデルを利用することで以下の3つのメリットが得られると思います。

  1. ActiveModelの強力なバリデーション機能を使える
    ⇒ 自分でバリデーションロジックを書かなくて済みます
  2. 他のソースコードとの統一性が保てる
    ⇒ 入力をモデル化することでform_forなどのメソッドが使えるため、テーブルありの場合のロジックと同様に処理することができます
  3. ソースコードが汚くならない
    ⇒ 無理にテーブルなしモデルを使わないで実装しようすると、既存のテーブルありモデルのソースに多くのロジックが追加され、何がしたいソースなのかわけけがわからなくなってしまいます

 

テーブルなしモデルの実装

ActiveAttr」というgemを利用すると「テーブルなしモデル」が少ない記述量で実現できます。このgemを利用しなくてもテーブルなしモデルは実現できますが、コードの可読性も高まるので利用することをオススメします。

ActiveAttrのインストールはGemfileに以下のように記述してbundle installコマンドを実行します。

gem 'active_attr'

 

このgemがインストールされていると以下のようにテーブルなしモデルを記述できます。

class Someone
  include ActiveAttr::Model

  attribute :name
  attribute :age

  validates :name, :presence => true

 end

ポイントはActiveAttr::Modelをインクルードしている点と、attributeというキーワードでカラムを定義している点です。以上でテーブルなしモデルが実現できました。

ただし、注意点があります。通常のテーブルありモデルはActiveRecordを基底クラスとしたクラスですが、ActiveAttrではActiveModelクラスを利用しています。そのため、テーブルありモデルでは使えたメソッドがテーブルなしモデルでは使えない場合もあります。(そんなにないですが)

また、モデルの属性名やバリデーションメッセージのja.ymlにおける記述場所も以下のようにActiveRecordとActiveModelでは異なっているため、注意が必要です。

ja:
 activemodel:
    attributes:
      someone:
        name: 名前
        age: 年齢
    errors:
      models:
        inventory_import:
          attributes:
            csv_file:
              blank: が入力されていません
  activerecord:
    attributes:
      item:
        name: 商品名
    errors:
      models:
        item:
          attributes:
            name:
              blank: が入力されていません

 

環境

Rails 3.2.9

参考文献

#326 ActiveAttr

 

 

Rails3.0.7から3.2.9へのバージョンアップ方法

Rails 3.0.7のアプリをRails 3.2.9にバージョンアップした際の手順と、発生した問題とその解決方法をこのメモに残します。

1.  Gemfileファイルに記述されているrailsのバージョンを変更する
変更前)gem 'rails', '3.0.7'
変更後)gem 'rails', '3.2.9'

2. Gemfile.lockファイルを削除する
Gemfile.lockファイルには以前のバージョンのgemとその依存関係が記述されているので、このファイルを削除します。

3.  古いgemを消す
[Railsアプリのroot]/vendor/bundle配下にgemをインストールしていた場合は、このディレクトリ以下のファイルを全て消します。

4.新しいGemをインストール
お馴染みの以下のコマンドを実行してGemfileで指定したgemをインストールします。
bundle install --path vendor/bundle

5. bundlerの機能を使ってRails本体の更新
以下のコマンドを実行します。
bundle update rails

6. 設定ファイル等の更新
以下のコマンドを実行します。
bundle exec rake rails:update
そうすると、バージョンアップしたRailsに対応する設定ファイルの変更が各ファイルに反映されます。もしバージョンアップ前後で差異があるファイルがあったならば、上書きするかどうか聞いてきます。「d」キーを押してEnterを押すとバージョンアップ前後での差分が見れますので、それを見ながら判断していくといいでしょう。特にroutes.rbは何も考えずに上書きしてしまうと、自分が編集した内容が初期化されてしまうので要注意です。

 

通常、ここまでRailsのバージョンアップは完了です。
しかしながら、私の環境ではそれ以外も変更しないとうまく動作しなかったので、そのトラブルシューティングを以下に載せておきます。

Gemの追加
Rails 3.1以上からAsset Pipelineの追加やjQueryの標準化が行われたので以下のGemfileに以下の内容を追加しました。

group :assets do
  gem 'sass-rails',   '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'
  gem 'uglifier', '>= 1.0.3'
end

gem 'jquery-rails'
gem 'execjs'
gem 'therubyracer'

 

Asset Pipelineへの対応
Rails 3.1以上からAsset Pipelineが登場したので、それに合わせた対応が加えて必要でした。具体的にはappディレクトリにassetsディレクトリを作成し、その配下にimages, stylesheets, javascriptsディレクトリを作成しました。そして、stylesheetsディレクトリ配下にapplication.cssを、javascriptsディレクトリ配下にapplication.jsを作成する必要があります。ここでだいぶハマったのですが、application.cssとapplication.jsは更新後のRailsのバージョンのものを使う必要があります。元々あるファイルをコピーするとJavaScriptのライブラリが読み込まれず、destoryアクションを処理できなくなってしまいます。どうやら、ファイルの先頭部分にあるコメント文が関係しているようです。そのため、私は新規にRails 3.2.9のプロジェクトを作成し、そこからapplication.cssとapplication.jsをコピーして使用しました。

Deviseでsign outができなくなったことへの対応
RailsをバージョンアップしたアプリではDeviseを使用しているのですが、バージョンアップ後になぜかsign out(ログアウト)ができなくなってしまいました。いろいろ調べた結果、以下の内容をroutes.rbに記述することで解決できました。

devise_for :users do get '/users/sign_out' => 'devise/sessions#destroy' end

 

CoffeeScript – 超簡単!マウスオーバー時にテーブルの行の背景色を変える

Railsでは標準になったCoffeScriptを使って、テーブルのある行にマウスオーバーしたらその行の背景色を変える方法を紹介します。

なんとCoffeeScriptならば約4行でこの処理が書けてしまいます。ステキじゃないの、CoffeeScript!

方法
まず、前提として以下のようなテーブルがあるとします。

<table id="huna">
  <tr>
    <td>人生</td>
    <td>うまくいかない</td>
  </tr>
  <tr>
    <td>嫁に</td>
    <td>早く会いたい</td>
  <tr>
</table>

まず、マウスオーバー時の背景色を指定するCSSを準備します。

.highlight {
  background-color: #d3deff;
}

そしてCoffeScriptですが、以下のようになります。

$ ->
  # テーブルのある行にマウスオーバーしたらその行の背景色を変える
  $("table#huna tr").mouseover ->
    $(this).addClass("highlight")
  # テーブルのある行からマウスアウトしたらその行の背景色を元に戻す
  $("table#huna tr").mouseout ->
    $(this).removeClass("highlight")

以上で実装は完了です。

上記のCoffeeScriptについて簡単に解説します。
まず1行目の「$ ->」ですが、これはbodyタグのonload属性に相当します。つまり、$->以降の記述はonload時に実行されるという意味です。

3行目はjQueryのセレクタ機能でマウスオーバーされたテーブルの行を指定しています。続く4行目で該当する行(trタグ)にクラス属性を追加しています。このクラス属性を追加することによってCSSで背景色を変えているわけです。

6,7行目は、マウスアウトした場合にクラス属性を取り除いて背景色を消しています。

このようにCoffeeScriptは少ない記述で明確に処理を指定できるので、食わず嫌いにならずに活用してみることをおすすめします。