<<私のスキルレベルや開発環境は最下部に掲載>>
【やりたいこと】
要約
Railsでアソシエーション先のテーブルのデータを用いて計算し、その計算値を元にソートを行う
具体的には…
4つのテーブルがあります。
Songsテーブル
Chordsテーブル
Practicesテーブル
Usersテーブル

※今回、関係ないカラム名は省略
Songsレコードのソートを行います。
計算値に基づいてdesc順に並べます。
計算値の求め方は次の通り
Songレコードの孫に当たるPracticeレコードの数を総計して算出。
ただしuser_idが同じものは重複カウントしない。
下図の例でいうと、practiceレコードの総計は5ですが、user_id:2が重複しているで、理想的な計算結果は4です。
計算自体は下記の記述でうまくいっています。
result = song.chords.joins(:practices).distinct.count('practices.user_id') ``` 計算値は仮想属性(Virtual Attribute)に代入。 ```Ruby on Rails # Songモデル 抜粋 class Song < ApplicationRecord attribute :song_practices, :integer, default: 0 attr_accessor :song_practices end
計算と結果の代入
song[:song_practices] = song.chords.joins(:practices).distinct.count('practices.user_id')
【問題】
@songs = @songs.order("song_practices desc")
orderメソッドを用いたソートを行おうとするが、エラー。
エラー文
「Mysql2::Error: Unknown column 'song_practices' in 'order clause': SELECT songs
.* FROM songs
ORDER BY song_practices desc」
次のように認識しました
→テーブルにそんなカラムはないよエラー
エラー検証
下記のコントローラにて、動作テストを実施。
binding.pryにて”check_point!!”で一時停止して変数の状態を確かめた
Ruby
1# Songsコントローラ 抜粋 2 3# searchは検索機能のためのアクションです。 4def search 5 6 # 今回は関係がない記述(検索機能のための記述) 7 params[:keyword].strip! 8 keywords = params[:keyword].split(/\s+/) 9 10 # songsレコードの呼び出し 11 @songs = Song.all.includes(:chords) 12 13 # 条件文 今回はif条件がtrueになるようにparamsの値が送られています。 14 if params[:sort] == "practice" 15 # 各songレコードごとに孫として紐づくpracticeの数を算出 16 @songs.each do |song| 17 # 計算、同時に仮想属性に代入 18 song[:song_practices] = song.chords.joins(:practices).distinct.count('practices.user_id') 19 end 20 21 # check_point!! ←ここでbinding.pry 22 23 # orderにてソート。 24 @songs = @songs.order("song_practices desc") 25 26 else 27 # 今回は関係がない記述(title順にソートする機能) 28 @songs = @songs.order("title asc") 29 end 30 31 # 以下、今回は関係ない記述(検索機能のための記述) 32 @songs = @songs.where(jam: params[:jam]) if (params[:jam] == "true") 33 @songs = @songs.where(standard: params[:standard]) if (params[:standard] == "true") 34 @songs = @songs.where(beginner: params[:beginner]) if (params[:beginner] == "true") 35 @songs = @songs.where(vocal: params[:vocal]) if (params[:vocal] == "true") 36 @songs = @songs.where(instrumental: params[:instrumental]) if (params[:instrumental] == "true") 37 38 keywords.each do |keyword| unless (params[:keyword].nil?) 39 @songs = @songs.where("title like ?", "%#{keyword}%") 40 end 41 42 respond_to do |format| 43 format.html 44 format.json 45 end 46 47 end
check_point!!時点における変数の状態は下記の通り
@songs
cmd
1 [#<Song:0x00007fb29bb44c90 2 id: 204533774, 3 title: "Little Cabin Home on the Hill", 4 jam: false, 5 standard: true, 6 beginner: false, 7 user_id: 1, 8 created_at: Mon, 25 May 2020 13:37:46 JST +09:00, 9 updated_at: Mon, 22 Jun 2020 20:31:35 JST +09:00, 10 vocal: false, 11 instrumental: false>, 12 #<Song:0x00007fb29bb449e8 13 id: 307520937, 14 title: "Foggy Moutain Breakdown", 15 jam: true, 16 standard: true, 17 beginner: true, 18 user_id: 1, 19 created_at: Mon, 25 May 2020 13:37:46 JST +09:00, 20 updated_at: Fri, 26 Jun 2020 23:22:29 JST +09:00, 21 vocal: false, 22 instrumental: true>, 23 24// 以下続く
※仮想属性:song_practiceは含まれていません。
@songs[0][:song_practices]
@songs[0][:song_practices] = [“理想的な計算結果※”]
エラーは発生しませんでした
(仮想属性の記述をモデルから削除すると、can't write unknown attribute song_practices
=「そんな属性存在しないよエラー」が発生 )
※"計算結果の妥当性を示すデータ"は読みづらさの回避のため、とりあえずは掲載しません。必要あればおっしゃってください。
【ほかに試したこと】
仮説
orderメソッドが呼び出されたときの挙動 が下記のようであるため、「DBにはそんな属性存在しないよエラー」が発生する
→
①モデルのテーブルのうち、指定されたカラムを探す
②見つけたカラムのデータを用いてソート
従って…
→テーブルにカラムが存在していることが前提。
対応策
①ソートのアルゴリズムをインスタンスメソッドとして定義
②定義したメソッドを用いてソートを実施
対応策用のメソッド実装前に…
→インスタンスメソッドの定義・利用ができるかテスト
問題
先程と同様にcheck_point!!にてインスタンスメソッドの試用。
テスト用のインスタンスメソッドの定義
Ruby
1# Songモデル 抜粋 2def test_song 3 puts "test#{title}" 4end
試用結果
①
@songs[0].test_song
test -> Little Cabin Home on the Hill => nil
→問題なく動作
②
@songs.test_song
NoMethodError: undefined method `test_song' for #<Song::ActiveRecord_Relation:0x00007fb2a5b1d230> from /Users/imotoshouta/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.4.2/lib/active_record/relation/delegation.rb:125:in `method_missing'
→エラー。
※メソッド自体が呼び出されていないのか、中の記述が問題なのか確かめるために③を実施
③
puts "test -> #{@songs.title}"
NoMethodError: undefined method `title' for #<Song::ActiveRecord_Relation:0x00007fb2a5b1d230> from /Users/imotoshouta/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.4.2/lib/active_record/relation/delegation.rb:125:in `method_missing'
→当然、エラー。
メソッド自体が呼び出された場合は、②でも同じエラーが出ると推測される。
しかしエラーの内容が異なる。
よって②においてはメソッド自体が呼び出されていない
推察
@songsと@songs[0]の違い
@songs→連想配列/インスタンス変数の配列
@songs[0]→ただのハッシュ/インスタンス変数単体
→連想配列に対してインスタンスメソッドを定義するためには別の記述方法や別の場所に記述することが必要なのではないか?
対策
連想配列に適用するメソッドといえばeach。
eachの定義文を見て、真似をすることでうまく定義できるのではないか?
→なかなか定義文にたどり着けず断念。
教えていただきたい内容
①「やりたいこと」を実現するための実装方法の案
②計算値を元にorderでソートをする方法
③連想配列に対してインスタンスメソッドを定義する方法
①〜③いずれでもアイデアがあれば教えていただけると幸いです。
ただし…
①読みづらさ回避のため、情報を少なく抑えています。他に必要な情報があればおっしゃってください
②songテーブルに計算用のカラムを用意するor計算値を格納するテーブル作成、の他の案があると、より嬉しいです
③eachメソッドなど、連想配列に使用するメソッドの定義文があればそれだけでも知りたいです。
【前提情報】
私のプログラミングレベル感
学習履歴
学習時間550h
3月後半 progate
4月 tech campカリキュラム
5月~ 自作アプリ作成
多少なりとも扱えるもの
html/css, javascript(どちらかというとjQuery), ruby, rails, MySQL, GitHub, aws
【参考】作成中のwebページ
技術レベルの把握にご利用ください
IP: 3.113.194.185
<<テストユーザ>>
メールアドレス: s@s
パスワード: 111111
※運用開始していないので自由に登録、削除していただいて構いません。
※https未対応ですのでご注意ください。
開発環境
OS macOS Catalina (10.15.4)
VScode (1.45)
言語・gem等のバージョン
ruby 2.5.1p57
rails (5.2.4.2)
haml (5.1.2)
haml-rails (2.0.1)
mysql2 (0.5.3)
sass (3.7.4)
sass-rails (5.1.0)
jquery-rails (4.4.0)
以上です。
日曜日から苦戦して、そろそろ打ち手が思いつかなくなったので質問しました。
ご助力のほど、何卒よろしくお願いいたします。
回答2件
あなたの回答
tips
プレビュー