dependent: :destroyを中間テーブル関係先につけてると発生するエラー

中間テーブルで関係を持っている親同士のテーブルにdependent: :destroyをつける時に発生するエラーについて書いていきたいと思います。

deviseの編集機能(registrations/edit.html.erb)でアカウントを削除しようとした際に発生するエラーになります。

registrations/edit.html.erb

<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>

デフォルトだとこの様になっているコードがアカウントの削除を担ってくれます。

モデルの状況がこちらです。

 has_many :game_player, dependent: :destroy
  has_many :game, through: :game_players, dependent: :destroy
  has_many :grades, through: :game_players, dependent: :destroy
  has_many :posts, dependent: :destroy
  has_many :comments, dependent: :destroy

この場合、games,gradeまで消そうとする状態になってしまうため、こちらのようなエラーが発生してしまいます。 解決方法としては、親としてのつながりのあるgames,gradeからdependent: :destroyを消すことで解決することができます。 中間テーブルも同時に消していきたい場合は、中間テーブルのみにdependent: :destroyを記述するようにしましょう。

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]行う処理を行います。 それにより、ユーザー自身に登録していただいたゲーム情報に基づいて一致した動画を引き出すことができる仕組みです。 一応登録されていない場合の処理を考え、無指定の動画一覧を表示する機能も付けております。

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

アプリ開発における経緯

今回ポートフォリオ用にアプリを開発することになったが、できれば、完成後に運用して、自分のアプリをリリースしていきたいと考えているため、開発するために考えた、経緯について記録していきたいと思う。

動機

個人的にはゲームをやるのが非常に好きであり、長時間ゲームをすることが多かった、最近はなかなかできてはいないものの自由に過ごしていい時間が訪れたらゲームをするつもりである。 そして、ゲームをやっているうちにやってくる悩みがある。 それは伸び悩みである。どのプレイヤーにも必ず起きるである現象であり、PvPをやる上では切り離すことはできない。頑張っても全体で見れば勝者も敗者も割合的には増減しないからである。所詮ゲームは格差を利用して楽しむものである。 仲間との共闘を楽しむものでもあるかもしれないが、勝たなければ面白くないものである。 そのため、ゲームの楽しみ方を新しく提案する形のアプリを開発することにした。 これは自分の伸び悩みに対しても落とし所を作りたい側面もあった。

アプリの目的

このアプリは戦術のみを投稿をするアプリである。 これだけではYoutubeと変わりがない。 そのため、追加した機能としては自分の力量に合わせた階級制度を設定することとしている。 この機能には2つの目的がある。 1つは、まず自分の力量に近いプレイヤーを研究して対策をする方法を見つけてほしいということ。 2つ目は、上に上がるためには、当然強者を研究することが一番ではあるが、当然レベルに差があり、理解できない非言語的な強者限定の判断基準がある。 その基準との認識の齟齬を翻訳して解釈するには同程度より少し上の人間の研究を間に挟んで研究した方が、理解が高まると考えているからである。 ここまでは、見る側の目的であり、次に投稿者側の利点について2点述べていく。 1つ目は、自分の軌跡を残し、思い出として残して欲しいこと。 2つ目は、他人に評価されることで、ゲームのモチベーションを上げることができると考えたいる。

ポートフォリオの企画

今日からポートファオリオ制作を開始し、できれば、8月頃に実装を完了させたい。そのため、個人的に苦手な作業であるデータベース設計を短期間で進める必要がある。

ポートフォリオとして開発に至るまでの経緯

私はクラロワが非常に好きであり、勉強を始めるまでは、最低4時間はやっていた。しかし、最近になって、自分のゲームの戦略的な考え方に限界を感じ、他の人ゲームプレイを見て学習しようとしても、簡単に検索で見つかるのはまだまだ遠く及ばないプロゲーマーなどで、次元が違いすぎること、使っているカードが環境ごとに違う傾向があることから、全く参考にできず伸び悩んでいた。 振り返ってみて、自分が今まで、どんな戦略を使用していたのかを覚えていないし、記録も撮っていないので、なんとなく感覚で、こうすればいいみたいなものがあった。 そのため、自分と近い能力を持っているプレイヤーを研究したり、反省したりするアプリの開発をしたいと考えるようのなった。

企画段階

・書く側も見る側もある程度メリットのあるアプリに仕上げたいと考えている。 ・書く側のメリットは、知名度をつけること。アウトプットを通じて、経験した戦い方をしっかり自分で整理してもらうなどメリットがあると考えている。 ・見る側のメリットとしては、自分にあった戦略を見つけることができること、テキストの部分をYoutubeに比べて、見やすいように意識する。 ・動画のでデメリットであると感じている、自分の速度で情報を集めて進めていく流れを動画と同程度テキストメインで用いることで、動画だけを見る人、文字だけで理解したい人の両方に評価されるようなものを実装したいと考えているが、この機能は現状あまり現実的な手段が出てこないため、実装できない可能性もある。

用件定義などはまだ決定していないため、明日以降書いていくこととする。

自分について

今日から勉強についての記録を書いていきたいと思う。
何のために書くのか、について記録することとする。
まず、最近プログラミングスクールに入ったので、勉強を忘れないための日記のつもりで書いていきたいと思った。また、タイピングに自信がないので、練習も兼ねている、上達してきたら、HTMLで記入をしていきたい。

 

・目標

 

基本的には毎日書くようにする。

少しづつ学んだことをこの記事に反映させるようにする。

内容に迷っても毎日続けるようにする。

 

 

・プログラム関連の目標

 

自分と同等のゲームボットの作成

ゲームをまとめたサイトづくり

 

 

最初の目標であるが、基本的にはすぐにできそうな目標ではない、

動機としては、FPSにおけるamiiboのようなものを個人で開発してみたいと考えている。

そのうちどっかの企業が開発をするかもしれないが、

自分で作ってみたいと思っているし、あらゆるFPSゲームに反映できるようにして、

将棋AIのようにプロを倒せるくらいに性能のボットを製作したい。

基本的には就職して、休みの日に少しづつ進めるようになると考えているので、目標期間は定める事ができない。

単純にプログラムについての知識が足りないのもあり、できるまでのイメージが湧かないので、生涯目標とも言えるかもしれない。

 

二つ目の目標は、今の自分にとって何とか作れそうなアプリである。

当然まだHTMLを軽くやったぐらいではあるが、そのうち作れそうなので、仮目標として、決めておくことにする。

機能としては、ゲームをみんはやのように投稿集積させる形をとり、カテゴリーで分する機能、ランダムでゲームを表示する機能、アプリのダウンロードやゲームの商品タグをリンクしておくなどして買う事ができる機能をつけて、twitterのようなコメント機能とWIkipediaのような情報機能を両立させて、ゲームを求める人や昔のゲームを振り返りたい人などをターゲットにアプリ開発をしていきたいと考えている。

 

二つ目の目標に対しては、プロトタイプを2ヶ月程度で開発する予定でいる。

それもスクールの進捗次第ではあるが、実現度が高いと考えている。

 

これから毎日書くことで自分の成果物として機能することを願う。