Ralisで動画とユーザーのタグを合わせて、ユーザーの属性を紐づける

今回は動画に対して後からユーザーによる紐付けを行い、また自由にユーザーは属性を帰ることができる方法を書いていきたいと思います。

データベース

動画投稿用のデータベース

create_post.rb

class CreatePosts < ActiveRecord::Migration[6.0]
  def change
    create_table :posts do |t|
      t.string :title, null: false
      t.string :youtube_url 
      t.text   :content, null: false
      t.references :user, null: false, foreign_key: true
      t.references :game, null: false, foreign_key: true
      t.references :grade, null: false, foreign_key: true
      t.references
      t.timestamps
    end
  end
end

ユーザーのゲーム名、階級を紐づけるためのデータベース

create_game_player.rb

class CreateGamePlayers < ActiveRecord::Migration[6.0]
  def change
    create_table :game_players do |t|
      t.references :user, null: false, foreign_key: true
      t.references :game, null: false, foreign_key: true
      t.references :grade, null: false, foreign_key: true
      t.timestamps
    end
    add_index  :game_players, [:user_id, :game_id], unique: true
  end
end

ゲーム名と階級の事前入力

seed.rb

Game.create(:game_title => 'マリオカート')
Game.create(:game_title => 'ポケモン')
Game.create(:game_title => 'クラッシュロワイヤル')
Grade.create(:grades => '初級')
Grade.create(:grades => '中級')
Grade.create(:grades => '上級')

今回はこちらのデータを使用いたしました。 まずは動画を投稿します。 posts_controller

  #中略
  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    url = params[:post][:youtube_url]
    url = if @post.youtube_url[0..16] == 'https://youtu.be/'
            url[17..27]
          elsif @post.youtube_url[43] == '&'
            url[32..42]
          else
            url.last(11)
          end
    @post.youtube_url = url
    if @post.save
      redirect_to root_path
    else
      render :new
    end
  end
      #中略

今回はyoutubeを埋め込み使用をするため、識別のためのコードを持ってくる必要がありました。 こちらの処理を利用すると大体の場合のURLには対応することができます。

登録機能

game_controller

def create
    @game_players = GamePlayer.new(game_player_params)
    @already_game_players = GamePlayer.where(user_id: current_user.id, game_id: params[:game_player][:game_id])
    if @game_players.grade_id.blank?
      redirect_to new_game_path
      flash[:alert] = '階級が記入されていません'
    elsif @already_game_players.present?
      @already_game_players.update(game_player_params)
      redirect_to new_game_path
      flash[:notice] = '更新しました'
    elsif @game_players.save
      redirect_to new_game_path
      flash[:notice] = '登録しました'
    else
      render :new
      flash[:alert] = '登録に失敗しました'
    end
  end

上記が登録をする処理を行うコントローラーになります。

elsif @already_game_players.present?
      @already_game_players.update(game_player_params)
      redirect_to new_game_path
      flash[:notice] = '更新しました'

こちらを記入することで前回と同じ階級を選択された場合でも更新がされます。

まず動画を投稿します。

<div class="posts">
  <h1 class="upload">投稿画面</h1>
  <%= form_with model: @post, url: posts_path, local: true do |f| %>
  <%= render "shared/errors_messages", model: f.object %>
  <%# タイトル %>
  <div class="post-field">
    <p>タイトル名</p>
    <%= f.text_area :title, class:"text-area", id:"title", placeholder:"タイトル 50文字まで", maxlength:"50" %>
  </div>
  <%# youtubeのURL %>
  <div class="post-field">
    <p>YoutubeのURL</p>
    <%= f.text_area :youtube_url, class: "text-area", id: "youtube-url", placeholder: "URLを記入", maxlength:"50" %>
  </div>
  <div class="post-field">
    <p>解説</p>
    <%= f.text_area :content, class: 'post-content', id: "content", placeholder: "2000文字まで", maxlength: "2000" %>
  </div>
  <%# 投稿するゲーム名選択 %>
  <div class="game-title">
    <%= f.select :game, Game.all.map { |game| [game.game_title, game.id] }, { prompt: '選択してください' } %>
  </div>
  <%# どの階級に向けて投稿したいか %>
  <div class="grade">
    <%= f.select :grade, Grade.all.map { |grade| [grade.grades, grade.id] }, { prompt: '選択してください' } %>
  </div>
  <div class="submit">
    <%= f.submit "送信" %>
  </div>
  <% end %>
</div>

投稿機能は自分で投稿先を指定する機能を設けました。 仮に投稿内容を

で投稿します。

そしてそれを表示させるためのゲーム情報を登録します。

<div class="game-maneger-body">
  <h1 class="game-maneger-title"><%= current_user.nickname %>さんのゲーム情報登録</h1>
  <% @games.each do |game|%>
    <%= form_with model: @game_players, url: games_path, local: true do |f| %>
      <%= render "shared/errors_messages", model: f.object %>
      <div class="game-title"> 
        <%= game.game_title %>
        <%= f.hidden_field :game_id, :value => game.id %>
        <%= f.hidden_field :user_id, :value => current_user.id %>
      </div>
      <div class="grade">
        <div class="graded">
          <p>現在の階級</p>
          <% already_game_player = GamePlayer.find_by(user_id: current_user.id, game_id: game.id) %>
          <% if already_game_player.present? %>
          <%= f.select :grade_id, Grade.all.map { |grade| [grade.grades, grade.id] }, { prompt: '選択してください', selected: already_game_player.grade_id }, include_blank: true %>
          <% else %>
          <%= f.select :grade_id, Grade.all.map { |grade| [grade.grades, grade.id] }, { prompt: '選択してください' }, include_blank: true %>
          <% end %>
        </div>
        <div class="actions" id="game-maneger-btn">
          <%= f.submit "登録" %>
        </div>
      </div>
    <% end %>
  <% end %>
</div>

ここでゲーム名:マリオカートの下の階級の値に初級を入力するとroot_pathにマリオカート初級の動画がが一覧に表示される仕組みになります。

一覧機能

次に一覧についてです。しかし今回の場合問題が発生しています。 これに関しては私の勉強不足,技術力不足で申し訳ないのですが、動画毎にデータベースとのやりとりをしなければならず、SQLの発行回数が多いため、データベースに負担をかけてしまっていることが問題になります。 そのため、見ていただいいた方で、改善する方法がある場合は指摘をいただきたいです。 今回の場合はページネーション機能を利用し、自分なりデータベースに対しての負荷対策を施しました。

まずはコントローラーの処理です。

def proto_recommend
    unless current_user.nil?
      @game_player = GamePlayer.where(user_id: current_user.id)
      @post_data = []
      i = 0
      ## ユーザーの登録しているゲーム名と階級と同じidを持つ動画を取り出す
      @game_player.length.times do
        @post = Post.where(game_id: @game_player[i].game.id, grade_id: @game_player[i].grade.id).limit(3)
        @post_data << @post
        i += 1
      end
    end
  end

今回はこちらのコードを使って処理をしています。 まず、ユーザー自身のゲーム情報を引き出します。 そしてi = 0の式を利用し、各登録しているゲーム情報ごとに対応している動画を引き出し、@post_dataに格納していきます。 この場合はゲームの種類が3種であるため、最大三回の繰り返しを行います。 そして、SQLの発行回数軽減のため、各ゲーム名ごとに3つまでの動画を引き出します。

<div class="posts-body">
  <%= render "shared/side_bar" %>
  <div class="posts-index">
    <%# ↓ 変数 @post_dataが3分割で定義されているため %>
    <% i = 0 %>
    <% unless current_user.nil? %>
      <% @game_player.map do |game| %>
        <% if @game_player != nil %>
          <div class="sub-header">
            <div class="info"><%= game.game.game_title %>のあなたに近いレベルの人の動画</div>
            <div class="more"><%= link_to 'もっと見る', grade_path(game.grade.id, game_id: game.game.id) %> </div>
          </div>
          <div class="videos">
            <% @post_data[i].each do |post|%>
              <div class="video">
                <%= link_to post_path(post.id) do %>
                  <img src="https://img.youtube.com/vi/<%= post.youtube_url %>/mqdefault.jpg" />
                <% end %>
                <div class="youtube-title">
                  <%= link_to post.title, post_path(post.id), method: :get %>
                  <%= link_to post.user.nickname, user_path(post.user.id), method: :get, class: "video-user"%>
                </div>
                <div class="video-status">
                  <%= post.game.game_title %><%= post.grade.grades %>
                </div>
              </div>
            <% end %>
          </div>
        <% i += 1 %>
        <% end %>
      <% end %>
    <% else %>
      <p class="no-login">ログイン/サインアップをしてください</p>
    <% end %>
    <div class="sub-header">
      <div class="info">動画一覧</div>
      <div class="more"><%= link_to 'もっと見る', games_path %></div>
    </div>
    <%= render 'shared/videos' %>
  </div>
</div>

次に一覧の表示のための処理です。 まずは、各ゲームごとにゲーム名を表示する様にします。 こちらもi = 0を利用した繰り返し処理を利用していきます。 そして、@post_dataは各ゲームごとに分割してデータを呼び出すことができる処理を行っています。 そのため、各ゲームの動画を@post_data[1] の動画を全て出し切ったらi += 1を行い、 @post_data[2]行う処理を行います。 それにより、ユーザー自身に登録していただいたゲーム情報に基づいて一致した動画を引き出すことができる仕組みです。 一応登録されていない場合の処理を考え、無指定の動画一覧を表示する機能も付けております。

今回初めてアプリを作成した形でしたが、自分では苦労する部分が多く、特にデータベース設計に関して、時間を要してしまいました。 この機能をいつかもう少し軽く処理できるよう勉強を続けていきたいと思います。