ユーザーのマイクロポスト | rails チュートリアル 19

Ruby on Rails チュートリアル:実例を使って Rails を学ぼう第13章 ユーザーのマイクロポストの勉強メモです。

これまで

ここmでのチュートリアルでは、以下の4つをメインに開発してきた

  • ユーザー
  • セッション
  • アカウント有効化
  • パスワードリセット

ここからは、ユーザーが短いメッセージを投稿できるようにするためのリソースであるマイクロポストを開発する

Micropostモデルの作成

$ bundle exec rails generate model Micropost content:text user:references

user:referencesを含めたことで、ユーザーと1対1の関係であることを表すbelongs_toのコードも追加されている。

class Micropost < ApplicationRecord
  belongs_to :user
end
class CreateMicroposts < ActiveRecord::Migration[5.1]
  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に関連付けられたすべてのマイクロポストを作成時刻の逆順で取り出すために、9行目に「add_index」を付加する。

user_idとcreated_at両方のカラムを1つの配列に含めることで、Active Recordで両方のキーを同時に使用する複合キーインデックスを作成できます。

$ bundle exec rails db:migrate

User/Micropostの関連付け

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
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
.
.
.
end

マイクロポストの描画

viewを作成するために、コントローラーを生成する

$ bundle exec rails generate controller Microposts
$ touch 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>
class UsersController < ApplicationController
.
.
.
  def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page])
    # debugger
  end
.
.
.
end
  <% if @user.microposts.any? %>
    <div>
      <h2>Microposts (<%= @user.microposts.count %>)</h2>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    </div>
  <% end %>

サンプルデータにマイクロポストを追加する

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

その後、サンプルデータの生成をし直す

$ bundle exec rails db:migrate:reset
$ bundle exec rails db:seed

マイクロポストを操作する

routesを修正する

Rails.application.routes.draw do
.
.
.
  resources :microposts,          only: [:create, :destroy]
end

logged_in_userメソッドをApplicationコントローラに移し、その後、microposts_controller.rbも修正

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

homeページを修正

<div class="ta_center">
<br>
<br>
<br>

<% if logged_in? %>
  <%#= render 'shared/user_info' %>
  <h1 class="title_signup">
    <%= link_to gravatar_for(current_user, size: 50), current_user %>
    <%= current_user.name %>
  </h1>
  <span><%= link_to "view my profile", current_user %></span>
  <span><%= pluralize(current_user.microposts.count, "micropost") %></span>
  <br>
  <br>

  <%#= render 'shared/micropost_form' %>
  <%= form_for(@micropost) do |f| %>
    <%= render '/users/error_messages', object: f.object %>
    <div class="field container">
      <%= f.text_area :content, placeholder: "Compose new micropost..." %>
    </div>
    <%= f.submit "Post", class: "btn btn-primary" %>
  <% end %>

<% else %>
  <h1 class="title_signup">HOME</h1>
  <br>
  <%= link_to "サインアップする", signup_path, class: "btn btn-lg btn-primary" %>
<% end %>
<br>
<br>
<br>
</div>
<% if object.errors.any? %>
  <div id="error_explanation">
    <h2>
        The form contains <%= pluralize(object.errors.count, "error") %>
    </h2>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>
class StaticPagesController < ApplicationController
  def home
     @micropost = current_user.microposts.build if logged_in?
  end

  def help
  end

  def about
  end

  def contact
  end
end

フィードの原型

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

  private
.
.
.
end

homeアクションにフィードのインスタンス変数を追加する

class StaticPagesController < ApplicationController
  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      @feed_items = current_user.feed.paginate(page: params[:page])
    end
  end
end

ステータスフィードのパーシャル

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

Homeページにステータスフィードを追加する

  <%= render 'shared/feed' %>

createアクションに空の@feed_itemsインスタンス変数を追加する

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
end

マイクロポストを削除する

<%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %>
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 correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end

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

ここまででマイクロポストに関する基本的な操作はすべて実装できた。(作成、表示、削除)

基本的な画像アップロード

# 画像upload
gem 'carrierwave'
gem 'mini_magick'
gem 'fog'
$ bundle install --path vendor/bundle

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

$ bundle exec rails generate uploader Picture
$ bundle exec rails generate migration add_picture_to_microposts picture:string
$ bundle exec rails db:migrate

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

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
  <%= form_for(@micropost) do |f| %>
    <%= render '/users/error_messages', object: f.object %>
    <div class="field container">
      <%= f.text_area :content, placeholder: "Compose new micropost..." %>
    </div>
    <span class="picture">
      <%= f.file_field :picture %>
    </span>
    <%= f.submit "Post", class: "btn btn-primary" %>
  <% end %>

Webから更新できる許可リストにpicture属性を追加

class MicropostsController < ApplicationController
.
.
.
  private

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

マイクロポストの画像表示を追加する

<li id="micropost-<%= micropost.id %>">
  <div class="image">
    <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  </div>
  <div class="contents">
    <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
    <span class="content"><%= micropost.content %></span>
    <span class="content-image"><%= image_tag micropost.picture.url if micropost.picture? %></span>
    <span class="timestamp">
      Posted <%= time_ago_in_words(micropost.created_at) %> ago.
      <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %>
    </span>
  </div>
</li>

これで画像がuploadできるようになったが、画像の検証ができていない

超巨大ファイルをuploadされることなども想定しないといけない。

拡張子を制御

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
<span class="picture">
  <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
</span>

画像の容量を制御

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
//= require jquery
//= require_tree .

$(function(){
  $('#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.');
    }
  });
});

画像をリサイズする

$ brew install imagemagick
class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  process resize_to_limit: [1000, 1000]

  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

herokuにup

ここまで行えば、画像の upができるようになったので、herokuにupする

$ git push heroku master
$ heroku pg:reset DATABASE
$ heroku run rails db:migrate
$ heroku run rails db:seed

あとは、第14章 ユーザーをフォローするだけ

コメント

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

内容に問題なければ、下記の「コメントを送信する」ボタンを押してください。

同じカテゴリの前後の記事


  1. KATOON.NET
  2. TRASH
  3. ユーザーのマイクロポスト | rails チュートリアル 19