railsチュートリアル　第十三章

#はじめに
この記事は、railsチュートリアル第十二章の行程を復習しやすいように要約したものとなっています。

##ユーザーのマイクロポスト
この章では短いメッセージを投稿できるようにするためのリソース**マイクロポスト**を追加していく。


###Micropostモデル
はじめにUserモデルの関連付けを含む```Micropostモデル```を生成する。

```Micropostモデル``` は、マイクロポストの内容を保存する ```content属性``` と、特定のユーザーとマイクロポストを関連付ける ```user_id属性``` の2つの属性だけを持つ。

```
$ rails generate model Micropost content:text user:references
```

ある程度の量のテキストを格納する場合はtext型が望ましいのでcontentにはtext型をあてている。

上記のコマンドを実行すると ```Micropostモデル``` が生成され、ユーザーと１対１の関係であることを表す```belongs_to```のコードも追加されている。

これは ```user:references``` という引数を含めたことにより、自動的にインデックスと外部キー参照付きの ```user_idカラム``` が追加され、**UserとMicropostを関連付け**する下準備をしてくれるから。

```app/models/micropost.rb
class Micropost < ApplicationRecord
   belongs_to :user
end
```

またUserモデルのときと同じで、Micropostモデルのマイグレーションファイルでも```t.timestamps```という行 (マジックカラム) が自動的に生成されている。

```db/migrate/[timestamp]_create_microposts.rb
class CreateMicroposts < ActiveRecord::Migration[5.0]
  def change
    create_table :microposts do |t|
      t.text :content
      t.references :user, foreign_key: true

      t.timestamps
    end
    #インデックスを付与
    add_index :microposts, [:user_id, :created_at]
  end
end
```
上のファイルではuser_idとcreated_atカラムに**インデックス**が付与されている。

こうすることで、**user_idに関連付けられたすべてのマイクロポストを作成時刻の逆順で取り出しやすく**なる。

ここでいつも通りマイグレーションで、データベースを更新しておく。

###Micropostのバリデーション

以下のようにバリデーションで存在性、最大文字を検証する。

```app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
```


###User/Micropostの関連付け
Webアプリケーション用のデータモデルを構築するにあたって、**個々のモデル間での関連付けを十分考えておくこと**が重要となる。

今回の場合は、**それぞれのマイクロポストは１人のユーザーと関連付けられ、それぞれのユーザーは (潜在的に) 複数のマイクロポストと関連付けられる。**

user/micropost関連メソッドのまとめは以下のようになる。

| メソッド | 用途 |
|:--|:--|
| micropost.user | Micropostに紐付いたUserオブジェクトを返す |
| user.microposts | Userのマイクロポストの集合をかえす |
| user.microposts.create(arg) | userに紐付いたマイクロポストを作成する |
| user.microposts.create!(arg) | userに紐付いたマイクロポストを作成する (失敗時に例外を発生) |
| user.microposts.build(arg) | userに紐付いた新しいMicropostオブジェクトを返す |
| user.microposts.find_by(id: 1)|userに紐付いていて、idが1であるマイクロポストを検索する

```@user.microposts.build```のようなコードを使うためには、 **UserモデルとMicropostモデルをそれぞれ更新して、関連付ける**必要がある。

```app/models/micropost.rb
class Micropost < ApplicationRecord
  #マイクロポストがユーザーに所属する関連付け
  belongs_to :user
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
```

```app/models/user.rb
class User < ApplicationRecord
 #ユーザーがマイクロポストを複数所有する (has_many) 関連付け
 has_many :microposts
  .
  .
  .
end
```

##マイクロポストを改良する
この項では、具体的には、**ユーザーのマイクロポストを特定の順序で取得**できるようにしたり、マイクロポストをユーザーに依存させて、ユーザーが削除されたら**マイクロポストも自動的に削除**されるようにしていく。

###デフォルトのスコープ
```user.micropostsメソッド``` はデフォルトでは読み出しの順序に対して何も保証しないが、ブログやTwitterの慣習に従って、作成時間の逆順、つまり最も新しいマイクロポストを最初に表示するように改良する。

これを実装するためには、```default scope```というテクニックを使用する。
 
投稿はSQL文で、
```order('created_at DESC')```
を引数に与えたい。

よって以下のようになる。


```app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  #追加
  default_scope -> { order(created_at: :desc) }
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
```
###Dependent: destroy
サイト管理者はユーザーを破棄する権限を持つ。ユーザーが破棄された場合、ユーザーのマイクロポストも同様に破棄されるようにしたい。

この振る舞いは、```has_manyメソッド```に**オプション**を渡してあげることで実装できる。

```app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  .
  .
  .
end
```
```dependent: :destroy```というオプションを使うと、ユーザーが削除されたときに、そのユーザーに紐付いた (そのユーザーが投稿した) **マイクロポストも一緒に削除**されるようになる。

##マイクロポストを表示する
Web経由でマイクロポストを作成する方法は現時点ではないが、ここでは、Twitterのような独立したマイクロポストのindexページは作らずに、ユーザーの**showページ**で直接マイクロポストを表示させることにする。

###マイクロポストの描画
ここで、ユーザーのプロフィール画面 (show.html.erb) でそのユーザーのマイクロポストを表示させたり、これまでに投稿した総数も表示させたりしていく。

まずは、Micropostのコントローラとビューを作成するために、**コントローラを生成**する。

```
$ rails generate controller Microposts
```
以前は_user.html.erbパーシャルを使って自動的に@users変数内のそれぞれのユーザーを出力していた。

これを参考に、_micropost.html.erbパーシャルを使ってマイクロポストのコレクションを表示しようとすると、次のようになる。

```
<ol class="microposts">
  <%= render @microposts %>
</ol>
```

よって対応するパーシャルを以下に示す。

```app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  </span>
</li>
```

上記では一度にすべてのマイクロポストが表示されてしまう潜在的問題に対処している。

前回のユーザー一覧では
```<%= will_paginate %>```
のようにコードは引数なしで動作していた。
これはwill_paginateが、Usersコントローラのコンテキストにおいて、@usersインスタンス変数が存在していることを前提としているためとなる。

このインスタンス変数は```ActiveRecord::Relationクラス```のインスタンスで、今回の場合はUsersコントローラのコンテキストからマイクロポストをページネーションしたいため (つまりコンテキストが異なるため)、明示的に@microposts変数を```will_paginate```に渡す必要がある。

したがって、そのようなインスタンス変数をUsersコントローラの ```showアクション``` で定義しなければならない。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def show
    @user = User.find(params[:id])
    #追加
    @microposts = @user.microposts.paginate(page: params[:page])
  end
  .
  .
  .
end
```

###マイクロポストの投稿数

投稿数の表示はcountメソッドを使うことで可能となる。
```user.microposts.count```


以上のことを踏まえてプロフィール画面にマイクロポストを表示させてみる。

```app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
  </aside>
  #追加
  <div class="col-md-8">
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>
```
###マイクロポストのサンプル

現時点ではマイクロポストがない状態なので、サンプルを追加する。

すべてのユーザーにマイクロポストを追加しようとすると時間が掛かり過ぎるので、```takeメソッド```を使って最初の6人だけに追加することにする。
```User.order(:created_at).take(6)```

この6人については、1ページの表示限界数 (30) を越えさせるために、それぞれ50個分のマイクロポストを追加するようにする。

```db/seeds.rb
.
.
.
users = User.order(:created_at).take(6)
50.times do
  content = Faker::Lorem.sentence(5)
  users.each { |user| user.microposts.create!(content: content) }
end
```

```
$ rails db:migrate:reset
$ rails db:seed
```

このコマンドをうち、railsサーバーを再起動するとプロフィールにマイクロポストのサンプルが表示される。

ここに CSSを加えてスタイルを整える必要があるが、ここでは省略。

##マイクロポストを操作する
データモデリングとマイクロポスト表示テンプレートの両方が完成したので、次はWeb経由でそれらを作成するためのインターフェイスを整えていく。

従来のRails開発の慣習と異なり、Micropostsリソースへのインターフェイスは、**主にプロフィールページとHomeページのコントローラを経由して実行される**から、Micropostsコントローラには```new```や```edit```のようなアクションは不要で```create```と```destroy```があれば十分ということになる。

したがって、Micropostsのリソースは以下になる。

```config/routes.rb
Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  #追加
  resources :microposts,          only: [:create, :destroy]
end
```
| HTTPリクエスト | URL | アクション | 名前付きルート |
|:--|:--|:--|:--|
| POST | /microposts | create | microposts_path |
|DELETE|/microposts/1|destroy|micropost_path(micropost)|

###マイクロポストのアクセス制御

関連付けられたユーザーを通してマイクロポストにアクセスするので、**createアクションやdestroyアクションを利用するユーザーは、ログイン済み**でなければいけない。

以前の章では、beforeフィルターの ```logged_in_userメソッド``` を使って、ログインを要求した。

あのときは ```Usersコントローラ``` 内にこのメソッドがあったので、beforeフィルターで指定していたが、このメソッドは ```Micropostsコントローラ``` でも必要となるので、このメソッドを```Applicationコントローラ```に移す。


```
ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper

  private

    # ユーザーのログインを確認する
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
end
```
コードが重複しないよう、このとき**Usersコントローラからlogged_in_userを削除**しておく。


###マイクロポストを作成する
```logged_in_userメソッド``` により、```createアクション``` や ```destroyアクション``` に対するアクセス制限が、beforeフィルターで簡単に実装できるようになった。

```createアクション``` を定義していく。

```app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end
```
上記では```micropost_params```でStrong Parametersを使っていることにより、**マイクロポストのcontent属性だけがWeb経由で変更可能**になっている。

次にマイクロポスト作成フォームを構築するために、サイト訪問者がログインしているかどうかに応じて異なるHTMLを提供するコードを使う。

```app/views/static_pages/home.html.erb
#追加
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
<% else %>

  <div class="center jumbotron">
    <h1>Welcome to the Sample App</h1>

    <h2>
      This is the home page for the
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </h2>

    <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
  </div>

  <%= link_to image_tag("rails.png", alt: "Rails logo"),
              'http://rubyonrails.org/' %>
#追加
<% end %>
```
上のコードを動かすにはいくつかのパーシャルを作る必要がある。

**サイドバーで表示するユーザー情報のパーシャル**


```app/views/shared/_user_info.html.erb
<%= link_to gravatar_for(current_user, size: 50), current_user %>
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>
```
↑ではユーザーが投稿したマイクロポストの総数が表示されるようになっている。

```pluralizeメソッド```を使って “1 micropost” や “2 microposts” と表示するように調整している。

**マイクロポスト投稿フォームのパーシャル**

```app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
<% end %>
```

上記のフォームを動かすには、2箇所の変更が必要となる。

一つ目はhomeアクションに@micropostを定義すること。

```app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
    @micropost = current_user.microposts.build if logged_in?
  end

  def help
  end

  def about
  end

  def contact
  end
end
```
```current_userメソッド``` はユーザーがログインしているときしか使えないから、```@micropost変数```もログインしているときのみ定義されるようになる。

もう一つの変更は**エラーメッセージのパーシャルを再定義**すること。

前回はエラーメッセージパーシャルが ```@user変数``` を直接参照していたが今回は代わりに ```@micropost変数``` を使う必要がある。

これらのケースをまとめると、**フォーム変数fをf.objectとすることによって、関連付けられたオブジェクトにアクセス**することができる。

```app/views/shared/_error_messages.html.erb
<% if object.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(object.errors.count, "error") %>.
    </div>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>
```

この修正により、**ユーザー登録・パスワード再設定・ユーザー編集のそれぞれのビューも変更する必要**がある。

```app/views/users/new.html.erb
  <%= render 'shared/error_messages', object: f.object %>
```
```app/views/users/edit.html.erb
<%= render 'shared/error_messages', object: f.object %>
```
```app/views/password_resets/edit.html.erb
 <%= render 'shared/error_messages', object: f.object %>
```

これによりマイクロポスト投稿フォームが動くようになる。

```HOMEページ``` にまだマイクロポストを表示する部分が実装されていないから今の段階では投稿した内容をすぐに見ることができない。

ユーザー自身のポストを含むマイクロポストのフィードがないと不便となるので```feedメソッド```をUserモデルで作る。

フィードの原型では、まずは現在ログインしているユーザーのマイクロポストをすべて取得する。

```app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # 試作feedの定義
  # 完全な実装は次章の「ユーザーをフォローする」を参照
  def feed
    Micropost.where("user_id = ?", id)
  end

    private
    .
    .
    .
end
```
↑のコードは↓のコードと本質的に同等となるが、↑の方が応用が効きやすいため↑を採用する。

```
def feed
  microposts
end
```

サンプルアプリケーションでフィードを使うために、現在のユーザーのページ分割されたフィードに ```@feed_itemsインスタンス変数``` を追加し、次にフィード用のパーシャルを ```Homeページ``` に追加する。

```app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      #追加
      @feed_items = current_user.feed.paginate(page: params[:page])
    end
  end

  def help
  end

  def about
  end

  def contact
  end
end
```

```app/views/shared/_feed.html.erb
<% if @feed_items.any? %>
  <ol class="microposts">
    <%= render @feed_items %>
  </ol>
  <%= will_paginate @feed_items %>
<% end %>
```

後は、いつものようにフィードパーシャルを表示すればHomeページにフィードを追加できる。

```app/views/static_pages/home.html.erb
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      #追加
      <%= render 'shared/feed' %>
    </div>
  </div>
<% else %>
  .
  .
  .
<% end %>
```

これでマイクロポストの作成はうまくいくようになった。

ただマイクロポストの投稿が失敗すると、 Homeページは ```@feed_itemsインスタンス変数``` を期待しているため、現状では壊れてしまう。

よって空の配列を渡しておく。

```app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      #追加
      @feed_items = []
      render 'static_pages/home'
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end
```

###マイクロポストを削除する
最後の機能として、マイクロポストリソースにポストを削除する機能を追加する。

これはユーザー削除と同様に、```"delete"``` リンクを使用する。

ユーザーの削除は管理者ユーザーのみが行えるように制限されていたのに対し、今回は**自分が投稿したマイクロポストに対してのみ削除リンクが動作**する。


最初のステップとして、マイクロポストのパーシャルに削除リンクを追加。

```app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      #追加
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>
```

次に、**Micropostsコントローラ**の```destroyアクション```を定義する。

```app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy
  .
  .
  .
  def destroy
    @micropost.destroy
    flash[:success] = "Micropost deleted"
    redirect_to request.referrer || root_url
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end

    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end
```

上記では```request.referrer```というメソッドを使っている。

このメソッドは**フレンドリーフォワーディング**の ```request.url変数``` と似ていて、**一つ前のURLを返す。**(今回の場合、Homeページになる)

ちなみに、元に戻すURLが見つからなかった場合でも、||演算子でroot_urlをデフォルトに設定している。

###マイクロポストの画像投稿

マイクロポストの基本はこれで実装できた。
これにプラスで画像投稿もできるようにする。

投稿した画像を扱ったり、その画像を ```Micropostモデル``` と関連付けするために、今回は```CarrierWave```という**画像アップローダー**を使う。

まずは ```carrierwave gem``` を ```Gemfile``` に追加し、```mini_magick gem``` と ```fog gems```も含めて追加しておく。

これらのgemは画像を**リサイズ**したり、**本番環境で画像をアップロードする**ために使う。

```
source 'https://rubygems.org'
.
.
gem 'carrierwave',             '1.2.2'
gem 'mini_magick',             '4.7.0'
.
.
group :production do
  gem 'fog', '1.42'
.
```

次に、いつものように```bundle install```を実行する。

```
$ bundle install
```
```CarrierWave``` を導入すると、**Railsのジェネレーターで画像アップローダーが生成できる**ようになる。

```
$ rails generate uploader Picture
```

次に必要となる ```picture属性``` を ```Micropostモデル``` に追加するために、マイグレーションファイルを生成し、開発環境のデータベースに適用させる。

```
$ rails generate migration add_picture_to_microposts picture:string
$ rails db:migrate
```

```CarrierWave``` に画像と関連付けたモデルを伝えるためには、```mount_uploader```というメソッドを使う。

このメソッドは、引数に**属性名のシンボル**と**生成されたアップローダーのクラス名**を取る。

```app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  #追加
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
```

システムによっては、ここで一旦Railsサーバーを再起動させる必要がある。

マイクロポスト投稿フォームに画像アップローダーを追加するには ```file_fieldタグ``` を含める必要があり、以下のようにする。

```app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="picture">
    #追加
    <%= f.file_field :picture %>
  </span>
<% end %>
```

最後に、Webから更新できる許可リストに ```picture属性``` を追加する。
追加すると、```micropost_paramsメソッド``` は次の通り。

```app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy
  .
  .
  .
  private

    def micropost_params
      params.require(:micropost).permit(:content, :picture)
    end

    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end
```

一度画像がアップロードされれば、```Micropostパーシャル``` の ```image_tagヘルパー``` でその画像を描画できるようになる。

また、画像の無い (テキストのみの) マイクロポストでは画像を表示させないようにするために、```picture?```という論理値を返すメソッドを使う。

このメソッドは、**画像用の属性名に応じて、CarrierWaveが自動的に生成してくれるメソッド**となる。

```app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    #追加
    <%= image_tag micropost.picture.url if micropost.picture? %>
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>
```

###画像の検証

上記のままではアップロードされた画像に対する制限がないため、もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生してしまう。

この欠点を直すために、**画像サイズやフォーマットに対するバリデーションを実装し、サーバー用とクライアント (ブラウザ) 用の両方に追加**するようにする。


最初のバリデーションでは、**有効な画像の種類を制限**していくが、これは ```CarrierWave``` のアップローダーの中に既にヒントがある。

生成されたアップローダーの中にコメントアウトされたコードがありますが、ここのコメントアウトを取り消すことで、**画像のファイル名から有効な拡張子 (PNG/GIF/JPEGなど) を検証**することができるようになる。

```app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先  
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end
```

2つ目のバリデーションでは、**画像のサイズを制御**する。

これは```Micropostモデル```に書き足してく。
先ほどのバリデーションとは異なり、ファイルサイズに対するバリデーションはRailsの既存のオプション (presenceやlengthなど) にはない。
したがって、今回は**手動でpicture_sizeという独自のバリデーションを定義**する。

```app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
  #追加 
  validate  :picture_size

  private

    # アップロードされた画像のサイズをバリデーションする
    def picture_size
      if picture.size > 5.megabytes
        errors.add(:picture, "should be less than 5MB")
      end
    end
end
```

このvalidateメソッドでは、引数にシンボル (:picture_size) を取り、そのシンボル名に対応したメソッドを呼びだす。


次に上で定義したバリデーションをビューに読み込むために、クライアント側に2つの処理を追加する。

まず ```file_fieldタグ``` に ```acceptパラメータ``` を付与し、大きすぎるファイルサイズに対して警告を出すために、ちょっとした**JavaScript(jQuery)**を書き加えることでバリデーションが動くようになる。


```app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="picture">
    #追加
    <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
  </span>
<% end %>
#追加
<script type="text/javascript">
  $('#micropost_picture').bind('change', function() {
    var size_in_megabytes = this.files[0].size/1024/1024;
    if (size_in_megabytes > 5) {
      alert('Maximum file size is 5MB. Please choose a smaller file.');
    }
  });
</script>
```

###画像のリサイズ

ファイルサイズに対するバリデーションはできたから、さいごに**画像サイズ (縦横の長さ) に対する制限**を追加する。

画像をリサイズするためには、画像を操作するプログラムが必要になる。
今回は```ImageMagick```というプログラムを使うので、これを開発環境にインストールする。

```
$ sudo yum install -y ImageMagick
```

次に、```MiniMagick```という**ImageMagickとRubyを繋ぐgem**を使って、画像をリサイズを試みる。

今回は```resize_to_limit: [400, 400]```という方法を使用しこれは、縦横どちらかが400pxを超えていた場合、適切なサイズに縮小するオプションとなる。

```app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  #追加
  include CarrierWave::MiniMagick
  process resize_to_limit: [400, 400]

  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先  
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end
```

これで画像に対する制御もすみ、マイクロポスト機能を実装することができた。

##さいごに

今回はマイクロポスト機能の実装がメインとなりプラスアルファで画像投稿機能を実装できた。

残るは１４章のフォロー機能だけとなる。

次↓
https://qiita.com/jonnyjonnyj1397/items/91c50bb5ac1d48bc29d3