rails、君に決めた！！~9[wip]

一連の記事の目次
[rails、君に決めた！！~目次](https://qiita.com/satousatou/items/d20b61c198a4d439f2e7)

# Model
## Modelの理解
### ApplicationRecordとActiveRecord
Rails4から5になったことによる仕様変更の話っぽい。

Rails4以前

```ruby
class User < ActiveRecord::Base
  # do something
end
```

Rails5

```ruby
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end


class User < ApplicationRecord
  # do something
end
```

ActiveRecordをApplicationRecordに継承させるっていうワンクッションが挟むようになってる。今までは共通処理を実装する時、ActiveRecordに変更を加える必要があったけど、ApplicationRecordに変更を加えれば良くなった。

何が嬉しい？
→正直よくわからないけど、大元のActiveRecordに変更が入ると、これを継承する全クラスに変更が影響しちゃってよくないよね、ってことかな

### 命名規則
Table名
→snake_caseの複数形
モデル名
→単数形

> スネークケース：単語間を_でつなぐこと

```ruby
bin/rails g model user name:string email:string
```

generatorでモデル名を指定する時、userのように単数形にする。この時生成されるテーブル名はusersと複数形になる。

### ActiveRecordの基本的なメソッド
事前準備として以下を実行

```ruby
bin/rails g model user name:string:index email:string
bin/rails db:migrate
```

ActiveRecordのメソッドの実験場として

```bash
bin/rails c --sandbox
```

を使う。`--sandbox`ってなんぞや。
> --sandboxオプションをつけることでコンソール終了時にデータベースへの変更は全てロールバックされる

便利〜〜
では、個々のメソッドを見ていこう
#### Create
INSERTと同じ
2パターンある

- new + save

```bash
[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

```bash
[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

全レコードの取得

```bash
[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を返す

```bash
[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の例外を発生させる

```bash
[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

指定条件にマッチしたレコードが存在すれば１件のみ取得し、レコードが存在しなければnilを返す

```bash
[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>
```

なんで１件のみ取得なんだろう。。この機能いらなくないか、、？

- where

指定条件にマッチしたレコードを全て取得。order,limit,joinsなどとメソッドチェーンできる。とてつもなく便利。djangoはここら辺すごくわかりづらかった。。。

```bash
[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
１レコード単位で更新を行う場合

```bash
[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>
```

レコードをまとめて更新する場合

```bash
[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件単位の削除

```bash
# コールバック付き削除
user.destroy

# コールバックを実行しない削除
user.delete
```

複数レコードの同時削除

```bash
# コールバックを実行して削除
User.destroy_all

# コールバックを実行せずに削除
User.delete_all
```

destroy_allはコールバックを実行しながら、一行ずつDELETEする
→遅い

delete_allは１本のSQLで一括削除
→高速

今までのDELETEは***物理削除***と呼び、データベースの復元ができないので、事前に別のストレージにデータを退避させておくと安心。

> 対して***論理削除***とは、deleted_atのような削除フラグをカラムに持ち、UPDATEによって削除処理を代替するもの。

deleted_atがnilのものを抽出すれば未削除レコードのみを取得できる。確かに！！
検索とか遅くなりそうではあるけど笑

#### 検証（バリデーション）
> モデルオブジェクトがデータベースに保存される前に、オブジェクトが有効かどうかチェックする仕組み

user.rb

```ruby
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        |                |

例えば、コールバックを利用して、新規ユーザ作成後にメールを送信する処理は以下のようになる

```ruby
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!