0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-03-27

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

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

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

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

$ rails generate model Micropost content:text user:references

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

上記のコマンドを実行すると Micropostモデル が生成され、ユーザーと1対1の関係であることを表す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アプリケーション用のデータモデルを構築するにあたって、個々のモデル間での関連付けを十分考えておくことが重要となる。

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

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コントローラにはneweditのようなアクションは不要でcreatedestroyがあれば十分ということになる。

したがって、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 gemGemfile に追加し、mini_magick gemfog 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

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

##さいごに

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

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

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?