Railsチュートリアル13章まとめ。Micropost実装

Railsチュートリアル 13章を終えました。
13章ではユーザーが短いメッセージを投稿できるようにするためのリソース「マイクロポスト」を追加していきます
いわゆるtwitterの簡易版コピーみたいなものですね

railstutorial.jp

概要

12章までで、ユーザ登録および操作、管理まで行うことができました。
ユーザ関連の操作ができるようになって基盤ができたので、ここではサービスとして簡易的なMicropostを作成します。
ユーザがツイートして、表示できるようにします。
そのため、ユーザとツイートを関連付け、そのツイートを操作できるようにします

Micropostモデル

ということで、まずはツイートを格納するためのモデルを作成していきます
以下のようなテーブルを作成します

micropostモデル
micropostモデル

このモデルを作成するため$ rails gを行います

$ rails generate model Micropost content:text user:references

ここでcontentカラムのText型はString型と違い255文字よりも多く格納することができます
英数字だけでなく、さらに色々な言語にも対応することができます

モデルの関連づけ

またUserモデル関連づけを行います。
user:referencesとreferenceを指定すると、ユーザモデルと関連付けするための準備を行うため、自動的にインデックスと外部キー参照付きのuser_idカラムが追加されます

$ rails db:migrateにてマイグレを行えばモデルを作成します

次にUserモデルとMicropostモデルとの関連づけを、model上で行います
belongs_toで関連付けできます。イメージは以下です。

belongs_toのイメージ
belongs_toのイメージ

Micropostモデルにbelongs_toを付与します

class Micropost < ApplicationRecord
  belongs_to :user
 ・・・
end

また1対nの場合はhas_manyになります。以下のようなイメージになります

has_many
has_many

Userモデルに付与します

class User < ApplicationRecord
  has_many :microposts

end

これを行うと、紐付いているユーザーを通してマイクロポストを作成することができます
例えばMicropost.new(content: "Lorem ipsum", user_id: @user.id)@user.microposts.build(content: "Lorem ipsum")と書き換えられます

default_scopeメソッド

現状、user.micropostsとした時の並び順がランダムです。
これを順序を決定するためにdefault_scopeメソッドを使用します
ラムダ式(->)を使用し引数を設定し、データベースから要素を取得したときの、デフォルトの順序を指定します。
default_scope -> { order(created_at: :desc) }

Dependent: destroy

次にユーザを削除した時に、それ関連付けたMicropostのレコードがゴミとして残ってしまいますので、それを削除するようにします。
具体的にはhas_manyDependent: destroyオプションを付与します

class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
end

Micropost表示

次にツイートを表示します。ここではユーザの詳細ページに作成することにします。
このため、app/view/users/show.html.erbに追記します。

    <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>

ここで_micropost.html.erbパーシャルを指定するに<%= render @microposts %>を指定します
<%= will_paginate @microposts %>とwill_paginateに@micropostsを指定していますが、変数を省略できるのは、Usesコントローラのコンテキストにおいて、@usersインスタンス変数があるなど、同じコントローラ内の同インスタンス変数が存在している場合です

ここで、ユーザにひもづくmicropostsのレコード数を表示するためにはuser.microposts.countを使用することでできます


Micropost操作

Micropostは新しい描写はいらなく、作成と削除しか行わないためcreatedestroyがあればいいです。route.rbは以下のようになります

resources :microposts,          only: [:create, :destroy]

次にログインしている場合にルートページにmicropostを表示するようにします
app/view/static_pages

<% 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>

ここでapp/views/shared/_user_info.html.erb内で全ポスト数を表示します。

pluralize

この時pluralizeを使用し<%= pluralize(current_user.microposts.count, "micropost") %>とすることで、count数によりmicropostを単数形、複数形で表示ができます
これは簡易にqiitaにまとめてみました(宣伝)

qiita.com

whereメソッド

また<%= render 'shared/feed' %>について、実際にMicropostを表示します。
ここで、Micropostは複数レコードを表示することもありますので、
モデルではfind_byではなくwhereを使用します

Micropost.where("user_id = ?", id)

ここで検索条件に?を指定することで、idがエスケープされるため、SQLインジェクション (SQL Injection) と呼ばれる深刻なセキュリティホールを避けることができます。

投稿削除

最後に投稿を削除します。この時どのユーザも好き勝手に削除するわけにはいきません。
・自分の投稿した投稿のみ削除可能
とする必要があります
これはcontrollerで実装します

app/controllers/microposts_controller.rb

class MicropostsController < ApplicationController
  before_action :correct_user,   only: :destroy

  def destroy
    @micropost.destroy
    flash[:success] = "Micropost deleted"
    redirect_back(fallback_location: root_url)
  end

  private

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

destroyを行う前にcorrect_userメソッドで、削除しようとするmicropostがログインしているユーザに紐付くmicropostかどうかを判定します。

destroyメソッド内にredirect_back(fallback_location: root_url)がありますが、これはRails5から追加されたメソッドで一つ前のURLを返します。
DELETEリクエストが発行されたページに戻すことができます。 元に戻すURLが見つからなかった場合はroot_urlに遷移します

Micropost画像表示

ここまでくると、ツイートを投稿と削除することができます。
次に画像を投稿する実装をします
ここでは画像関係のgemを使用します。

Gem追加

今回は以下のgemを使用します
・CarrierWave・・・画像uploader
・mini_magick・・・画像をリサイズ
・fog・・・本番環境にuploadする

gemfileについてはfogのみ

group :production do
  gem 'fog', '1.42'
  end

とgroup :production の中に入れます
それ以外は全ての環境に追加します
あとは$bundle installです
次に$ rails generate uploader PictureでPicture uploaderの作成ができます


次に画像名をUserモデルに保存するために、pictureカラムにstring型を追加します

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

次にCarrierWaveに画像と関連付けたモデルを設定します。mount_uploaderを使用します
app/models/micropost.rb

class Micropost < ApplicationRecord
 mount_uploader :picture, PictureUploader
end

画像の拡張子指定とサイズ指定(rails側での制御)

画像をuploadする際にjpgなど、画像以外の拡張子のファイルをアップデートしないようにします。
また、特定のサイズ以上の画像を上げれないようにします

app/uploaders/picture_uploader.rb

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

app/models/micropost.rbについてはvalidateを使用します(独自のvalidateのため、validatesではなくvalidateを使用します)

class Micropost < ApplicationRecord
  validate  :picture_size

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

また、フロント側でも制御します。こうすることでアップロードを行う前に警告を出すことができます
app/views/shared/_micropost_form.html.erb

  <span class="picture">
    <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
  </span>

<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

画像をリサイズするにはImageMagicを使用します。これはgemではないですので Linuxの場合は$ sudo yum install -y ImageMagick
Macの場合は$ brew install imagemagickでinstallします

MiniMagick

次にImageMagickRubyをつなぐMiniMagickを使用して、画像をリサイズします。
MiniMagickのドキュメントを見ると色々とリサイズできますが、今回は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]

end

最後に

ここまでくると記事投稿および画像投稿ができます。
画像などはAWSのS3サーバやその他ストレージサーバに上げる方法がよくとられます
ただ個人で開発する場合費用などが発生するため
そのあたりを含めストレージサーバを選択する必要がありそうです

あと1章で終わりですね

参考

今回も記事作りに参考になりました。ありがとうございます

www.masalog.site