見出し画像

レビュー投稿から新着投稿表示ページの実装

初めましてこんにちは!すんぎぃです。 SoundReviewというアプリができるまでの流れをやっています。 
今回は、レビューの新着投稿一覧ページ、レビューの投稿機能について話していきます。


 1.目的

 音声でのレビューなどの投稿をできるようにします。
基本的に変更されないデータを、データベースに保存せずに取り扱えるようにします。
また、投稿したものを表示して見ることができるようにします。


 2.扱うgem

carrierwave
audiojs-rails
active_hash

 3.方法

○投稿表示ページ


①まず、routingにて以下のように書き込みます。

Rails.application.routes.draw do
 mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
 devise_for :users
 root to: 'items#index'
 resources :items ,only: [:index]
   
end

まず、コントローラーを動かすためにルーティングを作成します。
railsではresourcesメソッドを使うことで図1のような7つのアクションを使うことができるようになります。

画像1

今回は、表示を行いたいのでindexアクションを使い指定したアクションのみのルーティングを生成するためにonlyオプションを使い resources :items ,only: [:index]としています。

②itemsコントローラーを作成します。terminalにて以下のコマンドを実行します。

% rails g controller items

③indexアクションをコントローラーに定義していきます。
以下のようにitemsコントローラーにindexアクションを定義します。

def index
   @items = Item.all.order('created_at DESC').limit(12)
 end

④投稿一覧のビューを作成します。
app/views/itemsディレクトリにビューファイルindex.html.erbを作成します。

⑤index.html.erbを以下のように編集します。

<%=render "shared/header"%>
<div class='main'>
     <div class="main-class">
       <div class="categories">
         <h1>Category</h1>
         <div class="category">
           <%= link_to "映画", "/" ,class:"category-name"%>
         </div>  
         <div class="category">
           <%= link_to "アニメ", "/" ,class:"category-name"%>
         </div>  
         <div class="category">
           <%= link_to "音楽", "/",class:"category-name" %>
         </div>  
         <div class="category">
           <%= link_to "本", "/",class:"category-name" %>
         </div>  
         <div class="category">
           <%= link_to "その他", "/" ,class:"category-name"%>
         </div> 
          
       </div>
   
       <video autoplay muted playsinline src="/videos/sound1.mp4",playsinline autoplay loop ></video>
     </div>
       <div class='item-contents'>
         <h2 class='title'>Pick Up</h2>
         <div class="subtitle" >
           new reviews
         </div>
         <ul class='item-lists'>
     
           <%@items.each do |item|%>
           <li class='list'>
             <%= link_to "/",class:"link" do %>
             <div class='item-info'>
               
               <%= item.name%>
               
               
             </div>
             <div class='item-img-content'>
               
               <%if item.image.attached? %>
                 <%= image_tag item.image, class: "item-img"  %>
               <%else%>
                 <%= image_tag 'soundreview2.png', class: "item-img"  %>
               <%end%> 
                
             </div>
             <div class="profile-all">
             <div class="profile2">
               <% if item.user.image.attached? %>
                 <%=link_to "/",class:"user-icon4" do %>
                   <%= image_tag item.user.image, class: "user-icon4" %>
                 <%end%>  
               <% else %>
                 <%=link_to "/",class:"user-icon4" do %>
                   <%= image_tag "willy.png", alt: "user-icon", class: "user-icon4" %>
                 <%end%>
               <% end %>
               
             </div>
             <div class="item-create">
               <div class="item-user-name">
                 <%= link_to item.user.nickname, "/" %>
               </div> 
               <div class="created_at" >
                   <%=item.created_at.strftime('%Y/%m/%d')%>
               </div>
               
             
             </div>
             
             
             <% end %>
           </li>
           <%end%>
         </ul>
         
         
       </div> 

       
     <%= link_to new_item_path, class: 'item-btn' do %>
     <span class='item-btn-text'>Post Review</span>
     <%= image_tag 'icon_camera.png' , size: '79x50' ,class: "item-btn-icon" %>
     <% end %>     
   </div>
   <%=render "shared/footer"%>

背景に使われている画像などは、好きな画像をapp/assets/imagesディレクトリに配置します。
投稿されたものの表示は、eachメソッドを使い表示させます。

○投稿の保存の実装


①Itemモデルの作成を行います。
terminalにて以下のコマンドを実行します。

% rails g model item

②テーブルを作成します。
モデル作成時に作られたdb/migrateディレクトリ以下にあるマイグレーションファイルの編集を行います。

class CreateItems < ActiveRecord::Migration[6.0]
 def change
   create_table :items do |t|
     t.string      :name, null: false
     t.text        :text
     t.integer     :category_id, null: false
     t.string      :audio,       null: false
     t.string      :url
     t.references  :user, null: false, foreign_key: true
     t.timestamps
   end
 end
end

t.に続くのが「型」で、:に続くのが「カラム名」です。

③マイグレーションファイルの実行を行います。
terminalにて以下を実行します。

%rails db:migrate

④音声を投稿するために新たなgemの追加を行います(carrierwave)。
また、音声ファイルの再生機能とダウンロード機能を実装するためのgemの追加を行います(audiojs-rails)。

gem 'carrierwave'
gem 'audiojs-rails'

terminalにて以下を実行します。

% bundle install

④audiojsの読み込みをするためにapp/javascript/packs/application.jsに以下を追記します。

//= require audiojs

④アップローダーの作成を行います。
terminalで以下のコマンドを実行します。

% rails generate uploader Audiofile

⑤データベースに保存したものを使わずにカテゴリー選択をしなくて済むようにします。
まずGemfileに以下を追加します。

gem 'active_hash'

そしてterminalで以下を実行します。

% bundle install

⑥app/modelsディレクトリ以下にcategory.rbを作成します。

⑦Categoryモデルにデータを定義します。
category.rbを以下のように編集します。

class Category < ActiveHash::Base
 self.data = [
   { id: 1, name: '---' },
   { id: 2, name: '映画' },
   { id: 3, name: 'アニメ' },
   { id: 4, name: '音楽' },
   { id: 5, name: '本' },
   { id: 6, name: 'その他' }
 ]
 include ActiveHash::Associations
 has_many :items
end

ActiveHash::Baseは、モデル内でActiveHashを用いる際に必要となるクラスです。また、ActiveHash::Baseを継承することで、ActiveRecordと同じようなメソッドを使用できるようになります。

⑥itemモデル、Categoryモデル、Userモデルのアソシエーション、バリデーションを設定します。(ここでは、itemモデルについてだけあげています。)

class Item < ApplicationRecord
 extend ActiveHash::Associations::ActiveRecordExtensions
 belongs_to :category
 belongs_to :user
 has_one_attached :image

 with_options presence: true do
   validates :name
   validates :category_id
   validates :audio
 end
 validates :category_id, numericality: { other_than: 1 }
 mount_uploader :audio, AudiofileUploader
end

ActiveHashを用いてアソシエーションを設定する場合は、ActiveHashで定義されている*moduleをモデルに取り込む必要があります。
ここでは
extend ActiveHash::Associations::ActiveRecordExtensions
としてmoduleを取り込んでいます。

*moduleとは、特定の役割を持つメソッドや定数に役割をつけてまとめたものです。


⑦routingにて以下のように書き込みます。

Rails.application.routes.draw do
 mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
 devise_for :users
 root to: 'items#index'
 resources :items ,only: [:index,:new]
   
end

生成、保存をしたいのでnewアクション、createアクションをつけました。

⑧itemsコントローラーを以下のように編集します。

class ItemsController < ApplicationController
 before_action :authenticate_user!, except: [:index]
 
 def new
   @item = Item.new
 end
 
 def create
   @item = Item.new(item_params)
   if @item.save
     redirect_to root_path
   else
     render :new
   end
 end
 

 def index
   @items = Item.all.order('created_at DESC').limit(12)
 end
 
 private
 
 def item_params
   params.require(:item).permit(:name, :text, :audio, :category_id, :url, :image, :minicategory).merge(user_id: current_user.id)
 end
 
end

createアクションは、フォームで送られてきたデータをもとに、レコードを保存します。ここでは、*ストロングパラメーターを使った保存を行なっています。また、privateメソッドを使うことによってclassの外部から呼ばれたら困るメソッドを隔離することができ、また可読性を高めることができます。

*ストロングパラメーターを使う理由としては、悪意のあるユーザーによる意図しないデーターベースの読み書きを防ぐためです。
ストロングパラメーターの定義として、*requireメソッドと、*permitメソッドを組み合わせてします。

*requireメソッドは、パラメーターからどの情報を取得するか選択します。ストロングパラメーターとして使用する場合は、主にモデル名を使用します。

*permitメソッドは、取得したいキーを指定でき、指定したキーと値のセットのみを取得します。permitメソッドを使うことによってカラムに保存したいものだけに絞ることができます。

⑨app/views/itemsディレクトリにビューファイルnew.html.erbを作成します。

⑩new.html.erbを以下のように編集します。

<div class="items-sell-contents">
 <header class="items-sell-header">
   <%= link_to image_tag('soundreview.png' , size: '185x50'), "/" %>
 </header>
 <div class="items-sell-main">
   <h2 class="items-sell-title">レビューの情報を入力</h2>
   <%= render 'shared/error_messages', model: @item %>
   
   <%= form_with model:@item, local: true do |f| %>
   <div class="items-detail">
     <div class="weight-bold-text">
     カテゴリー
     <span class="indispensable">*必須</span>
     </div>
     <div class="form">
       
       <%= f.collection_select(:category_id, Category.all, :id, :name, {}, {class:"select-box", id:"item-category"}) %>
     </div>
   </div> 
   <div class="new-items">
     <div class="weight-bold-text">
       サブカテゴリー
     </div>
     <%= f.text_area :minicategory, class:"items-text", id:"item-name" %>
   </div>   
   <div class="img-upload">
     <div class="weight-bold-text">
       レビュー画像
       
     </div>
     <div class="click-upload">
       <p>
         クリックしてファイルをアップロード
       </p>
       <%= f.file_field :image, id:"item-image" %>
     </div>
   </div>
   <div class="new-items">
     <div class="weight-bold-text">
       作品名<span class="counter">(40文字まで)</span>
       <span class="indispensable">*必須</span>
     </div>
     <%= f.text_area :name, class:"items-text", id:"item-name" %>
   </div>  
   <div class="sound-upload">
     <div class="weight-bold-text">
       音声レビュー
       <span class="indispensable">*必須</span>
     </div>
     <div class="click-upload">
       <p>
         クリックしてファイルをアップロード
       </p>
       <%= f.file_field :audio, id:"item-sound" %>
     </div>
     
   </div>
   <div class="new-items">
     <div class="items-explain">
       <div class="weight-bold-text">
         文字レビュー
       </div>
       <%= f.text_area :text, class:"items-text", id:"item-info"%>
     </div>
   </div>
   <div class="item-url">
     <div class="weight-bold-text">
       作品URL
       
     </div>
     <%= f.text_area :url, class:"url", id:"item-name",class:"url" %>
   </div>  
   <div class="sell-btn-contents">
     <%= f.submit "投稿する" ,class:"sell-btn" %>
     <%=link_to 'もどる', root_path, class:"back-btn" %>
   </div>
   
 </div>
 <% end %>
 <footer class="items-sell-footer">
   <%= link_to image_tag('soundreview.png' , size: '185x50'), "/" %>
   <p class="inc">
     ©︎SoundReview,Inc.
   </p>
 </footer>
</div>

form_withを使い、レビュー投稿フォームを実装します。form_withは、modelオプションに指定されたインスタンス変数の状態により、リクエストを送るアクションを判断しています。@itemは、newアクションで定義したインスタンス変数であるため、リクエストは、createアクションに送信されます。

11.app/assets/stylesheets/items以下にindex.scssとnew.cssを追加し以下のように編集します。

index.scss

* {
 box-sizing: border-box;
 margin:0;
padding:0;
}
.wrap{
 overflow: hidden;
}
.main{
 width:100vw;
 position:relative;
 
}
.ma{
 background-color: rgb(156, 206, 221);
}
video{
 
 width: 100%;
 
}
.categories{
 
	position: absolute;
	left: 10vh;
	top: 3vh;
	z-index: 1;
}
.category{
 font-family: serif;  
	color: white;
	font-size: 3.5vw;
 cursor: pointer;
}
.categories h1{
 font-family:serif;         
	color: white;
 font-size:5vw;
}
.item-name{
 color:black;
}
.item-contents {
 background-color: #FFF;
 display: flex;
 flex-direction: column;
 align-items: center;
 padding: 10vh 0;
}
.item-contents>.title {
 font-size: 5vh;
 line-height: 1.4;
 font-weight: bold;
}
.item-contents>.subtitle {
 font-size: 3vh;
 margin: 1vh 0;
 text-decoration: none;
 color: black;
 font-weight: bold;
}
.item-user-name{
 font-size:26px;
 margin:2.7vh 0 0 0;
}

.item-img {
 width: 33vh;
 height: 33vh;

}
.good-county{
 margin-top:4vh;
 margin-left:23px;
}
.item-btn {
 width: 130px;
 background: rgba(87, 89, 109,0.5);
 text-align: center;
 border-radius: 10%;
 bottom: 32px;
 right: 32px;
 position: fixed;
 z-index: 10;
 padding: 10px ;
 text-decoration: none;
}
.item-btn-text {
 color: #fff;
 display: block;
 font-size: 18px;
 text-decoration: none;
 margin-bottom: 5px;
}
.item-btn-icon {
 width: 60%;
}

.title,.subtitle,.item-info{
 font-family:inherit;
}
.item-info{
 font-weight: bold;
}
.item-lists {
 width: 100vw;
 display: flex;
 flex-wrap: wrap;
 justify-content: space-around;
}
.item-lists>.list {
 width:25%;
 padding: 1vw;
 background-color: #FFF;
 
}


new.css

.items-sell-contents {
 background-color:  rgb(156, 206, 221);
 font-family: "Source Sans Pro", Helvetica, Arial, "游ゴシック体", "YuGothic", "メイリオ", "Meiryo", sans-serif;
 font-size: 14px;
 display: flex;
 align-items: center;
 flex-direction: column;
}
.items-sell-header {
 padding: 5vh 0;
 text-align: center;
}
.items-sell-main {
 background-color: #FFF;
 width: 70vw;
 min-width: 450px;
 padding: 10vh 15vw;
 font-size:18px;
}
.items-sell-title {
 font-size: 24px;
 font-weight: bold;
 text-align: center;
}
.items-comment-title {
 font-size: 20px;
 font-weight: bold;
 text-align: center;
 margin-top:50px;
 border:1px solid black;
}
.indispensable {
 margin-left: 8px;
 padding: 3px;
 border-radius: 3px;
 color: #ea352d;
 font-size: 12px;
 font-weight: bold;
}
.weight-bold-text {
 font-weight: bold;
 min-width: 170px;
 margin-bottom: 10px;
}
.counter{
 font-size:18px;
}
.select-box {
 height: 30px;
 width: 100%;
 background-color: #f5f5f5;
 padding-left: 20px;
}
.img-upload {
 display: flex;
 flex-direction: column;
 flex-wrap: wrap;
 padding: 2vh 0;
}
.click-upload {
 margin: 15px 0;
 min-width: 250px;
}
.click-upload>p {
 margin-bottom: 15px;
 font-size:14px;
}

.new-items {
 padding: 2vh 0;
}
.items-text {
 width: 100%;
 padding: 16px 16px 8px;
 border-radius: 4px;
 border: 1px solid #ccc;
 font-size: 16px;
 resize: none;
}
.items-detail {
 
 padding: 2vh 0;
}
.items-detail>.form {
 width: 200px;
 padding: 2vh 0;
}
.select-box {
 margin: 2vh 0;
}
.item-name{
 width:100vh;
}
.url{
 width:100%;
}

.sell-btn-contents {
 width:100%;
 text-align: center;
 display:flex;
 flex-direction: column;
 align-items: center;
}
.sell-btn {
 background: #f85408;
 border: 1px solid #f85408;
 color: #fff;
 width: 53%;
 height: 50px;
 font-size: 18px;
 margin: 4vh 0;
 border-radius: 30px;
}
.back-btn {
 background: #aaa;
 border: 1px solid #aaa;
 color: #fff;
 padding: 15px 20%;
 text-decoration: none;
 border-radius: 30px;
 
}
.items-sell-footer {
 background-color:  rgb(156, 206, 221);
 width: 60vw;
 padding: 3ch 0;
 display: flex;
 flex-direction: column;
 align-items: center;
 color:white;
}

.items-sell-footer>.menu {
 display: flex;
 flex-wrap: wrap;
 justify-content: center;
 margin: 3vh 0;
}
.menu>li>a {
 color:white;
 font-size: 12px;
 margin: 2vw;
 text-decoration: none;
}
.inc {
 font-size: 13px;
}
img{
 max-width:100%;
 height:auto;
}
.f-img{
 max-width:450px;
 height:450px;
}こ

また、app/viewsディレクトリにsharedディレクトリを作成し以下のファイルをダウンロードし配置します。

app/assets/stylesheets/shared以下にもファイルを配置します。

terminalにて以下のコマンドを実行し

% rails s

以下のように表示せれユーザーログインし投稿ができ表示することができたら完成です。

画像2


これにて完成です。ありがとうございました。



この記事が気に入ったらサポートをしてみませんか?