Rails学習　2日目

#Ruby on Rails5速習実践ガイド chapter4

##4-2-1 データ型
データベースのカラムにはそれぞれデータ型というものが必要になってくる。
データ型というのはその中に入る値を特定の条件付きで指定するものである。

|データ型|説明|
|:---|:---|
|:boolean|真偽値|
|:integer|符号付きの整数|
|:float|浮動小数点数|
|:string|文字列（短い）|
|:text|文字列（長い）|
|:date|日付|
|:datetime|日時|

それぞれのカラムに上記のデータ型を付け加えることで中に入るデータを区別する

#4-2-2 NOT NULL制約
カラムは値のない場合NULLとして値を設定しテーブルとして保存するがNOT NULLを使うことによってカラムには必ず何らかの値が入ることを指定することができる。

・NOT NULL制約のかけかた
NOT NULL制約には２種類のかけ方がある。１つ目がテーブルを作成する時に制約をつける方法。２つ目がテーブルを作成し終わってから制約を付け足したい時の制約をつける方法である。

1.テーブルを作成する時に制約をつける方法
テーブルを作成するためのマイグレーションファイルにそのままNOTNULL制約をかけてしまってからdb:migrateしてテーブルを作成する方法

```ruby:db/migrate/XXXXXXXXXX_create_tasks.rb
class CreateTasks < ActionRecord::Migration[5.2]
 def change
  create_table :tasks do |t|
   t.string :name, null: false
   t.text :description
...
```

nameの部分は何か値がないと困るのでnameの後ろにnullをつける。
その後ろにnull(空白)でもいいならtrue,空白が嫌（NOTNULL）ならfalseをつける

2.テーブルを作成し終わってから制約を付け足したい時の制約をつける方法

```
$ bin/rails g migration ChangeTaskNameNotNull
```
テーブルはもう作ってしまったのでマイグレーションファイルを変更しても意味がない。なので変更用のマイグレーションファイルを作りそこに変更用のコードを書く

```ruby:db/migrate/XXXXXXXXXXXX_change_tasks_name_not_null.rb
class ChangeTaskNameNotNull < ActionRecord::Migration[5.2]
 def change
  change_column_null :tasks, :name, false
 end
end
```

できた変更用マイグレーションファイルにchange_column_null(カラムをnullにchangeする)を使ってタスクを変更する。
change_column_null :タスク名, :カラム名, :trueかfalseか（trueはnull、falseはnotnull）,

##文字列カラムの長さを指定する　　　　　　　（changeメソッド　と　upメソッドdownメソッド）
文字列の長さを指定するには上記の説明である通り、テーブル作成のマイグレーションファイルに情報を追加するか、情報が決まってしまったテーブルに情報追加のためのマイグレーションファイルを作るかのどちらかである。今回は情報が決まってしまったテーブルに情報追加のためのマイグレーションファイルを作る方法を見てみる。

```ruby
class ChangeTasksNameLimit30 < ActionRecord::Migration[5.2]
 def up
  change_column :tasks, :name, :string, :limit: 30
 end
 def down
  change_column :tasks, name, :string
 end
end
```

今回はchangeメソッドの代わりにupメソッドとdownメソッドが書かれているところに注目する。本来changeメソッドは上のupメソッドとdownメソッドを合わせて作られるメソッドである。なぜ今回changeメソッドを二つに分けたかというと、答えは[change_column]にある。change_columnをchangeメソッドで使うと何かあった時に元に戻せなくなるのだ。バージョンを戻す時には上げたバージョン（upやchange）を見ながらそれと逆の操作をしてバージョンを戻す。しかしchangeメソッドだとそれがうまくいかないのである。

##4-2-4 ユニークインデックスを作成する
ユニークインデックスも上記のようにテーブル作成のマイグレーションファイルか追加変更用のマイグレーションファイルに書かれる。

```ruby
class AddNameIndexToTasks < ActiveRecord::Migration[5.2]
 def change
  add_index :task, :name, unique: true
 end
end
```

ユニークインデックスをつけると値（この場合はname)の重複が無くなる（被らなくなる）

##4-3-6 オリジナルの検証コードを書く　　　　　　　（自分でvalidatesを作る）

```ruby:app/models/task.rb
validate :validate_name_not_including_comma
```
まず自分で作ったvalidateの名前をvalidateに入れる。

```ruby:app/models/tasks.rb
private

def validate_name_not_including_comma
  errors.add(:name,'にカンマを含めることはできません') if name&.include?(',')
end
```

自分の作ったvalidateの内容をprivateメソッドのなか（外部からいじられたらダメなので）で定義。
if name&.include?(',')　もしカンマがついた名前があると普通に実行するがカンマがない名前になると結果はnilになりエラーが出てしまう。ぼっち演算子ならnilが出てエラーにならずにnilという言葉だけ出てくる。

##4-5-1 セッション
セッションがあることによってどんなページへ何回とんでも自分が操作しているという状態は保たれる（ログイン状態になる）

```
session[user_id] = @user.id

@user.id = session[user_id]
```
上がセッションに情報を入れる操作。下がセッションの値を取り出す操作になっている。


##4-5-2 Userモデルを作る（password-digest）
password-digestを使うとパスワードが暗号化されパスワード漏洩や不正アクセスの心配が無くなる
password-digestを対応させるにはhas_secure_passwordというコードを書くと使えるようになる。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_secure_password
end
```
モデルにhas_secure_passwordをかく。そうするとpasswordカラムの下にpassword_confimationというカラムができる。これはパスワード確認のために2回入力させるためのやつ。この２つのパスワードがpassword-digestで暗号化された時に一致しているかどうかで判断する。

##4-5-8 ログイン情報の取得を簡単にする
```ruby
User.find_by(id:session[user_id])
```

今セッションしているidを探すというコードのなのでログインをしていればこのコードでログインしているユーザーを特定できる。
これをApplicationControllerのなかでメソッドとして定義してやるとどこのアクション内でもUser.find_by(id:session[user_id])が働き、ログインしているという情報がついてくる。
なので新しくメソッドを作る

```ruby:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  helper_method :current_user

 private

 def current_user
   @current_user||=User.find_by(id:session[user_id]) if session[:user_id]
 end
end
```

「もし@current_userが機能しているなら@current_userを、そうじゃない場合はもしuser_idがセッションしている状態ならそのセッションしているユーザーの情報を探して、それを変数@current_userとする」という動作をcurrent_userメソッドと定義する。このメソッドは外部から操作されてはいけないのでprivateメソッドのなかに入れる

##4-5-9 ログアウト機能を実装する
ログイン状態はreset_sessionでログアウト状態になる

```ruby:app/controllers/session_controller.rb
 def destroy
   reset_session
   redirect_to root_url, notice: 'ログアウトしました'
end
```
なのでreset_sessionはdestryコントローラーの中に定義する。

##4-5-11-1 データベース上でUserとTaskを紐付ける
UserとTaskを紐づける上で重要になってくるのは【1対多】という関係。User一人に対しTaskは複数存在するといった状況のことである。具体的な紐付けについて説明する。

1.まずtaskのユーザー情報のuserカラムがNOTNULLな状態にならなければいけない。

```ruby:db/migrate/XXXXXXXXXX_AddUserIdToTasks.rb
class AddUserIdToTasks < ActiveRecord::Migration[5.2]
 def up
   execute 'DELETE FROM tasks;'
   add_reference :tasks, user, null: false, index: true
 end

 def down
   remove_reference :tasks, :user, index: true
 end
end
```

remove_reference：カラムの中の制約を付けたい時にreferenceを使う。
remove_reference :tasks, :user, index: true←このデータを
 add_reference :tasks, user, null: false, index: true←userがnull:falseのなるように制約を付け加えた。
execute 'DELETE FROM tasks;'←制約をつける前にuserがnull:falseだったデータがあるかもしれない、もしあったらエラーになる。なのでこのコードを使いテーブルにあるデータを全削除した。

2.1対多の関係を表すhas_many :tasksとbelongs_toを使って関係性を構築する。

```ruby:app/models/user.rb
 has_many :tasks
end
```

```ruby:app/models/task.rb
 belongs_to :user
end
```

データベース間の関係はモデルが行うのでモデルに記入。
user has many tasks
tasks belongs to user
の関係になるので
userモデルにhas_many :tasks
tasksモデルにbelongs_to :user
を加える。

##4-5-11-3 ログインしているユーザーのTaskデータの登録

ログインするためのcreateアクションは以下のようになっている

```ruby:app/controller/tasks_controller.rb
 def create
   @task = Task.new(task_params)
　　・
　　・
　　・
　　・
　　・
　private

 def task_params
   params.require(:task).permit(:name, :descrition)
 end
end
```

このままだと@taskで新規タスクは作られるがログイン中のユーザーのタスクだということがわからなくなる。なので上のコードを書き換える

```ruby:app/controller/tasks_controller.rb
 def create
   @task = current_user.task.new(task_params)
　　・
　　・
　　・
　　・
　　・
　private

 def task_params
   params.require(:task).permit(:name, :descrition)
 end
end
```

current_userがつくことによって「現在ログイン中の」という意味のtaskになる。
つまり先頭にcurrent_userをつけると何でも「ログイン中の」という意味がついてくる。
例えば
Task.find(params[:id])→指定したidのタスクを取ってくる
current_user.tasks.find(params[:id])→ログイン中の人のタスクの中から指定したタスクを取ってくる。

##4-8 scopeを活用する
order(created_at: :desc)などはscopeを使うともっとシンプルな名前に変えることができる。

```ruby
scope :recent, -> {order(created_at: :desc)}
```
このコードでorder(created_at: :desc)はrecentに書き換えることができる。

##その他勉強になったこと

```
task.errors.full_messages
```
taskのエラー情報が表示される

```
task.persisted?
```

taskがデータベースへ登録済みかどうか確認できる。

```slim
if task.errors.present?
  ul#errors_explanation
   -task.errors.hull_messages.each do |message|
      li= message
```

errors.present?でエラーがあるかないかを判断する。

```ruby
if user&.authenticate(session_params[:password])

private
 def session_params
   params.require(:session).permit(:email, :password)
 end
```
本来はuser(session_params[:password])だがpassword-digestで暗号化した場合は
user&.authenticate(session_params[:password])になる。

```ruby
if current_user.admin?
```
adminでユーザー管理権限を持っているかを確認することができる。そこにcurrent_userが加わり、「ログインしている人はそのユーザーの管理権限を持っているのか？」というif文になる。

```ruby
@task = current_user.task.order(created_at: :desc)
```
orderは指定した基準で一覧を並び替える操作
今回はcreated_at(作成日時)を基準で並ぶ順番が変わっている