基礎Ruby on Rails Chapter4 データの保存

前 [基礎Ruby on Rails Chapter4 データベースとモデル](https://qiita.com/tseno/items/b878c41b0e9be2e35c56)
次 [基礎Ruby on Rails Chapter5 RESTとルーティング](https://qiita.com/tseno/items/284711d49de511061541)

## レコードの作成と更新

### Railsコンソール

- Railsコンソールを起動するには、`bin/rails c`コマンドを実行する。
- `Member.count`で、membersテーブルの件数をカウントする。

```sh
$ bin/rails c
Loading development environment (Rails 5.2.1)
irb(main):001:0> Member.count
   (0.2ms)  SELECT COUNT(*) FROM "members"
=> 0
irb(main):002:0>
```

### レコードの作成

- `member = Member.new`でインスタンス作成、属性を設定して、saveメソッドでレコードを保存する。

```sh
irb(main):002:0> member = Member.new
=> #<Member id: nil, number: nil, name: nil, full_name: nil, email: nil, birthday: nil, sex: 1, administrator: false, created_at: nil, updated_at: nil>
irb(main):003:0> member.number=1
=> 1
irb(main):004:0> member.name="Taro"
=> "Taro"
irb(main):005:0> member.save
   (0.0ms)  begin transaction
  Member Create (6.1ms)  INSERT INTO "members" ("number", "name", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["number", 1], ["name", "Taro"], ["created_at", "2018-09-20 12:03:44.207546"], ["updated_at", "2018-09-20 12:03:44.207546"]]
   (14.4ms)  commit transaction
=> true
irb(main):006:0>
```
- レコードの作成方法は以下のいずれかが考えられる。
  - `member = Member.new` → `member.number = 1` → `member.name = "Taro"` → `member.save`
  - `member = Member.new(number: 1, name: "Taro")` → `member.save`
  - `member = Member.new` → `member.assign_attributes(number: 1, name: "Taro")` → `member.save`
  - `member = Member.create(number: 1, name: "Taro")`
- `save`メソッドはバリデーションが失敗した場合はfalseを返す。
- `save!`メソッドは、バリデーションに失敗した場合は例外が発生する。

### レコードの更新

- レコードの更新方法は以下のいずれかが考えられる。
  - `member = Member.first` → `member.number = 41` → `member.save`
  - `member = Member.first` → `member.assign_attributes(number: 51, name: "Ichiro")` → `member.save`
  - `member = Member.first` → `Member.update_attributes(number: 55, name: "Hideki")`

```rb
irb(main):006:0> member = Member.first
  Member Load (5.4ms)  SELECT  "members".* FROM "members" ORDER BY "members"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Member id: 1, number: 1, name: "Taro", full_name: nil, email: nil, birthday: nil, sex: 1, administrator: false, created_at: "2018-09-20 12:03:44", updated_at: "2018-09-20 12:03:44">
irb(main):007:0> member.assign_attributes(number: 51, name: "Ichiro")
=> nil
irb(main):008:0> member.save
   (0.1ms)  begin transaction
  Member Update (1.5ms)  UPDATE "members" SET "number" = ?, "name" = ?, "updated_at" = ? WHERE "members"."id" = ?  [["number", 51], ["name", "Ichiro"], ["updated_at", "2018-09-20 12:16:35.031549"], ["id", 1]]
   (11.1ms)  commit transaction
=> true
```

## シードデータの投入

### シードデータの使い方

- db/seeds.rbに、シードデータを作成するスクリプトを書いて、`bin/rails db:seed`コマンドを実行すれば、シードデータが保存される。
- 本番環境の場合は、`bin/rails db:seed RAILS_ENV=production`のように、環境変数を付加する。

```rb:db/seeds.rb
Member.create(number: 1, name: "Taro", administrator: true)
```

```sh
$ bin/rails db:seed
```

### モード別のシードデータ

- db/seeds/developmentディレクトリの下に、`members.rb`があれば、それをrequireメソッドで実行する。

```rb:db/seeds.rb
table_names = %w(members) # ほかに入れたいテーブルがあれば、ここに空白区切りで追加して、rbファイルも作る
table_names.each do |table_name|
  path = Rails.root.join("db/seeds", Rails.env, table_name + ".rb")
  if File.exist?(path)
    puts "Creating #{table_name}..."
    require path
  end
end
```

- memberにデータを投入するプログラム

```rb:db/seeds/development/members.rb
names = %w(Taro Jiro Hana John Mike Sophy Bill Alex Mary Tom)
fnames = ["佐藤", "鈴木", "高橋", "田中"]
gnames = ["太郎", "次郎", "花子"]
0.upto(9) do |idx|
  Member.create(
    number: idx + 10,
    name: names[idx],
    full_name: "#{fnames[idx % 4]} #{gnames[idx % 3]}",
    email: "#{names[idx]}@example.com",
    birthday: "1981-12-01",
    sex: [1, 1, 2][idx % 3],
    administrator: (idx == 0)
  )
end
```

### シードデータの再投入

```sh
# データベースを破棄→db/schema.rbを実行→シードデータの投入
$ bin/rails db:reset
# データベースを破棄→最初からマイグレーションをやり直す→シードデータの投入
$ bin/rails db:migrate:reset db:seed
```

- db:rebuildタスク

```rb:lib/tasks/database.rake
namespace :db do
  desc "Rebuild the development database from scratch"
  task :rebuild => :environment do
    sh "rm -f db/development.sqlite3"
    Rake::Task["db:migrate"].invoke
    Rake::Task["db:seed"].invoke
  end
end
```

- 上記コマンドを使って、シードデータの再投入をする。

```sh
$ bin/rails db:rebuild
```

## findとfind_by

### findメソッド

- `find(キーの値)`で、レコードを1件取得する。
- 該当データが無い場合、例外ActiveRecord::RecordNotFount発生。

### find_byメソッド

- `find_by(項目名1: 値1, 項目名2: 値2, ・・・)`で、最初に一致したレコードを1件取得する。
- 該当データが無い場合、nilが返る。

## クエリーメソッドとリレーションオブジェクト

### クエリーメソッドとリレーションオブジェクト

- whereメソッドは、条件に一致する複数のレコードを取り出す。
- 実際に返すのは、配列ではなくActiveRecord::Relationクラスのオブジェクト。
- `Member.where(name: "Taro")`、`Member.where("number < 20")`のように書ける。

### 検索が実行されるタイミング

- whereメソッドは、呼び出しただけでは検索は実行されない。検索が実行されるのはデータが必要になった時。
- `@members = Member.where(sex: 2).load` のようにloadメソッドを使うと、即座に実行される。
- lengthメソッドは検索を実行して、配列を作ったうえで配列の長さを返す。必ず結果を使うのなら、こちらがいい。
- sizeメソッドはcountメソッド呼び出して、SQLで件数をカウントする。件数によって結果を取得しないのなら、こちらがいい。

### クエリーメソッドの重ね合わせ

- whereをAND条件で結びつける。

```rb
# 2つの検索条件をANDで結びつける
members = Member.where(name: "Taro")
members = members.where("number < 20")
# 1行でまとめる
members = Member.where(name: "Taro").where("number < 20")
```

- whereとorderに組み合わせ

```rb
# ソート順を指定する。
members = Member.where(sex: 2).order("number")
# 引数をハッシュにする書き方
members = Member.where(sex: 2).order(number: :desc)
```

### ファインダーメソッドとの組み合わせ

- firstメソッドは先頭から1つだけ取り出す。
- lastメソッドは最後から1つ取り出す。
- firstとlastはソート順をつけること。付けないと結果が不定になる。
- そのほか、find(1)、find_by(id:1)。

```rb
members = Member.where(sex: 2).order("number").first
```

### whereメソッドの便利な使い方

- `カラム値：配列`というハッシュを渡すと、どれかと同じという検索条件を指定できる。

```rb
# 配列で取り出す。
members = Member.where(number: [15,17,19])
members.map(&:name)
# = ["Sophy","Alex","Tom"]
```

- `カラム値：範囲オブジェクト`というハッシュを渡すと、その範囲という検索条件を指定できる。

```rb
# 範囲で取り出す。
members = Member.where(number: 12..14)
members.map(&:name)
```

- `not`メソッドは、〇〇でないという検索条件を指定できる。

```rb
# 範囲で取り出す。
members = Member.where.not(name: "Taro")
```

### プレースホルダー

- `?`に、変数の値を埋め込む。SQLインジェクション対策。

```rb
name = "Taro"
members = Member.where("name = ?", name)
```

### 集計用のメソッド

- `average(カラム名)`、`count`、`maximum(カラム名)`、`minimum(カラム名)`、`sum(カラム名)`で集計をすることができる。

```rb
Member.maximum("number") 
# = 19
Member.where(sex: 1).count
# = 7
```

- SQLを直接書くには、`find_by_sql`メソッドを使う

```rb
members = Member.find_by_sql("SELECT * FROM members WHERE number = 11")
# プレースホルダーを使うには、配列で指定する
members = Member.find_by_sql(["SELECT * FROM members WHERE name = ?", "Taro"])

```

参考
[改訂4版 基礎 Ruby on Rails (IMPRESS KISO SERIES)](https://www.amazon.co.jp/%E6%94%B9%E8%A8%824%E7%89%88-Ruby-Rails-IMPRESS-KISO/dp/429500460X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1536666358&sr=1-1)