【Rails】いいねボタンを作ろう part1/2

## この記事について
- いわゆる「いいねボタン」をRailsで実装する記事です
- 少々長いので記事は2分割されています
- 自分で書いていたのですが、せっかくなので解説などを付け足してQiitaの記事にしました

## 対象読者(≒この記事を読むことによって幸せになれる可能性がある人)
- いいね機能を作りたい人
- 多対多の関係がいまいちピンと来ていない人
- 愛に溢れるマサカリを投げてくれる奇特なエンジニアさん

## こちらもどうぞ
- [【Rails】いいねボタンを作ろう part2/2](http://qiita.com/mohira/items/148c0f6ef89e8f62df72)
  - この記事の続き
- [【Rails】ミニアプリを作りながら多対多のアソシエーションとネストされたルーティングを学ぶ](http://qiita.com/mohira/items/2362dc905999b013a746)
  - 多対多とネストされたルーティングによりフォーカスした記事

## 設計
### ざっくり要件
- ユーザー認証機能(ログイン機能)
- ログインユーザーはブログ記事を作成できる
- [これが本丸]**ログインユーザーは各ブログ記事に1回だけいいね！ することができる**
  - いいね！ は1ユーザー1ブログ記事につき1回の制限
  - 自分のブログ記事に対してもいいね！ することはできる
- 既に、いいね！ をしているブログ記事に対して、いいね！ を解除することができる
  - ボタンは1つで実装し、いいね！ の状態によって切り替える
- ユーザーのブログ記事一覧が表示される
- ブログ記事詳細画面で、いいね！ 件数および、どのユーザーがいいね！ しているかが表示される

### テーブル
- データ型はRails的な書き方

#### Users
- ユーザーを管理するテーブル
- Deviseによって生成される構造に準拠する

| カラム | データ型 | 役割 |
| ----- | ------- | ---- |
| email | string | メールアドレス |
| password | string | パスワード |
| その他いろいろ | いろいろ | |

#### Postモデル
| カラム  | データ型 | 役割 |
| ------- | ------- | ------------------ |
| user_id | integer | 投稿したuserのid    |
| title   | string  | ブログ記事のタイトル |
| body    | text    | ブログ記事の本文    |

#### Likeモデル
| カラム  | データ型 | 役割 |
| ------- | ------- | ------------------ |
| user_id | integer | いいね！ したuserのid    |
| post_id | integer | いいね！ されたpostのid    |


## GitHub
- [Github: mohira/like-btn](https://github.com/mohira/like-btn)
  - [v1タグ: この記事の最終型](https://github.com/mohira/like-btn/tree/v1)
  - [v2タグ: 次の記事の最終型(いいねボタン完成)](https://github.com/mohira/like-btn/tree/v2)
  - [v3タグ: おまけの実装](https://github.com/mohira/like-btn/tree/v3)

## 開発環境
- Ruby 2.3.0
- Rails 5.0.1
  - おそらくRails 4.x でも動作すると思います(未確認)
  - 4.x での`rake`コマンドは、5.xでは`rails`になっているので注意


# ここから実装
## プロジェクト作成
```bash
$ rails new like-btn
$ cd like-btn
```


## Devise導入とUserモデル生成
- Deviseを使って生成する

```rb:Gemfile
gem 'devise'
```

```bash
$ bundle install
$ rails g devise:install
$ rails g devise User
$ rails db:migrate
```

```rb:config/routes.rb
root 'posts#index'
```



## Postモデル生成
```bash
$ rails g model Post title:string body:text user:references
```

Migrationスクリプト修正
null制約だけ追加

```rb:db/migrate/yyyymmddhhiiss_create_post.rb
class CreatePosts < ActiveRecord::Migration[5.0]
  def change
    create_table :posts do |t|
      t.string :title,    null: false
      t.text :body,       null: false
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end
```

```bash
$ rails db:migrate
```

## Seedデータ作成
- 3人のユーザーが3件ずつ記事を持つ

```rb:db/seeds.rb
User.delete_all
Post.delete_all

3.times do |i|
  i += 1
  user = User.create(
    email: "user#{i}@example.com",
    password: 'password'
  )

  3.times do |j|
    j += 1
    Post.create(
      title: "#{user.email}の記事 その#{j}",
      body: "body#{j} by #{user.email}",
      user_id: user.id
    )
  end
end
```

## UserとPostのアソシエーション(1対多)
- 基本的な1対多の関係(多対多の話はしばらく先)
- ポイントはuserを削除したときに関連する(≒そのuserが作成した)ブログ記事を削除するところ

### User
- `dependent: :destroy`をつける
  - `dependent`は「依存」という意味。やはり英単語を抑えておくとイメージしやすい
  - かの有名な「依存関係」の「依存」である
  - 「依存」の反対を意味する`independence`(独立)の方が有名かもしれない

```rb:app/models/user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end
```

#### consoleで確かめる
- `dependent: :destroy`の威力を確かめる

```bash
$ rails c
irb(main):001:0> u3 = User.find(3)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
=> #<User id: 3, email: "user3@example.com", created_at: "2017-01-26 05:55:16", updated_at: "2017-01-26 05:55:16">
irb(main):002:0> u3.posts
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ?  [["user_id", 3]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Post id: 7, title: "user3@example.comの記事 その1", body: "body1 by user3@example.com", user_id: 3, created_at: "2017-01-26 05:55:16", updated_at: "2017-01-26 05:55:16">, #<Post id: 8, title: "user3@example.comの記事 その2", body: "body2 by user3@example.com", user_id: 3, created_at: "2017-01-26 05:55:16", updated_at: "2017-01-26 05:55:16">, #<Post id: 9, title: "user3@example.comの記事 その3", body: "body3 by user3@example.com", user_id: 3, created_at: "2017-01-26 05:55:16", updated_at: "2017-01-26 05:55:16">]>
irb(main):003:0> u3.destroy
   (0.1ms)  begin transaction
  SQL (0.7ms)  DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 7]]
  SQL (0.1ms)  DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 8]]
  SQL (0.1ms)  DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 9]]
  SQL (0.2ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 3]]
   (9.0ms)  commit transaction
=> #<User id: 3, email: "user3@example.com", created_at: "2017-01-26 05:55:16", updated_at: "2017-01-26 05:55:16">
```

### Post
- Postモデル生成時に`user:references`を使ったので既に記述されている

```rb:app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end
```



## 関連モデルと外部キーと指定する場合の1対多アソシエーションの記述方法
- Postモデルに記述したアソシエーションはこれで問題ないが、別の書き方もできる
- そもそもアソシエーションを指定するにあたり必要な情報はいくつかある
  1. どんな関係性か？ (belongs_to や has_manyなど)
  2. どういうメソッド名で取り出すか？ (第1引数)
  3. どのモデルとの関係か？ (`class_name`で指定)
  4. 外部キーの名前は何か？ (`foreign_key`で指定)


```rb:現状の記述
belongs_to :user
```

- 上記のような記述で指定しているのは、関係性(1)とメソッド名(2)だけである
- しかし、postsテーブルには`user_id`とカラムがあり、
- これらの名前がルールどおりになっているので、
- 自動的に、関連モデル(3)はUserモデル、外部キー(4)は`user_id`と判断している

逆に言えば、こちらから指定することもできるわけだ。

```rb:app/models/post.rb
class Post < ApplicationRecord
  # belongs_to :user

  # 上の記述と同じ
  belongs_to :user, class_name: :User, foreign_key: :user_id
end
```

このように記述しても、先程の記述と同じ働きをする。
しかし、わざわざ書くのは手間なので(普段は)短く書いていたわけである。

アソシエーションを覚えたばかりの段階では、「規約通りの書き方」になるが、やはり色々な指定ができるので、次のような使い方もできる。


```rb:app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user

  # 上の記述と同じ
  # belongs_to :user, class_name: :User, foreign_key: :user_id

  # このような書き方もできる(post.user_infoでアクセス)
  # belongs_to :user_info, class_name: :User, foreign_key: :user_id
end
```

この場合、ある1つのpostから、そのpostを書いたuserを辿るときに`@post.user_info`という記述になる(下記にconsoleでの結果を載せた)。
何がメリットかというと、**メソッド名を分かりやすくできる**できるのだ。今回の場合は、「ブログ記事を書いたユーザー情報」なので`user`でも違和感は無いが、モデルの設計が変わる場合(例えば、ユーザー情報は全てUserモデルで管理するが、ユーザー同士に上司と部下の関係性があるといった場合など)には有用である。

今回は`belongs_to :user`の記述で進める。

```bash
$ rails c
irb(main):003:0> p1 = Post.first
=> #<Post id: 1, title: "user1@example.comの記事 その1", body: "body1 by user1@example.com", user_id: 1, created_at: "2017-01-26 05:55:16", updated_at: "2017-01-26 05:55:16">
irb(main):004:0> p1.user_info
=> #<User id: 1, email: "user1@example.com", created_at: "2017-01-26 05:55:15", updated_at: "2017-01-26 05:55:15">
irb(main):005:0>
```

## ルーティングの設計
- ブログ記事を見るときのルーティングをどうするか？
- 改めて要件を確認(どんなページを用意するか？)
  1. 全ユーザーの一覧が見れる
  2. 各ユーザーの詳細が見れる
  3. 全ユーザーが投稿したブログ記事の一覧が見れる
  4. 各ブログ記事の詳細が見れる
  5. 各ユーザーごとの投稿したブログ記事一覧が見れる



### 「1. 全ユーザーの一覧が見れる」 と  「2. 各ユーザーの詳細が見れる」
- まずはこれから考える
- `devise_for :users`という記述によってUser関連のルーティングがいくつかできる
- しかし、この中にはCRUDで言うところの一覧(#index)と詳細(#show)がない
- なので、この2つのルーティングを素直に加えればOK

```rb:config/routes.rb
Rails.application.routes.draw do

  devise_for :users

  # ココを追記
  resources :users, only: [:index, :show]

  root 'posts#index'
end
```

これにより次のルーティングが追加される

```bash:ルーティング(一部)
Prefix Verb URI Pattern          Controller#Action
 users GET  /users(.:format)     users#index
  user GET  /users/:id(.:format) users#show
```


### 「3. 全ユーザーが投稿したブログ記事の一覧が見れる」  と  「4. 各ブログ記事の詳細が見れる」
- これは単純に`resources :posts`という記述でOK
- postsのデータ生成についてはseedでまかなうので#indexと#showだけつくる
- あくまで主役はブログ記事だからuserは関係がない

```rb:config/routes.rb
Rails.application.routes.draw do

  devise_for :users

  resources :users, only: [:index, :show] do

  resources :posts, only: [:index, :show]

  root 'posts#index'
end
```

### 「5. 各ユーザーごとの投稿したブログ記事一覧が見れる」
- これが問題である
- 目的としては、「◯◯さんの記事一覧」や「★★さんの記事一覧」という設計にしたい
  - `/users/1/posts`(user_idが1のユーザー記事一覧)
  - `/users/2/posts/3`(user_idが1のユーザーが投稿したブログ記事)
- つまり、URLに**投稿したuserのidが必要**になる
- ここで、ネストされたルーティングを利用する


```rb:config/routes.rb
Rails.application.routes.draw do

  devise_for :users

  resources :users, only: [:index, :show] do
    resources :posts
  end

  resources :posts, only: [:index, :show]

  root 'posts#index'
end
```

その結果、下記のルーティングが生成される

```bash:ルーティング(一部)
        Prefix Verb   URI Pattern                              Controller#Action
    user_posts GET    /users/:user_id/posts(.:format)          posts#index
               POST   /users/:user_id/posts(.:format)          posts#create
 new_user_post GET    /users/:user_id/posts/new(.:format)      posts#new
edit_user_post GET    /users/:user_id/posts/:id/edit(.:format) posts#edit
     user_post GET    /users/:user_id/posts/:id(.:format)      posts#show
               PATCH  /users/:user_id/posts/:id(.:format)      posts#update
               PUT    /users/:user_id/posts/:id(.:format)      posts#update
               DELETE /users/:user_id/posts/:id(.:format)      posts#destroy
         users GET    /users(.:format)                         users#index
          user GET    /users/:id(.:format)                     users#show
```

- いくつかのポイントがある
  1. `users`が先にくる
  2. パスの中で`userのid`は`:user_id`というシンボルになっているが、`postのid`は`:id`となっている(`:post_id`ではない！)
  3. Prefixはuserとpostを含んだものになり、これによって新たなメソッドが開放される(後述)


### アソシエーション設定によって開放されるメソッド群(パス編)
- ネストされたリソースのパスを返すメソッドがいくつ
- `link_to` や `form_for` `redirect_to` は パスを指定するが、そのパスを指定する際に、`Prefix_path` だけではなくて パスを表す配列も使える

#### 表にまとめる
- 前提
  - `@user`は1つのuserオブジェクトが格納
  - `@post`は1つのpostオブジェクトが格納

- ポイント
  - 単数形と複数形をみればわかる
  - 順番も大事

| アクション | path指定の場合 | 配列指定の場合 |
| ------- | ------------------------------------- | -------------------------|
| index   | user_entries_path(`@user`)            | [`@user`, :posts]      |
| create  | user_posts_path(`@user`, `@post`)     | [`@user`, :posts]      |
| new     | new_user_post_path(`@user`)           | [:new, `@post`, :post]   |
| edit    | edit_user_post_path(`@user`, `@post`) | [:edit, `@user`, `@post`] |
| show    | user_post_path(`@user`, `@post`)      | [`@user`, `@post`]        |
| update  | user_post_path(`@user`, `@post`)      | [`@user`, `@post`]        |
| destroy | user_post_path(`@user`, `@post`)      | [`@user`, `@post`]        |


```html+erb:パスの指定
<!-- どっちも同じで、あるuserの記事一覧にとぶ -->
<!-- 生成パス: /users/:user_id/posts -->
<% @user = user.frist %>
<%= link_to 'firstさんの記事一覧へ', [@user, :posts] %>
<%= link_to 'firstさんの記事一覧へ', user_posts_path(@user) %>
```

## Postsコントローラ生成
- ルーティングが出来たので画面を作成する

### ブログ記事管理の設計を確認する
- Postsコントローラを作って、次の2つをつくる
  1. #index: 記事一覧(全ユーザーの記事一覧と各ユーザーごとの記事一覧)
  2. #show:  記事詳細
- #indexではURLを基にして**全ユーザーの記事一覧**を表示するか、**特定のユーザーの記事一覧**を表示するかを分ける


まずはコントローラ生成

```bash
$ rails g controller Posts index show
```


### indexアクション
- URLを考えると
  - 全ユーザー記事一覧 → `/posts`
  - 特定のユーザーの記事一覧 → `users/:user_id/post`
- つまり、`params[:user_id]`の有無によって判断する

```rb:app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    if params[:user_id]
      @user = User.find(params[:user_id])
      @posts = @user.posts # ここでアソシエーションが生きる
    else
      @posts = Post.all
    end
  end
end
```

ビューは例えば、次のようになる

```html+erb:app/views/posts/index.html.erb
<% if @user.present? %>
  <h1><%= @user.email %>さんの記事一覧</h1>
<% else %>
  <h1>全ユーザーの記事一覧</h1>
<% end %>

<% @posts.each do |post| %>
  <li>タイトル: <%= link_to post.title, post %></li>
<% end %>
```

### showアクション
- 基本的な書き方

```rb:app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    if params[:user_id]
      @user = User.find(params[:user_id])
      @posts = @user.posts
    else
      @posts = Post.all # ここでアソシエーションが生きる
    end
  end

  def show
    @post = Post.find(params[:id])
  end
end
```

```html+erb:app/views/posts/show.html.erb
<h1>タイトル: <%= @post.title %></h1>
<h2>投稿者: <%= @post.user.email %>さん</h2>

<h3>本文</h3>
<p><%= @post.body %></p>
```

## ここまでのまとめ
- 長くなってしまったのでここで区切る
  - ここまでの実装は`v1`というタグを付けています
  - [mohira/like-btn v1](https://github.com/mohira/like-btn/tree/v1)
- 肝心のいいね機能は[次の記事](http://qiita.com/mohira/items/148c0f6ef89e8f62df72)で実装する

### ポイント
- アソシエーションにおける関連モデルと外部キーの記述
- ネストされたルーティング
- ネストされたリソースのパスを返すメソッドとその記述


# [つづきはこちら！](http://qiita.com/mohira/items/148c0f6ef89e8f62df72)