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分以上悩みました。。

FactoryGrilの書き方いろいろ

テストデータの定義に使われるFactoryGirlの書き方に関するメモです。

データ投入時に値を指定する

FactoryGirlの定義ファイルに値を記述するのではなく、データ投入時(FactoryGirl.createするとき)にどのようなデータにするかを指定する方法です。FactoryGirlの定義ファイルのカラムにシンボルを指定しておき、FactoryGirl.createするときにシンボルとともに設定したい値を指定します。

# FactoryGirlデータファイル
FactoryGirl.define do
  factory :user, :class => User do
    name: "田村くん"
    address :address
  end
end
# RSpecファイル
describe 'User' do
  before do
     @user = FactoryGirl.create(:user, address:"山形県米沢市")
  end
end

 

 

 

Pryの使い方

RubyのデバッグツールPryの使い方の覚書です。

 

デバックモード内でのコマンド

コマンド 用途
show-source デバック中のコードを表示する

 

 

 

Rails – RSpecの設定と使い方

RSpecのRailsプロジェクトへのインストール

Gemに以下の記述を追加します。

group :development, :test do
  gem "rspec-rails"
  gem "factory_girl_rails"
end

 

RSpecの初期化

以下のコマンドを実行し、RSpecのテストファイルを格納するディレクトリ等を作成します。

rails g rspec:install

 

スペックファイルの生成

例えばUserというモデルのスペックファイルを生成する場合は以下のコマンドを実行する。

rails g rspec:model user

 

RSpecによるテストの実行

array_spec.rbというスペックファイルのテストを実行する場合には以下のコマンドする。

rspec spec/model/user_spec.rb

 

データ投入時にモデルのバリデーションを無視する方法

FactoryGirl.define do
  factory :item_a, :class => Item do
    company_id 1

    # save時にvalidationをスキップする
    to_create do |instance|
      instance.save validate: false
    end 
  end
end

 

 

 

ハマりどころ

FactoryGirlファイルの場所の明示が必要

テストデータを作成するためにFactoryGirlを組み合わせて使ったのですが、ちゃんとFactoryGirlファイルを準備しているのに、その定義内容ではなくNULLがDBに投入されようとしてエラーになってしまいました。

fixtureの代替としてFactoryGirlを使用することを設定に明記していないのが原因でした。以下の一文をconfig/application.rbに追記すればエラーが発生しなくなりました。

config.generators.fixture_replacement :factory_girl, dir: 'spec/factories'

 

FactroyGirlファイルの内容を変えたときはspringの再起動が必要

springを使ってrspecテストを高速化している場合、FactoryGirlファイルの内容はrspec初回起動時のものが保持され続けるので注意が必要です。FactoryGridファイルを変更したら以下のコマンドを実行して一度springを止め、再び再起動(spring rspec)しましょう。

spring stop

 

 

 

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でよく使うけと忘れてしまいがちなテクニックたち

このメモにはRailsでよく(?)使うけど毎回忘れてしまい、Google検索するところから始めなければならないようなテクニックたちを載せておきます。

 

redirect_toするときにパラメータを渡す

実は簡単で、遷移先のパスの後ろに括弧書きでパラメータを記述すればできます。

redirect_to users_path(:name => '田村', :age => '7')

 

Assets Pipelineを使っている環境でのCSSにおけるファイルパスの書き方

CSSのbackground-imageプロパティにurlキーワードでファイルパスを指定しますが、RailsではAssets Pipelineを使っていると本番環境ではファイルパスが事前にわかりません。そこでimage-urlというキーワードを使います

background-image: image-url("tamurasouko.png")

 

グローバル定数を定義する場所

いろいろなやり方があるみたいですけど、config/initializers/constants.rbを作成してそこに定義するのが個人的にはしっくりきました。

# constants.rb
COMPANY_NAME = '田村倉庫'
PHONE_NUMBER = '0238-24-2130'

 

DBのテーブルから特定のカラムの値を取り出す

ActiveRecordのpluckメソッドを使います。

@user_names = User.where(:age => 20).pluck(:name)

ちなみにUser.all.pluckとするとエラーになるので注意。この場合はUser.pluckと記述します。

 

日付の操作

Rails、というかRubyの日付処理は以下のように非常に直感的に行えます。

current_date = Date.today
at_next_month = current_date + 1.month # 1ヶ月後の日付
at_next_year = current_date + 1.year # 1年後の日付

 

ActiveRecordで「比較」の検索条件を指定

大なり小なりといった大小を比較するときは以下のようにします。(他にもっといい方法があるような気もしますが)

target_users = User.where("age > ?", 20)

 

ActiveModelのバリデーションを実行

通常、ActiveModelのバリデーションはsaveメソッドやupdateメソッドが実行されるときに行われますが、事前にバリデーションだけを行いたい、というときには以下のようにします。(※@userはActiveModelオブジェクトです)

@user.valid?

 

バリデーションなしでsaveメソッドを実行する方法

@user.save :validate => false

 

Railsに設定したタイムゾーンで文字列をDatetime型に変換する

date = Time.parse('2013/10/23 16:53:07').to_datetime

Datetime.parse('...')としてしまうとRailsに設定したタイムゾーンを無視して変換されてしまうので注意が必要です。

 

モデルからヘルパーメソッドを呼び出す

ApplicationController.helpers.[ヘルパーメソッド名]

 

他のテーブルと結合して他のテーブルの値によってレコードを絞り込む by ActiveRecord

scope :with_role, ->(role_name) { joins(:user_roles).where(user_roles: {role: role_name})}

 

カラムを追加するときに順番を指定する

マイグレーションファイル中でafterを使って指定します。

add_colum :users, :phone_number, :string, after: :name

 

 ActiveRecordでBETWEENのように範囲を条件指定する

# 今日から1ヶ月先までに誕生日があるユーザを抽出
today = Time.current
next_month = today + 1.month
User.where(birthday: today..next_month)

 

環境

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

 

 

Rails – Deviseのコントローラをカスタマイズする方法

Railsの認証エンジン「Devise」のコントローラをカスタマイズする方法を紹介します。

 

コントローラファイルの作成

Deviseをインストールしてもそのコントローラは生成されません。Deviseが内部的に持っているコントローラが使用されます。そのため、Deviseのコントローラの挙動を変えたい場合は、そのDeviseのコントローラクラスを継承した独自のコントローラを作成する必要があります。

認証用のモデルを「User」という名前にした場合、app/controllers/usersディレクトリ配下にsessions_controller.rbとregistrations_controller.rbという2つのコントローラファイルを作成します。前者はユーザのログイン/ログアウト機能のコントローラで、後者はユーザ登録機能のコントローラになります。それぞれソースコードは以下のようにします。

class Users::SessionsController < Devise::SessionsController

  def new
    super
  end

  def create
    super
  end
end
class Users::RegistrationsController < Devise::RegistrationsController

  def new
    super
  end

  def create
    super
  end

end

ポイントはクラスの宣言部分です。 Devise::SessionsControllerとDevise::RegistrationsControllerをそれぞれ継承して、親クラスのnewメソッドとcreateメソッドをオーバーライドしています。独自の処理を加えたい場合は、これらのメソッド内に処理を追加すればよいわけです。

 

Deviseで使用するコントローラファイルの指定

上記で作成したコントロールファイルをDeviseが使用するように設定を記述する必要があります。config/routes.rbファイルのDeviseに関する設定を以下のように変更します。

devise_for :users, :controllers => {
  :sessions => 'users/sessions',
  :registrations => 'users/registrations'
}

:controllersオプションで使用するコントローラを指定しています。ちなみに'users/sessions'のusersはapp/controllers/usersディレクトリのことを表し、sessionsはsessions_controller.rbファイルの先頭を表しています。そのため、実際のディレクトリ構造とファイル名とがこの設定と一致していないとうまく動きません。

 

使用するコントローラが切り替わっていることを確認

最後に以下のコマンドを実行してDeviseが使用するコントローラが今回作成したコントロールになっているか確認しましょう。
> rake routes

devise_controller_changed_routes

 

環境

Rails 3.2.9
Devise 2.1.2