Ruby on RailsのCRUDを学ぶ。Create編。createメソッドとsaveメソッド
こちらの記事では、RubyOnRailsのメソッドを使いながらRailsでのCRUDについてざっくり説明しました。
今回は今一歩踏み込みつつ、的をしぼりつつRailsでのCreateについて学んでいきます。
createメソッドとsaveメソッド
Railsでのレコードの保存方法(永続化と言ったりもします)はふた通りです。
モデルインスタンスの生成と作成を同時におこなうcreateメソッドとあらかじめ生成されたインスタンスを保存するsaveメソッドがあります。
Bookモデルというモデルがあった場合はそれぞれこのような形でレコードを作成することができます。
create
Book.create({author: '太宰治', title: '走れメロス'})
save
book = Book.new({author: '太宰治', title: '走れメロス'})
Book.save
# saveメソッドはあらかじめ生成したインスタンスを保存するので、
# 生成してから保存までにインスタンスの変数を変更してから保存できる
二つのメソッドには、コメントにも書いたようにsaveメソッドはインスタンスの生成と保存が別の処理に別れるので その間にインスタンスの値を書き換えたりということが可能になります。 生成後のインスタンスを特に変更する必要がない場合はコードが冗長になるので多くの場合createメソッドを使います。
create/create!とsave/save! | !の有無による違い
これはActiveRecord(RailsのORM)では共通の考え方ではあるのですが、saveとsave!、createとcreate!では微妙に挙動が異なります。
メソッドの宣言レベルでは小さな違いなのですが、実装レベルでは違いを知っておくと大きな違いがでるのでRailsをやる方にはぜひとも知っておいて頂きたい内容になります。
早速本題に入るとsaveやcreateの場合は返り値としてfalse/trueのbool値を返します。一方、save!やcreate!(update!)などは、実際に作成したインスタンスを返却します。
また、ここが大きな違いなのですが!(エクストラメーション)をつけたメソッドはモデルの更新や作成に失敗した場合に例外を発生させます。
そのため!をつけた場合は、以下のように書くのは正しくありません。
if user.save!
flash.now[:info] = '保存しました。'
render :new
else
flash.now[:alert] = '保存に失敗しました。'
render :new
end
!をつけた場合は、先に書いたように例外を発生させるので処理を中断して例外を通知します。 !をつけない場合は、上のコードでも動いてくれるのですが、!をつけた場合は動きません。
このように!の有無は例外の有無とほぼイコールなのですが、これの使い訳としてはあらかじめ例外が予期できるものかどうかになります。特にトランザクションを張っている場合には必ず!をつけます。トランザクションとは、トランザクションの開始と終了の間に予期せぬ自体に陥った場合にトランザクションの開始状態にロールバックを行い、終了と同時に複数の変更を確定をすることを言います。
トランザクション内で例外を発生させない場合は、たとえレコードの保存に失敗した場合でもそのまま処理が継続してしまうので、!を使ったメソッドで例外を発生してデータ整合性を保証します。
複数レコード作成
複数のレコードを作成する場合は、
10000.times do |idx|
Blog.create!( title: "title_#{idx}", content: "Hello World", category_id: 1)
end
のようにループを回して一つずつレコードを追加していく方法がありますが、これだと1万回のクエリが発行されることになり、 効率が悪いです。こういった大量のコードをインサートをする場合多くのサービスでバルクインサートという方法で大量のレコードを作成します。
バルクインサートをSQLで表現すると、
INSERT INTO blogs (title, content, category_id)
VALUES('title_0', 'Hello World', 1),
('title_1', 'Hello World', 1),
('title_2', 'Hello World', 1),
... ('title_9999', 'Hello World', 1)
となり一回のデータベースへの問い合わせで複数のレコードを挿入できるSQLになります。 Railsでこのクエリを実行する機能はないので、activerecord-importというgemを使います。
gemの追加方法は、gemに次の行を追加してbundle installするだけです。
gem 'activerecord-import'
```
gemを追加すると、bulk insertが次のように書くことができます。
```ruby
blogs = []
10000.times do |idx|
blogs << Blog.new(title: "title_#{idx}", content: '1万回のHello World', category_id: 1)
end
Blog.import blogs
実際にバルクインサートがどれくらいか早いか測ってみると、10000行のレコード挿入で、
普通のinsert | 80.12秒 |
---|---|
bulk insert | 36.10秒 |
という感じで2倍以上速いです。
実務レベルでは、csvで大量にデータをアップロードする場合やバッチを動かして大量データを投入する場合などに違いがでてきます。 大量データを投入する処理を実装する場合はこちらの処理を検討してみましょう。
一点注意点としてactiverecord-importはバリデーションをスキップしてインサートするので、実際にはあらかじめバリデーションを施した インスタンスを保存するという形になります。
まとめ
- レコードを保存するには、saveメソッドcreateメソッドの二つがある。
- !の有無によるメソッドの挙動の違いはエラーを発生させるかどうか
- 大量レコードの挿入はバルクインサートをする
以上、今回のCreateの説明は以上になります。!の有無による挙動の違いは、activerecord全般に言えることなので特に意識してコードがかけるようになれると良いと思います。