ウェブエンジニア珍道中

日々の技術的に関する経験を書いていきます。脱線もしますが助けになれば幸いです。

RailsのActiveRecordでcreate_or_updateを分岐なしでスマートにする方法

最近見つけたので紹介します。 今回は本(Book)モデルを例題にします。

方法

こんな感じでできます。

book = Book.find_or_initialize_by(id: 1)
# book = Book.find_or_initialize_by(name: "こころ") # idじゃなくてもOK

book.update_attributes(
  name:   "こころ",
  author: "夏目漱石"
)

解説

find_or_initialize_byメソッドは引数で与えられた要素を使って検索を行います。

見つかった時にはインスタンスを返し、見つからなかった場合は新しくインスタンスを作って返します。

あとは update_attributesメソッドを使ってDBに値を保存してます。このメソッド内でcreateかupdateの判定はしてくれているようです。

これでif文がぐっと減りますね!

Railsでテスト用DBのテーブル定義が開発環境と違う時に打つコマンド

最近RSpecを使ってテストコードを書いてます。それで開発環境でdb:rollbackとか色々していたらテスト用のDBと内容がズレてきてしまい、「えーい、今の開発環境に合わせんかい!」という時に打つと良いコマンドを見つけたのでメモ。

Rails5

bundle exec rails db:environment:set RAILS_ENV=test
bundle exec rails db:migrate:reset RAILS_ENV=test

Rails4

bundle exec rake db:environment:set RAILS_ENV=test
bundle exec rake db:migrate:reset RAILS_ENV=test

bundle execはシステムのgemに直接インストールしている人は必要ありません。

Rails5でnested attributesに詰まった話

早速Rails5を触ってると、nested attributes周りで詰まったので紹介します。

症状

  • 全ての項目を入れてもバリデーションに引っかかって保存できない。

モデル

ここではモデル名をParent, Childとします。

class Parent < ApplicationRecord
  has_many :children
  accepts_nested_attributes_for :children
end
class Child < ApplicationRecord
  belongs_to :parent
end

対応

以下の用に書くと動くようになりました。

class Child < ApplicationRecord
  belongs_to :parent, optional: true
end

原因

@parent.errorsでバリデーションの内容を見てみると
子テーブルにおいて親テーブルのIDが無いという内容でした。

で、調べ進めていくとRails5による新しい挙動が原因のようです。

qiita.com

つまりChildモデル内でparent_idが自動的にrequired: true
バリデーションがかかるようになっていたみたいです。

そしてRailsでバリデーションと保存の順番は大まかに

  1. Parentのバリデーション
  2. Childのバリデーション
  3. Parentの保存処理
  4. Childの保存処理

の順で行われ、Parentの保存がされて生まれたIDが
Childのparent_idに入ります。

しかし、Childモデル内でparent_idが必須になっているために
はじめのParentのバリデーションの時点で
「Childにparent_idねーよ!」と怒って終了するようです。

なのでbelongs_tooptional: trueを付けて
parent_id必須を無くしたという感じです。

「いや、そもそもrequired: trueつけんなよ」 という方は
こちらに取り方が書いてました。

Rails5 の落とし穴 - zeny.io

設定でRails4と同じ挙動にできるみたいですね。
どっちがいいのやら。

他にもRails5でハマった時は紹介したいと思います。