LoginSignup
13
8

More than 3 years have passed since last update.

rails 関連の備忘録

Last updated at Posted at 2017-06-20

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 に別れてるのが曲者ですよね。。

# 例
select(object, property, choices, options = {}, html_options = {})

-form_for のオプションと、プルダウンのonChangeメソッド slimで

# 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モデルの中間モデル。

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)

-登録した改行をそのまま画面に表示

textarea等から複数行の文字列を入力した場合の表示。
simple_formatメソッドを使う。

view
<%= simple_format(text) %>
<!-- htmlエスケープしたい場合は html_escapeと合わせて。 -->
<%= simple_format(h(text)) %>

参考
Railsでタグをエスケープしつつ、改行を含む複数行のTextを表示したい - Qiita

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

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

参考: [Rails]submitタグにつけておきたいdisable_withオプション - Qiita

-link_toタブを使ってボタン名を改行させる

section tabを使用。

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

参考:[Ruby on Rails] link_to の使い方 - Qiita

-date_selectで年月のみ表示する

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

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

参考
- date_select - リファレンス - - Railsドキュメント
- Rails の date_select でつくるセレクトボックスを「年」「月」「日」で区切る - 彼女からは、おいちゃんと呼ばれています

-radio_buttonの書き方と初期値の設定

Userモデルの編集画面。
性別の初期値がmale、データが存在する場合はデータを反映する。
(sexカラムが必須の場合)

model/User.rb
#  sex :integer
class User < ApplicationRecord
  enum sex: { male: 0, female: 1}
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]
config/locales/ja.yml
ja:
  enums:
    user:
      sex:
        male: 男性
        female: 女性

-HTMLタグをエスケープしつつ、連続改行も反映する

simple_formatでは連続した改行がつぶされてしまう。
連続した改行も反映させるために、以下の処理を行う。
改行はmacでもwindowsでも対応できるように正規表現で。

= safe_join(news_mail_body.split(/\R/), tag(:br))

参考
- Ruby - <%= simple_format() %> で連続改行が反映されません(Railsです)|teratail
- Ruby で文字列を改行コードで区切る - valid,invalid
- Railsでタグをエスケープしつつ、改行を含む複数行のTextを表示したい - Qiita

slim

-JSとsassの記述

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 すること。

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

参考
assign_attributes (ActiveRecord::AttributeAssignment) - APIdock

-nilまたは空白の場合に特定の値を返す

blank_or_value.presence || 'Value'

-オブジェクトの配列から条件にあったものを抽出する。

selectメソッド。  
更新前のバリデーション等に使う。

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!

- enumのi18n対応

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」で取得する。

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

#値の判定
return if user.status_was.to_sym == :unpublished

参考・ほか
Railsで変更前の値、型変換前の値、などなど

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

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(テックスコア)
[Ruby] 例外処理を実装する時のrescue書き方3パターン - qiita

- validationを切り分ける

saveメソッドのcontextパラメータで切り分け。

updateにはcontextパラメータが無いので、attributesでデータを渡してからsaveメソッドを使う。

# 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

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'

参考:
- メールを配信直前に加工する
- Rails5: production環境でのAutoloadの廃止 - Qiita

-view のURL表示で使うhelper

xxx_path ではなく、xxx_urlを使う。

Rspec

-基本構成

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
  • マッチャメモ
# datetime を不等号で比較。
expect(hoge_at).to be >= Time.current

# datetimeの範囲確認。
expect(hoge_at.to_i).to be_within((Time.current - 1.minute).to_i).of(Time.current.to_i)

-Mock

  • 基本系

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

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)を使ったテストの書き方」

  • メソッドを通ったか確認
mock = double('mock')
allow(object).to receive(:method).and_return(mock)
expect(mock).to have_received(:method).once

参考
rspec3でメソッドが呼び出されたかテストする

-feature test

メモ

  • visitしたページを確認
visit hoge_path
p page.html
  • プルダウンの選択。
select [optionの表示名], from: [select name]
# 例
# select 'バナナ', from: 'fruits'

参考)Rspec + Capybara | の value を指定して選択状態にする - Qiita

  • capybara で、hostを変更してアクセスする。
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

gem

-devise

アカウント認証・セッション管理を行う。 

-導入

rails g devise:installを実行し、configファイルを作成。
コンソールに出てくる案内に従って設定。
rails g devise User でUserモデルを作成。
参考:[*Rails*] deviseの使い方(rails5版) - Qiita

-複数モデルを管理していて、それぞれに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を分けるのはやや面倒そうなので、通常のaction_mailerを使うのが一番簡単な気もする。

-controller/view のカスタマイズ

# controllerの生成
$ rails g devise:controllers [model(スネークケース複数形)]
# viewの生成
$ rails g devise:views [model(スネークケース複数形)]

-devise.ja.ymlのダウンロード

I18n · plataformatec/devise Wiki から、自身のバージョンにあったものを確認。
又は以下。
devise-i18n/ja.yml at master · tigrish/devise-i18n · GitHub

# バージョンの確認
$ 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

viewに出力するオブジェクトの一覧をページングする。
- 基本形

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

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

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

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

- whenever

cronのbatchを生成するgem。

  • バッチを実行する値を好きな値に設定する。
    値を直に書かないといけないよう。
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

 - i18n

- 導入

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

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

イニシャライザを作る。

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と

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

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

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

参考:あなたはいくつ知っている?Rails I18nの便利機能大全!|モデル名やモデルのアトリビュート名を取得する

-annotate

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

導入

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 メソッド

オブジェクトが存在する場合はオブジェクトを、存在しない場合は別の値を返す書き方。

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

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

config/initializers/time_formats.rb を作成。

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'
使用法
> 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

13
8
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
8