一連の記事の目次
rails、君に決めた!!~目次
Model
Modelの理解
ApplicationRecordとActiveRecord
Rails4から5になったことによる仕様変更の話っぽい。
Rails4以前
class User < ActiveRecord::Base
# do something
end
Rails5
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
class User < ApplicationRecord
# do something
end
ActiveRecordをApplicationRecordに継承させるっていうワンクッションが挟むようになってる。今までは共通処理を実装する時、ActiveRecordに変更を加える必要があったけど、ApplicationRecordに変更を加えれば良くなった。
何が嬉しい?
→正直よくわからないけど、大元のActiveRecordに変更が入ると、これを継承する全クラスに変更が影響しちゃってよくないよね、ってことかな
命名規則
Table名
→snake_caseの複数形
モデル名
→単数形
スネークケース:単語間を_でつなぐこと
bin/rails g model user name:string email:string
generatorでモデル名を指定する時、userのように単数形にする。この時生成されるテーブル名はusersと複数形になる。
ActiveRecordの基本的なメソッド
事前準備として以下を実行
bin/rails g model user name:string:index email:string
bin/rails db:migrate
ActiveRecordのメソッドの実験場として
bin/rails c --sandbox
を使う。--sandbox
ってなんぞや。
--sandboxオプションをつけることでコンソール終了時にデータベースへの変更は全てロールバックされる
便利〜〜
では、個々のメソッドを見ていこう
Create
INSERTと同じ
2パターンある
- new + save
[1] pry(main)> user = User.new(name: 'foo')
=> #<User:0x007ff566df3b18 id: nil, name: "foo", email: nil, created_at: nil, updated_at: nil>
[2] pry(main)> user.save
(0.5ms) SAVEPOINT active_record_1
SQL (0.4ms) INSERT INTO `users` (`name`, `created_at`, `updated_at`) VALUES ('foo', '2018-03-21 14:24:31', '2018-03-21 14:24:31')
(0.2ms) RELEASE SAVEPOINT active_record_1
=> true
- create
[3] pry(main)> User.create(name: 'bar')
(0.4ms) SAVEPOINT active_record_1
SQL (0.4ms) INSERT INTO `users` (`name`, `created_at`, `updated_at`) VALUES ('bar', '2018-03-21 14:25:03', '2018-03-21 14:25:03')
(0.2ms) RELEASE SAVEPOINT active_record_1
=> #<User:0x007ff565e9e078
id: 2,
name: "bar",
email: nil,
created_at: Wed, 21 Mar 2018 14:25:03 JST +09:00,
updated_at: Wed, 21 Mar 2018 14:25:03 JST +09:00>
何が違うのか?
→戻り値がかなり異なる
save
検証に成功した時のみtrueを返し、失敗した場合データベースに保存されずにfalseを返す
create
検証は行うが、成功失敗に関わらずモデルのインスタンスを返す。エラー情報を含んだオブジェクトが返却されるので、APIのレスポンス構築の時なんかは便利。
Read
SELECTと同じ
- all
全レコードの取得
[4] pry(main)> User.all
User Load (0.4ms) SELECT `users`.* FROM `users`
=> [#<User:0x007ff567101940
id: 1,
name: "foo",
email: nil,
created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>,
#<User:0x007ff5670b81a0
id: 2,
name: "bar",
email: nil,
created_at: Wed, 21 Mar 2018 14:25:03 JST +09:00,
updated_at: Wed, 21 Mar 2018 14:25:03 JST +09:00>]
- first
idが最も小さい先頭のレコードを取得。0件の場合、nilを返す
[6] pry(main)> User.first
User Load (0.4ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User:0x007ff5659b8068
id: 1,
name: "foo",
email: nil,
created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>
- find 指定したidのレコードを取得。複数idも可。該当レコードがない場合、ActiveRecord::RecordNotFoundの例外を発生させる
[11] pry(main)> User.find(0)
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 0 LIMIT 1
ActiveRecord::RecordNotFound: Couldn't find User with 'id'=0
from /Users/suzukitarou/ruby/rails_app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.5/lib/active_record/core.rb:189:in `find'
[12] pry(main)> User.find(1)
User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User:0x007ff569995758
id: 1,
name: "foo",
email: nil,
created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>
- find_by
指定条件にマッチしたレコードが存在すれば1件のみ取得し、レコードが存在しなければnilを返す
[16] pry(main)> User.find_by(name: 'bob')
User Load (2.6ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = 'bob' LIMIT 1
=> nil
[17] pry(main)> User.find_by(id:1)
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User:0x007ff56727ce00
id: 1,
name: "foo",
email: nil,
created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>
なんで1件のみ取得なんだろう。。この機能いらなくないか、、?
- where
指定条件にマッチしたレコードを全て取得。order,limit,joinsなどとメソッドチェーンできる。とてつもなく便利。djangoはここら辺すごくわかりづらかった。。。
[27] pry(main)> User.where(created_at: Time.zone.now.all_month).where(name:'a').order(:name).limit(3)
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE (`users`.`created_at` BETWEEN '2018-03-01 00:00:00' AND '2018-03-31 23:59:59') AND `users`.`name` = 'a' ORDER BY `users`.`name` ASC LIMIT 3
=> []
Update
1レコード単位で更新を行う場合
[34] pry(main)> user = User.first
User Load (0.9ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User:0x007ff56708b3f8
id: 1,
name: "foo",
email: nil,
created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>
[35] pry(main)> user.update(name: 'rails')
(0.3ms) SAVEPOINT active_record_1
SQL (0.6ms) UPDATE `users` SET `name` = 'rails', `updated_at` = '2018-03-21 16:11:28' WHERE `users`.`id` = 1
(0.2ms) RELEASE SAVEPOINT active_record_1
=> true
[36] pry(main)> user
=> #<User:0x007ff56708b3f8
id: 1,
name: "rails",
email: nil,
created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
updated_at: Wed, 21 Mar 2018 16:11:28 JST +09:00>
レコードをまとめて更新する場合
[40] pry(main)> User.update(name:'foo')
User Load (0.5ms) SELECT `users`.* FROM `users`
(0.2ms) SAVEPOINT active_record_1
SQL (0.4ms) UPDATE `users` SET `name` = 'foo', `updated_at` = '2018-03-21 16:12:43' WHERE `users`.`id` = 1
(0.2ms) RELEASE SAVEPOINT active_record_1
(0.2ms) SAVEPOINT active_record_1
SQL (0.5ms) UPDATE `users` SET `name` = 'foo', `updated_at` = '2018-03-21 16:12:43' WHERE `users`.`id` = 2
(0.1ms) RELEASE SAVEPOINT active_record_1
=> [#<User:0x007ff5661b9848
id: 1,
name: "foo",
email: nil,
created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
updated_at: Wed, 21 Mar 2018 16:12:43 JST +09:00>,
#<User:0x007ff5661b9640
id: 2,
name: "foo",
email: nil,
created_at: Wed, 21 Mar 2018 14:25:03 JST +09:00,
updated_at: Wed, 21 Mar 2018 16:12:43 JST +09:00>]
Delete
SQLのDELETEに相当。対応するのはdeleteとdestroy。deleteはSQLのDELETEを発行するだけの低レベルなメソッド。対してdestroyはdeleteを行う前後にコールバックを実行する。
ここでのコールバックは、関連テーブルのオブジェクトを同時に削除したり、オブジェクトの状態によっては削除処理を中断するといった処理のこと。
1件単位の削除
# コールバック付き削除
user.destroy
# コールバックを実行しない削除
user.delete
複数レコードの同時削除
# コールバックを実行して削除
User.destroy_all
# コールバックを実行せずに削除
User.delete_all
destroy_allはコールバックを実行しながら、一行ずつDELETEする
→遅い
delete_allは1本のSQLで一括削除
→高速
今までのDELETEは物理削除と呼び、データベースの復元ができないので、事前に別のストレージにデータを退避させておくと安心。
対して論理削除とは、deleted_atのような削除フラグをカラムに持ち、UPDATEによって削除処理を代替するもの。
deleted_atがnilのものを抽出すれば未削除レコードのみを取得できる。確かに!!
検索とか遅くなりそうではあるけど笑
検証(バリデーション)
モデルオブジェクトがデータベースに保存される前に、オブジェクトが有効かどうかチェックする仕組み
user.rb
class User < ApplicationRecord
validates :name, uniqueness: true
validates :email, presence: true
end
エラーメッセージオブジェクト
モデルオブジェクトのライフサイクルとコールバック
そもそもコールバックとは
コールバックとは、オブジェクトのライフサイクル期間における特定の瞬間に呼び出されるメソッドのことです。コールバックを利用することで、Active Recordオブジェクトが作成/保存/更新/削除/検証/データベースからの読み込み、などのイベント発生時に常に実行されるコードを書くことができます。
モデルオブジェクトが生成されてからINSERT,UPDATE,DELETEされる間のよしななタイミングでいろんなコールバックメソッドを実行できるよ〜ってことっぽい。
表でまとめる。(around_xxxはよろしくないらしいのでとばす)
CREATE | UPDATE | DELETE |
---|---|---|
before_validation | before_validation | before_destroy |
VALIDATION | VALIDATION | DELETE |
after_validation | after_validation | after_destroy |
before_save | before_save | |
before_create | before_update | |
INSERT | UPDATE | |
after_create | after_update | |
after_save | after_save |
例えば、コールバックを利用して、新規ユーザ作成後にメールを送信する処理は以下のようになる
class User < ApplicationRecord
after_create :send_email
private
def send_email
# do something
end
end
callback内での処理の中断
saveとsave!の使い分け
ActiveRecordでは、saveとsave!、updateとupdate!のように!つきのメソッドが用意されている。
処理の違いは以下
save
→成功ならtrue,失敗ならfalseを返す
sava!
→成功ならtrue,失敗なら例外を発生させる
どう使い分けるか?
saveは成功失敗に関わらず後続処理が行われてしまう。
save!は失敗したら止まる。
成功・失敗時の処理をif文などで定義したい場合
→save
失敗した場合に後続の処理を定義するのが難しい、またはそもそも失敗を想定していない場合
→save!