質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.49%

gem:kaminariを使用していて発生したundefined method `total_pages'というエラーを解決したい。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,314

score 13

前提・実現したいこと

現在Railsを使用してYouTubeの動画をタイトルとURLの末尾11桁を利用して記録するWebアプリを作り、
タグ機能を実装することで登録した動画をジャンルや単語でまとめることで擬似的にYouTubeのプレイリストを作成出来るようなアプリを作ろうとしています。

そこでgem kaminariを使用してのページネーションを実現しようとしていたのですが、
プルダウン式のタグ選択機能と併用しようとしていたら上記タイトル通りのエラーが発生してしまいました。

現状の自分の知識では該当するエラーを解決出来ず、検索しても自分の環境に該当するものや近い解決手段が見付からずどうしても直接解決に至りそうなものが見つからなかったので質問させていただきました。

未定義であると言われているtotal_pagesという単語についても自分でControllerModelに記述した記憶も無く心辺りが無くておそらくgem側が用意しているものなのでは無いかと思うのですが、
コードを書き換えて色々と試してみてもエラーの内容が全く変わらず途方に暮れているところです。

また、念の為、タグによる検索機能を排除した状態で通常通りのページネーションは可能なのかと思いindex.html.erbのタグのプルダウン
を行うための該当コードをコメントアウトしてみたところページネーションは問題無く動作しました。

おそらくmovieとplaylistを繋いでいることがページネーションに引数を渡して結果を表示させる際に干渉しているのではないかと考えました。

エラーメッセージ内に記載されているActiveRecord_Relationがなんとなくリレーションに関わるものであることは分かったのですが、該当するファイルがあるvendor以下のファイルについてはあまり触ったことが無く全く理解が出来ていない状態なのでどうして良いか分からない状態です。

予想ではmovies_controller.rb内のindexアクションの記述によって改善出来るのでは無いかと考えているのですが、どのように記述を変えるべきなのか見当が付いていない状況です。

勉強不足・力不足で自力ではまだまだ足りないところが多く皆様のお力添えを頂きたいと思い質問させていただきました。

どなたかご協力いただければ幸いです。宜しくお願いしたします。

発生している問題・エラーメッセージ

NoMethodError in Movies#index


undefined method `total_pages' for #<Movie::ActiveRecord_Relation:0x00007fde8d966888>

該当のソースコード

Modelはuser,playlist,movieそしてmovieとplaylistの間を繋いでリレーションを作っている中間テーブルのmovie_playlist_relationがあります。

# app/models/user.rb ユーザーのモデル

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :movies, dependent: :destroy
  has_many :playlists, dependent: :destroy
end
# app/models/playlist.rb プレイリストのデータを記録するためのモデル

class Playlist < ApplicationRecord
  has_many :movie_playlist_relations, dependent: :destroy
  has_many :movies, through: :movie_playlist_relations
  default_scope -> { order(created_at: :desc) }
end
# app/models/movie.rb 動画を登録しタイトルとURLを記録するためのモデル

class Movie < ApplicationRecord
  has_many :movie_playlist_relations, dependent: :destroy
  has_many :playlists, through: :movie_playlist_relations
  default_scope -> { order(created_at: :desc) }
end
# app/models/movie_playlist_relation.rb 中間テーブルのモデル

class MoviePlaylistRelation < ApplicationRecord
  belongs_to :movie
  belongs_to :playlist
end
# app/controllers/movies_controller.rb 動画を記録し記録した動画の一覧などを表示したりするためのController

class MoviesController < ApplicationController
  before_action :set_movie, only: [:show, :edit, :update, :destroy]
  # before_action :authenticate_user!

  def index
    # この箇所をどうにかしたいと思っているが解決策が分からない。
    @movies = Movie.page(params[:page])
    @movies = params[:playlist_id].present? ? Playlist.find(params[:playlist_id]).movies : Movie.all.page(params[:page])
  end

  def show
    @movie = Movie.find(params[:id])
  end

  def new
    @movie = Movie.new
    @movie.movie_playlist_relations.build
  end

  def edit
  end

  def create
    @movie = current_user.movies.build(movie_params)
    url = params[:movie][:youtube_url]
    url = url.last(11)
    @movie.youtube_url = url

    respond_to do |format|
      if @movie.save
        format.html { redirect_to @movie, notice: 'Movie was successfully created.' }
        format.json { render :show, status: :created, location: @movie }
      else
        format.html { render :new }
        format.json { render json: @movie.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @movie.update(movie_params)
        format.html { redirect_to @movie, notice: 'Movie was successfully updated.' }
        format.json { render :show, status: :ok,
        location: @movie }
      else
        format.html { render :edt }
        format.json { render json: @movie.errors, status:
        :unprocessable_entity }
      end
    end
  end

  def destroy
    @movie.destroy
    respond_to do |format|
      format.html { redirect_to movies_url, notice: 'Movie was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

    def set_movie
      @movie = Movie.find(params[:id])
    end

    # def movie_params
    #   params.require(:movie).permit(:title, :youtube_url)
    #   params.require(:playlist).permit(:playlist_name, playlist_ids:[])
    # end

    def movie_params
      params.require(:movie).permit(:title, :youtube_url, :playlist_name, playlist_ids: [])
    end
end
# routes.rb deiviseを使用しているためuser周りのルーティングがややごちゃごちゃしてしまっているかもしれません

Rails.application.routes.draw do
  root 'static_pages#home'
  get  'help', to:'static_pages#help'
  get  'about', to:'static_pages#about'
  get  'contact', to:'static_pages#contact'
  get  '/', to: 'users#index'
  devise_for :users, :controllers => {
    :registrations => 'users/registrations',
    :sessions => 'users/sessions',
    :passwords => 'users/passwords'
  }
  devise_scope :user do
    # get 'user/:id', to: 'users/registrations'
    get 'signup', to: 'users/registrations#new'
    get 'login', to: 'devise/sessions#new'
    post 'login', to: 'devise/sessions#create'
    delete 'signout', to: 'devise/sessions#destroy'
  end
  resources :users, :only => [:index, :show]
  resources :movies do
    resource :playlists, only: [:create, :destroy]
  end
  resources :playlists
end
# app/views/movies/index.html.erb

<p id="notice"><%= notice %></p>

<h1>Movies</h1>

  # ここからがタグのプルダウンをするためのコード
    <%= form_tag movies_path, method: :get, class: 'boards__searchForm' do %>
      <%= select_tag :playlist_id,
                     options_from_collection_for_select(Playlist.all, :id, :playlist_name, params[:playlist_id]),
                     {
                       prompt: 'プレイリストをタグで絞り込み検索',
                       onchange: 'submit(this.form);'
                     }
      %>
    <% end %>
  #ここまでがタグのプルダウンをするためのコード

<%= paginate @movies %>

<div class="container-fluid">
  <div class="row">
    <% @movies.each do |movie| %>
      <div class="col-md-6">
        <div>
          <%= movie.title %>
          <br>
       # この部分がデータベースに記録されたURLの末尾11桁から動画をサムネイルで引っ張って来るためのコード
          <iframe width="560" height="315" src="https://www.youtube.com/embed/<%= movie.youtube_url %>" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
          <br>
          <%= link_to 'Show', movie %>
          <br>
          <%= link_to 'Edit', edit_movie_path(movie) %>
          <br>
          <%= link_to 'Destroy', movie, method: :delete, data: { confirm: 'Are you sure?' } %>
          <br>
        </div>
      </div>
    <% end %>
  </div>
</div>

<br>

<%= link_to 'New Movie', new_movie_path %>
<%= paginate @movies %>

とりあえず原因となりそうなソースコードを列挙してみましたが、
原因になりそうなコードがまだ不足していたらすみません。
指摘していただければ追記させていただきます。

試したこと

kaminariの初期設定のために

$ bin/rails g kaminari:config


$ bin/rails g kaminari:views default

 

をして

# app/views/kaminari/_paginator.html.erb

<%# The container tag
  - available local variables
    current_page:  a page object for the currently displayed page
    # ここにtotal_pagesという項目がある。
    total_pages:   total number of pages
    per_page:      number of items to fetch per page
    remote:        data-remote
    paginator:     the paginator that renders the pagination tags inside
-%>
<%= paginator.render do -%>
  <nav class="pagination" role="navigation" aria-label="pager">
    <%= first_page_tag unless current_page.first? %>
    <%= prev_page_tag unless current_page.first? %>
    <% each_page do |page| -%>
      <% if page.display_tag? -%>
        <%= page_tag page %>
      <% elsif !page.was_truncated? -%>
        <%= gap_tag %>
      <% end -%>
    <% end -%>
    <% unless current_page.out_of_range? %>
      <%= next_page_tag unless current_page.last? %>
      <%= last_page_tag unless current_page.last? %>
    <% end %>
  </nav>
<% end -%>

にてtotal_pagesという項目を見付けることは出来ましたがこれをどう活用して良いのか分かりませんでした。

補足情報(FW/ツールのバージョンなど)

Ruby 2.6.3
Rails 5.2.4.1
devise (4.7.1)
kaminari (1.2.0)
kaminari-actionview (1.2.0)
kaminari-activerecord (1.2.0)

参考にした記事など

リレーションやタグ関連の実装に関してはこの記事を参考にさせていただきました。

railsでタグ検索機能を実装し多対多を理解する - Qiita

動画の埋め込みに関するコードについてはこの記事を参考にしています

railsアプリに投稿されたYouTubeURLを自動的に埋め込み表示させる方法~無理やり編~ - Qiita

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

check解決した方法

0

自己解決することが出来ました。
ただ、突然の思い付きでやってみただけでたまたまそれが噛み合って解決出来てしまった感じなので結局の所何が悪かったのか?という理屈については理解が出来ていません。今後の勉強でそれについても理解して行きたいと考えています。

では以下が解決に至った手段です。

# app/controllers/movies_controller.rb

  def index
    @movies = params[:playlist_id].present? ? Playlist.find(params[:playlist_id]).movies : Movie.page(params[:page])
  end

# app/controllers/movies_controller.rb

    @movies = params[:playlist_id].present? ? Playlist.find(params[:playlist_id]).movies.page : Movie.page(params[:page])

とすることで解決出来た。

元々このコード自体質問の参考にした記事の中にあったものを流用していたもので自身でも理解がしっかりと出来ていなかったのでずっとこのコードについて考えていたのですが、
:の前後で実はコードがif文のように分岐しているのではないか?と考えました。

moviesではタグを選んだ場合のページが表示される、
Movie.page(params[:page])ではタグを何も選択していない状態のページが表示される。
このような分岐があるのではないかと考え思い付きでmoviesの後に.page
を挿入してみた所エラーが表示されなくなりページが表示されるようになりました。

理屈を全く理解出来ていないのでこれが根本的な解決として捉えて良いのかまだ分からないのですが、
とりあえずエラー出なくなったので解決としたいと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.49%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る