##マイクロポストの画像投稿
画像付きマイクロポストを投稿できるようにしてみます。
画像アップロード機能を追加するためには、2つの視覚的な要素が必要です。
1つは画像をアップロードするためのフォーム、
もう1つは投稿された画像そのものです。
###基本的な画像アップロード
Railsに組み込まれているActive Storageという機能を用いることです。
Active Storageを使うことで画像を簡単に扱うことができ、画像に関連付けるモデルも自由に指定できます。
平文テキストはもちろん、PDFファイルや音声ファイルといったさまざまなバイナリファイルも扱えます。
$ rails active_storage:install
$ rails db:migrate
####Micropostモデルに画像を追加する
app/models/micropost.rb
class Micropost < ApplicationRecord
# ApplicationRecordを継承したモデルが作られます
belongs_to :user
# userモデルとの間に「1対1」のつながりが設定
# 割り当てる
has_one_attached :image
# 指定のモデルと、アップロードされたファイルを関連付ける
# この場合はimageを指定してMicropostモデルと関連付けます
default_scope -> { order(created_at: :desc) }
# データベースから要素を取得したときの、
# デフォルトの順序を指定するメソッドです。
# order(created_at:desc) でこのカラムの順序に指定する
# デフォルトで最も古い投稿が最初に投稿が表示されている。
# :desc 新しい投稿から古い投稿の順になります
# -> ラムダ式という。
validates :user_id, presence: true
# presence これは存在するか
# presenceメソッドはオブジェクトが存在すればそのオブジェクトを返し、
# オブジェクトが存在しなければnilを返すメソッドとなります
validates :content, presence: true, length: { maximum: 140 }
# 最大140文字まで
end
Active Storageではそのほかにhas_many_attachedオプションも提供しています。これは、Active Recordオブジェクト1件につき複数のファイルを添付できます。
####マイクロポストのcreateフォームに画像アップロードを追加する
app/views/shared/_micropost_form.html.erb
<%= form_with(model: @micropost, local: true) do |f| %>
<!--local: trueがない場合、Rails5ではAjaxによる送信という意味になる。
普通にHTMLとしてフォームを送信する場合にlocal: trueが必要になる-->
<!--マイクロポストをHTMLのフォームとして一つずつ取り出す-->
<%= render 'shared/error_messages', object: f.object %>
<!--sharedフォルダのerror_messagesを表示させる。-->
<!--object: f.objectでは、f.objectに@userが入っている-->
<!--object: f.objectはerror_messagesパーシャルの中でobjectという変数名を
作成してくれるので、この変数を使って
エラーメッセージを更新すればよいということです-->
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
<!--コメント欄を作ることができる-->
<!--その欄にコメントを書いておく-->
</div>
<%= f.submit "Post", class: "btn btn-primary" %>
<span class="image">
<%= f.file_field :image %>
<!--app/models/micropost.rbのhas_one_attached :imageに対応する-->
</span>
<% end %>
####許可済み属性リストにimageを追加する
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
# create,destroyを行う前にログインを求めらえれる。
before_action :correct_user, only: :destroy
def create
@micropost = current_user.microposts.build(micropost_params)
# 慣習的に関連するモデルを生成するときは、buildを使う
@micropost.image.attach(params[:micropost][:image])
# micropostオブジェクトに画像を追加
if @micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
# redirect_to は、view の表示には直接は関係なく、新たな HttpRequest が発行されます。
else
@feed_items = current_user.feed.paginate(page: params[:page])
render 'static_pages/home'
# action で view を指定しない場合、規約に従って、リソース名 や
# action名を元に、表示する view が決まります。
end
end
def destroy
@micropost.destroy
flash[:success] = "Micropost deleted"
redirect_back fallback_location: (root_url)
# redirect_bac 直前のページにリダイレクト
end
private
def micropost_params
params.require(:micropost).permit(:content, :image)
# マイクロポストのcontentカラムだけ取り出すことができる
# Web経由で更新できるようにする必要もあります。
end
def correct_user
@micropost = current_user.microposts.find_by(id: params[:id])
# idをもとにユーザーのマイクロぽすとを探す
redirect_to root_url if @micropost.nil?
# マイクロポストが空であればホーム画面に行く
end
end
あるメッセージに何らかの画像がアタッチされているかどうかを調べるには、images.attached?を呼び出します。
####マイクロポストの画像表示を追加する
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="content">
<%= micropost.content %>
<%= image_tag micropost.image if micropost.image.attached? %>
<!--image_tagヘルパーを用いて、関連付けられたmicropost.imageを
描画(レンダリング)できるようになります-->
<!--画像がアタッチされていたら画像を表示させる-->
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
<!--メソッド名の表すとおりですが、「3分前に投稿」といった文字列を出力します。-->
<% if current_user?(micropost.user) %>
<!--もし自分の投稿だったら-->
<%= link_to "delete", micropost, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</span>
</li>
###演習
1.
画像付きのマイクロポストを投稿してみましょう。もしかして、大きすぎる画像が表示されてしまいましたか?(心配しないでください、次の13.4.3でこの問題を直します)。
確認
リスト 13.64に示すテンプレートを参考に、13.4で実装した画像アップローダーをテストしてください。テストの準備として、まずはサンプル画像をfixtureディレクトリに追加してください(リスト 13.63)。リスト 13.64で追加したテストでは、Homeページにあるファイルアップロードと、投稿に成功した時に画像が表示されているかどうかをチェックしています。なお、テスト内にあるfixture_file_uploadというメソッドは、fixtureで定義されたファイルをアップロードする特別なメソッドです18 。ヒント: image属性が有効かどうかを確かめるときは、11.3.3で紹介したassignsメソッドを使ってください。このメソッドを使うと、投稿に成功した後にcreateアクション内のマイクロポストにアクセスするようになります。
require 'test_helper'
class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "micropost interface" do
# 完全に削除されたか?
log_in_as(@user)
# ログインさせる
get root_path
# ホーム画面に転送することを要求
assert_select 'div.pagination'
# 要求が認められページネートが表示されているか?
assert_select 'input[type= file]'
# 画像追加ボタンがあるか?
#type="file" 型の <input> 要素は、ユーザーが一つまたは複数のファイルを
# 端末のストレージから選択することができるようにします。
# ページの仕様を設定してるのか?↑
assert_no_difference 'Micropost.count' do
# 投稿が無効
# 無効な送信
post microposts_path, params: { micropost: { content: "" } }
# 空の投稿
end
assert_select 'div#error_explanation'
# エラ〜メッセージが表示されているか?
assert_select 'a[href=?]', '/?page=2'
# 正しいページネーションリンク
# ページネートが2ページに行くか?
content = "This micropost really ties the room together"
# 有効なマイクロポスト
image = fixture_file_upload('test/fixtures/kitten.jpg', 'image/jpeg')
# 'image/jpeg'はファイルの種類を表す
# テスト用の画像を返す
assert_difference 'Micropost.count', 1 do
# 投稿が一つ投稿されたか?
post microposts_path, params: { micropost:
{ content: content, image: image } }
# 画像を含んだ投稿を送信
end
assert assigns(:micropost).image.attached?
# assignsメソッドを使うと対応するアクション内のインスタンス変数に
# アクセスできるようになります。
# assignsメソッドはコントローラのインスタンス変数をテストするメソッド。
# 引数にインスタンス変数をシンボル型で渡す。
# root_urlにリダイレクトされているか?
follow_redirect!
# 後から調べる
assert_match content, response.body
# マイクロポストの数があれば、コンテントがあるか?
# 投稿を削除する
assert_select 'a', text: 'delete'
# aタグにdeleteがあるか?
first_micropost = @user.microposts.paginate(page: 1).first
#1枚目の 1番目のマイクロポストを返す
assert_difference 'Micropost.count', -1 do
# 削除して一つ減っているか?
delete micropost_path(first_micropost)
# 先のマイクロポストを削除する
end
# 違うユーザーのプロフィールにアクセス(削除リンクがないことを確認)
get user_path(users(:archer))
# archerのuser_pathに行くことを要求
assert_select 'a', text: 'delete', count: 0
# aタグのdeleteが表示されていないか?
end
test "micropost sidebar count" do
log_in_as(@user)
# ログインさせる
get root_path
# ログインできたらホームページへの移動を要求
assert_match "#{@user.microposts.count} microposts", response.body
# ページの中に{@user.microposts.count}があるか?
# まだマイクロポストを投稿していないユーザー
other_user = users(:malory)
# malory
log_in_as(other_user)
# maloryをログインさせる
get root_path
# root_path(ホーム画面)への要求
assert_match "0 microposts", response.body
# 0 micropostsがページのどこかにあるか?
other_user.microposts.create!(content: "A micropost")
# マイクロポストを作成する
get root_path
assert_match "1 micropost", response.body
# 1 micropostがページのどこかに表示されているか?
end
end
多分間違っていたらごめんなさい。