rails 関連の備忘録

rails関連の備忘録をまとめてく。

## 環境
```
$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

$ rails -v
Rails 5.0.0.1
```

## migrate

### -add_column等のmigrate で NameError: uninitialized constant が出たとき。

- 型がintやvarcharではないか。integer、stringが正しい。
- migrationファイル名と実際の処理対象のテーブル名（model名）が違う場合。合わせる。

## view

viewヘルパーのフォーマットは、options と html_options に別れてるのが曲者ですよね。。

```ruby:
# 例
select(object, property, choices, options = {}, html_options = {})
```
### -form_for のオプションと、プルダウンのonChangeメソッド slimで

```ruby:
# GETメソッドでsubmit。
# form のidを指定。
= form_for @object, html: { method: 'GET', enforce_utf8: false, id: 'form_id' } do |f|
  = select :status, :status, array, { selected: @selected_status, include_blank: 'all' }, {:onchange=> 'change_select();'}
```
※`include_blank`は、プルダウンの先頭に空のoptionを入れるオプションだが、今回は「all」という文字列を先頭のoptionに出力している。本来`prompt`でやることだが、プルダウン選択後のsubmitでロードした後になぜか prompt が効かずに非表示となったのでこの対処をとった。

### -time_select を非表示にする
```
<%= f.time_select :flight_at_from, {}, {style: "display: none;"} %>
```
参考
http://apidock.com/rails/ActionView/Helpers/DateHelper/time_select

### -中間モデルのcollection_select のCRUD
collection_selectの基本的な書き方。
serviceモデルとprefectureモデルの中間モデル。

```ruby:view
<div class="field">
  <%= f.label :prefecture_id %><br />
  <%= collection_select :service_prefecture, :prefecture_id, Prefecture.all, :id, :name %>
</div>
```
参考
[[Rails] has_many through (nested table)
](http://qiita.com/sasarkyz/items/b17c8a108b3075a015c3)

### -登録した改行をそのまま画面に表示
textarea等から複数行の文字列を入力した場合の表示。
`simple_format`メソッドを使う。

```ruby:view
<%= simple_format(text) %>
<!-- htmlエスケープしたい場合は html_escapeと合わせて。 -->
<%= simple_format(h(text)) %>
```
参考
[Railsでタグをエスケープしつつ、改行を含む複数行のTextを表示したい - Qiita](https://qiita.com/t_oginogin/items/0164ad76b753fdff9902)

###　-submit button押下時にdisableしてボタン名を変更するdata-disable-withオプション

```
<%= f.submit "送信する", data: { disable_with: "送信中..." } %>
```

参考: [\[Rails\]submitタグにつけておきたいdisable\_withオプション \- Qiita](https://qiita.com/sue738/items/09f569bdc3a73d26df88)

### -link_toタブを使ってボタン名を改行させる
section tabを使用。

```erb
<%= link_to '#', class:"button " do %>
　　<section>
　　　　<p>登録！！</p>
　　　　<p class="sub-text">〜今月末まで</p>
　　</section>
<% end %>
```

参考：[\[Ruby on Rails\] link\_to の使い方 \- Qiita](https://qiita.com/wada811/items/cd952471b75355079ca2#link_to-%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9-1)

### -date_selectで年月のみ表示する

discard_dayオプションをtrueにすると、日が非表示となる。
型はdate型で、日の値には「1」が入る。

```erb
<%= raw sprintf(
  f.date_select(
    :date_from,
    use_month_numbers: true,
    date_separator:    '%s',
    discard_day: true),
  '年 ') + '月' %>
```

参考
- [date\_select \- リファレンス \- \- Railsドキュメント](http://railsdoc.com/references/date_select)
- [Rails の date\_select でつくるセレクトボックスを「年」「月」「日」で区切る \- 彼女からは、おいちゃんと呼ばれています](http://blog.inouetakuya.info/entry/2014/01/31/233000)

### -radio_buttonの書き方と初期値の設定
Userモデルの編集画面。
性別の初期値がmale、データが存在する場合はデータを反映する。
（sexカラムが必須の場合）

```rb:model/User.rb
#  sex :integer
class User < ApplicationRecord
  enum sex: { male: 0, female: 1}
```
```slim:users/edit.html.slim
= simple_form_for(@user) do |f|
  ・・・
  
  label
    = f.radio_button :sex, :male, { checked: f.object.new_record? || f.object.male? }
    = User.sexes_i18n[:male]
  label
    = f.radio_button :sex, :female
    = User.sexes_i18n[:female]
```
```rb:config/locales/ja.yml
ja:
  enums:
    user:
      sex:
        male: 男性
        female: 女性
```

### -HTMLタグをエスケープしつつ、連続改行も反映する
`simple_format`では連続した改行がつぶされてしまう。
連続した改行も反映させるために、以下の処理を行う。
改行はmacでもwindowsでも対応できるように正規表現で。

```slim
= safe_join(news_mail_body.split(/\R/), tag(:br))
```
参考
- [Ruby \- <%= simple\_format\(\) %>　で連続改行が反映されません\(Railsです\)｜teratail](https://teratail.com/questions/114594)
- [Ruby で文字列を改行コードで区切る \- valid,invalid](https://ohbarye.hatenablog.jp/entry/2016/06/07/215425)
- [Railsでタグをエスケープしつつ、改行を含む複数行のTextを表示したい \- Qiita](https://qiita.com/t_oginogin/items/0164ad76b753fdff9902)

## slim
### -JSとsassの記述
```ruby
javascript:
  function change_select(){
    f = document.forms['form_name'];
    f.submit();
  }

sass:
  .is-panel
    display: inline-block
    width: 30%
    vertical-align: top
```

## model
### -オブジェクトの一時的な更新（保存はされない）。
コントローラ等でsaveする前に任意の変更を反映。
元のオブジェクトを画面に返す場合は `reload` すること。

```ruby
@object.assign_attributes(params)
@object.assign_attributes({key: 'value'})
```

参考
[assign_attributes (ActiveRecord::AttributeAssignment) - APIdock](https://apidock.com/rails/v3.2.3/ActiveRecord/AttributeAssignment/assign_attributes)

### -nilまたは空白の場合に特定の値を返す
```ruby
blank_or_value.presence || 'Value'
```

### -オブジェクトの配列から条件にあったものを抽出する。
selectメソッド。　　
更新前のバリデーション等に使う。

```ruby
class Group < ActiveRecord::Base
  has_many :members
end
class Member < ActiveRecord::Base
  belongs_to :group
end

# groupにはマネージャーが最低1人必要、とか。
group.members.select(position: :manager)
# [#Members]
```
参考　　
[Rubyで条件に合う要素を抽出する方法：select, select!](http://uxmilk.jp/24791)

### - enumのi18n対応
[enum_help](https://github.com/zmbacker/enum_help)を使う。

```
# app/models/order.rb
class Order < ActiveRecord::Base
  enum status: { "nopayment" => 0, "finished" => 1, "failed" => 2, "destroyed" => 3 }

end
```
```
# config/locales/ja.yml
ja:
  enums:
    license_holder:
      status:
        nopayment: 支払待ち
        finished: 支払済み
        failed: 支払失敗
        destroyed: 削除済
```
```
# 使用方法
Order.statuses_i18n[:nopayment]
# => 支払待ち
```

```
# selectboxでの使い方
= f.select :status, Order.statuses_i18n.invert, {}, { class: 'select' }
```

## Validation
### -validationで、変更前の値をチェックする。
変更される前の値は「属性名_was」で取得する。

```ruby
#Userモデルのnameカラムを「はなこ」から「たろう」へ変更。
user.name_was #たろう

#値の判定
return if user.status_was.to_sym == :unpublished
```
参考・ほか
[Railsで変更前の値、型変換前の値、などなど](http://www.techscore.com/blog/2012/12/13/rails%E3%81%A7%E5%A4%89%E6%9B%B4%E5%89%8D%E3%81%AE%E5%80%A4%E3%80%81%E5%9E%8B%E5%A4%89%E6%8F%9B%E5%89%8D%E3%81%AE%E5%80%A4%E3%80%81%E3%81%AA%E3%81%A9%E3%81%AA%E3%81%A9/)

### - 日付が正しいかチェックするカスタムバリデータを実装。

```app/validators/date_validator.rb
class DateValidator < ActiveModel::EachValidator
  # 日付の値が入っているか、日付が正しいかを判定する。
  def validate_each(record, attribute, value)
    value.to_date
  rescue
    record.errors.add(attribute, options[:message] || 'が空か不正です。正しい日付を入力してください。')
  end
end
```

```app/models/hoge.rb
validates :expires_on, date: true
```
参考
[3.9. カスタムバリデータの作成 | 3. バリデーション(5) | TECHSCORE(テックスコア)](http://www.techscore.com/tech/Ruby/Rails/model/validation/5#9)
[[Ruby] 例外処理を実装する時のrescue書き方3パターン - qiita](https://qiita.com/sue738/items/d0d6ca135c4546222159)

### - validationを切り分ける
saveメソッドのcontextパラメータで切り分け。  
updateにはcontextパラメータが無いので、attributesでデータを渡してからsaveメソッドを使う。

```rb
# user_controller
# password登録処理
def register_password
  @user.attributes = user_params
    if @user.save(context: :register_pass)
...
end

# userモデル
validates :password, presence: true, confirmation: true, on: [:register_pass]
validates :password_confirmation, presence: true, on: [:register_pass]
```

参考：[Rails4でModelのvalidatesを切り替える \- Qiita](https://qiita.com/HamaTech/items/e4016224a134f1b35535)

## Action Mailer
### -メール送信時に条件（環境）によって処理を差し込む（Subjectに文言を追加する）。
モジュールに処理を書込み、 `config/initializers` にそれを呼び出す処理を追加する。

```
# ElasticBeanstalk に設定した環境変数をsubjectの先頭に差し込む。
module Modulename
  class EmailInterceptor
    def self.delivering_email(message)
      message.subject.insert(0, "[#{Rails.application.config.eb_env}]")
    end
  end
end
```
```
# config/initializers/mailer.rb
# 本番でない場合、差し込み処理を行う。
ActionMailer::Base.register_interceptor(Modulename::EmailInterceptor) unless Rails.application.config.rails_env == 'production'
```

参考： 
- [メールを配信直前に加工する](https://railsguides.jp/action_mailer_basics.html#%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%92%E9%85%8D%E4%BF%A1%E7%9B%B4%E5%89%8D%E3%81%AB%E5%8A%A0%E5%B7%A5%E3%81%99%E3%82%8B)
- [Rails5: production環境でのAutoloadの廃止 \- Qiita](https://qiita.com/joooee0000/items/3ab0f3d791e0d0beb639)

### -view のURL表示で使うhelper

xxx_path ではなく、xxx_urlを使う。

## Rspec
### -基本構成
```ruby
RSpec.describe ObjectName, type: :model do
  let(:user) { FactoryGirl.create(:user) }
  describe 'hoge' do
    before do
      # devise でlogin
      login_as(user, scope: :user)
    end
    context 'fuga' do
      it 'is poyo' do
        ...
      end
    end
  end
end
```
- マッチャメモ

```ruby
# datetime を不等号で比較。
expect(hoge_at).to be >= Time.current

# ｄａｔｅｔｉｍｅの範囲確認。
expect(hoge_at.to_i).to be_within((Time.current - 1.minute).to_i).of(Time.current.to_i)
```
- login_asメソッドで`uncaught throw :warden`エラー    
deviseのエラー。  
createしたuserのデータが不正だった。  
confirmed_atがnilだったため、値を入れてあげるとvisitできるようになった。  
参考）[ArgumentError: uncaught throw :warden が 出た場合 \- Qiita](https://qiita.com/ms2sato/items/9c8b4784dca5f4e14628)


### -Mock
- 基本系

特定のオブジェクトのメソッド（例えばAPIにリクエストするメソッド）の返り値をmock（又は任意の値）に置きかける。
mockのメソッドもmockか任意の値に置き換える。

```ruby
api_mock = double('api mock')
allow(ApiObject).to receive(:method).and_return(api_mock)
allow(api_mock).to receive(:mock_method).and_return(api_mock)
allow(api_mock).to receive(:mock_method?).and_return(true)
```
参考
[使えるRSpec入門・その3「ゼロからわかるモック（mock）を使ったテストの書き方」](http://qiita.com/jnchito/items/640f17e124ab263a54dd)  

- メソッドを通ったか確認

```ruby
mock = double('mock')
allow(object).to receive(:method).and_return(mock)
expect(mock).to have_received(:method).once
```
参考
[rspec3でメソッドが呼び出されたかテストする](http://qiita.com/_am_/items/398b0782fa3754ff3878)

### -feature test

#### メモ
- visitしたページを確認

```ruby
visit hoge_path
p page.html
```

- プルダウンの選択。

```ruby
select [optionの表示名], from: [select name]
# 例
# select 'バナナ', from: 'fruits'
```
参考)[Rspec \+ Capybara \| <select value=""> の value を指定して選択状態にする \- Qiita](https://qiita.com/Yinaura/items/f26cffc6c5432646895e)

- capybara で、hostを変更してアクセスする。

```ruby
feature 'signup admin' do
  given(:first_admin) { Admin.first }
  before do
    Capybara.default_host = 'http://admin.example.com'
    Capybara.app_host = 'http://admin.example.com'
    login_as(first_admin, scope: :admin)
  end
  # 終わったら戻すこと。
  after do
    ActionMailer::Base.deliveries.clear
    Capybara.default_host = 'http://www.example.com'
    Capybara.app_host = nil
  end
```
参考）[サブドメインを指定して、featureスペックを実行する \- Qiita](https://qiita.com/Oakbow/items/3da1b20ff17e5e192f47)

## gem 
### -[devise](https://github.com/plataformatec/devise)
アカウント認証・セッション管理を行う。　
#### -導入
`rails g devise:install`を実行し、configファイルを作成。
コンソールに出てくる案内に従って設定。
`rails g devise User` でUserモデルを作成。
参考：[\[\*Rails\*\] deviseの使い方（rails5版） \- Qiita](https://qiita.com/cigalecigales/items/f4274088f20832252374)

#### -複数モデルを管理していて、それぞれにviewのカスタマイズを行いたい場合。
`config/initializers/devise.rb` で　`config.scoped_views = true` とすることで、モデルごとにviewのオーバーライドができる。
参考：https://github.com/plataformatec/devise#configuring-views

`app/views/[model名]s/mailer/[メソッド名].html.erb` がメール文面となる。
が、[subjectを分けるのはやや面倒そう](https://stackoverflow.com/questions/16742863/add-dynamic-value-in-devise-email-subject)なので、通常のaction_mailerを使うのが一番簡単な気もする。

#### -controller/view のカスタマイズ
```
# controllerの生成
$ rails g devise:controllers [model(スネークケース複数形）]
# viewの生成
$ rails g devise:views [model(スネークケース複数形）]
```

#### -devise.ja.ymlのダウンロード
[I18n · plataformatec/devise Wiki](https://github.com/plataformatec/devise/wiki/I18n) から、自身のバージョンにあったものを確認。
又は以下。
[devise\-i18n/ja\.yml at master · tigrish/devise\-i18n · GitHub](https://github.com/tigrish/devise-i18n/blob/master/rails/locales/ja.yml)

```
# バージョンの確認
$ bundle show | grep devise
```
GistのRawボタンよりRawの画面に遷移し、URLをコピー、curlでダウンロード。

```
$ curl -s https://gist.githubusercontent.com/kaorumori/7276cec9c2d15940a3d93c6fcfab19f3/raw/a8c4f854988391dd345f04ff100441884c324f2a/devise.ja.yml -o config/locales/devise.ja.yml
```

### -[kaminari](https://github.com/kaminari/kaminari)
viewに出力するオブジェクトの一覧をページングする。
- 基本形

```ruby
# Controller
@objects = Object.page(params[:page]).per(10)

# view
# ページングリンクを表示。
<%= paginate @objects %>
<%# トータル件数と今表示している件数を表示。　%>
<%= page_entries_info @objects %>

# model
# 1ページあたりの表示件数を指定。デフォルトは25。
paginates_per 10
```

`paginate` でエラーが出る場合は、 `pagenate` と綴間違いしてないかチェック！！！（涙

### - [whenever](https://github.com/javan/whenever)
cronのbatchを生成するgem。

- バッチを実行する値を好きな値に設定する。  
値を直に書かないといけないよう。

```ruby
every '2,7,12,17,22,27,32,37,42,47,52,57 * * * *' do
  command "cd /path/to/project && bundle exec rake hoge:fuga"
end
```
参考:[ruby - whenever gem and scheduling a job every n min starting at an offset - Stack Overflow](https://stackoverflow.com/questions/6311233/whenever-gem-and-scheduling-a-job-every-n-min-starting-at-an-offset)

###　- i18n

#### - 導入

デフォルトのロケールファイルをダウンロード。

```
curl -s https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml -o config/locales/ja.yml
```

イニシャライザを作る。

```ruby:config/initializers/i18n.rb
I18n.available_locales = %i(en ja)
I18n.enforce_available_locales = true
I18n.default_locale = :ja
I18n.locale = :ja
```
参考：[i18nについて \| 酒と涙とRubyとRailsと](http://morizyun.github.io/ruby/rails-function-i18n-internationalization.html)

#### - モデルから項目名（アトリビュート名）を参照する。

```config/locales/ja.rb
ja:
  activerecord:
    # モデル名
    models:
      user: 顧客
    # モデルごとのアトリビュート名
    attributes:
      user:
        name: 名前
        address: 住所
  # 全モデル共通のアトリビュート名
  attributes:
    created_at: 作成日時
    updated_at: 更新日時
```
```ruby
> User.model_name.human
=> "顧客"
> User.human_attribute_name(:name)
=> ”名前”
> User.human_attribute_name(:created_at)
=> "作成日時"

# 或いは、尋常に階層を書く。
> I18n.t('activerecord/attributes/user/name')
=> ”名前”
```

参考：[あなたはいくつ知っている？Rails I18nの便利機能大全！|モデル名やモデルのアトリビュート名を取得する](https://qiita.com/Kta-M/items/bd4ba36a58ad602a9d8b#%E3%83%A2%E3%83%87%E3%83%AB%E5%90%8D%E3%82%84%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AE%E3%82%A2%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%83%88%E5%90%8D%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)

### -[annotate](https://github.com/ctran/annotate_models/blob/develop/README.rdoc)

modelのファイルにschema情報を書き出してくれる。
`rails g annotate:install`しておかないと、migrate時に更新されない。

```
# 導入
$ bundle exec annotate

# rakeファイル作成
$ bundle exec rails g annotate:install
# => create  lib/tasks/auto_annotate_models.rake
```

### -[better\_errors](https://github.com/charliesome/better_errors)
#### 導入
```ruby:config/environmentes/development.rb
BetterErrors::Middleware.allow_ip! '0.0.0.0/0'
```

## log
### - `less` コマンドに　`-R` オプションをつける。（ `-r` でもいい。）
railsのログにはデフォルトでANSIカラーのエスケープシーケンスがついているので、これを反映する。

```
less -R log/production.log
```

## その他
### -Object#presence メソッド
オブジェクトが存在する場合はオブジェクトを、存在しない場合は別の値を返す書き方。

```ruby
name = params[:name].presence || '名無し'
```
参考
http://blog.livedoor.jp/sasata299/archives/51718602.html

### -railsのパーシャルビューのみ抽出する

```
find app/views/www/ -type f -name '_*erb'
```
### -ルーティングのパスをコンソールで確認。
```
pry(main)> m=Message.first
pry(main)> app.message_path(m)
=> "/messages/XXXXX"
```
参考：[Rails でリンクパスを生成する方法色々・・とRails console で 生成される path を確認したい時 - Qiita](https://qiita.com/mm36/items/f266977e12df9d1dc548)


### -datetime型のフォーマット設定

`config/initializers/time_formats.rb` を作成。

```ruby:config/initializers/time_formats.rb
Time::DATE_FORMATS[:default] = '%Y/%m/%d %H:%M'
Time::DATE_FORMATS[:datetime] = '%Y/%m/%d %H:%M'
Time::DATE_FORMATS[:date] = '%Y/%m/%d'
Time::DATE_FORMATS[:time] = '%H:%M:%S'
Date::DATE_FORMATS[:default] = '%Y/%m/%d'
```
```ruby:使用法
> Time.now.to_s
 => "2014/03/03 15:22" 

> Time.now.to_s(:date)
 => "2014/03/03" 

> Time.now.to_s(:datetime)
 => "2014/03/03 15:23" 

> Time.now.to_s(:time)
 => "15:23:07" 
```
参考：[日付/時間フォーマットのデフォルトを設定 - Qiita](https://qiita.com/Vit-Symty/items/399c77d1fd681b77d593)