Rails Console How do I get records in a many to many relationship

45 Views Asked by At

rails 6.1 ruby 3.1.1

I have a many to many relationships inside a sub-directory (namespaced) ...living_muay_thai. I have three tables: students, levels, and student_levels. I have records created in each table. Now I want to display the levels for a specific student. I think the namespacing is confusing me.

code

Models:

# Table name: living_muay_thai_students
#
#  id         :bigint           not null, primary key
#  fname      :string
#  lname      :string
#
class LivingMuayThai::Student < ApplicationRecord
    has_many :living_muay_thai_student_levels
    has_many :living_muay_thai_levels, through: :living_muay_thai_student_levels, dependent: :destroy
end



# Table name: living_muay_thai_levels
#
#  id         :bigint           not null, primary key
#  color      :string
#  level_name :string
#  sort_order :integer
#
class LivingMuayThai::Level < ApplicationRecord
    has_many :living_muay_thai_student_levels
    has_many :living_muay_thai_students, through: :living_muay_thai_student_levels, dependent: :destroy
end

# joins table

# Table name: living_muay_thai_student_levels
#
#  id         :bigint           not null, primary key
#  level_id   :integer
#  student_id :integer
#
class LivingMuayThai::StudentLevel < ApplicationRecord
  belongs_to :living_muay_thai_student, class_name: 'LivingMuayThai::Student', foreign_key: :student_id
  belongs_to :living_muay_thai_level, class_name: 'LivingMuayThai::Level', foreign_key: :level_id
end

As noted I have a record in each of these tables:

# living_muay_thai_students
id: 1
fname 'John'
lname 'Smith'

# living_muay_thai_levels
id: 1
color: 'White'
level_name: 'Level 1'
sort_order: 1

# living_muay_thai_student_levels
id: 1
student_id: 1
level_id: 1

Now, in a console or a view, if I have a Student, how do I access the corresponding Level record?

#console:

3.1.1 :001 > student1 = LivingMuayThai::Student.first

3.1.1 :002 > level1 = LivingMuayThai::Level.first

What I want to do is (in simple english): student1.level_name which would be Level 1

# first try/error:
3.1.1 :003 > student_level = student1.living_muay_thai_levels.level_name
#
# error: `compute_type': uninitialized constant LivingMuayThai::Student::LivingMuayThaiStudentLevel (NameError)  old_raise.call(*args)

2nd try/error:
3.1.1 :004 > student_level = student1.living_muay_thai_student_levels.level_name
#
# error: `compute_type': uninitialized constant LivingMuayThai::Student::LivingMuayThaiStudentLevel (NameError)  old_raise.call(*args)

Any ideas on what I'm doing wrong and how I should be trying to traverse the data?

Thanks!

1

There are 1 best solutions below

1
max On

I would start by just declaring the modules properly and removing the horrifibly long prefixes on all your associations.

module LivingMuayThai
  class Student < ApplicationRecord
    has_many :student_levels
    has_many :levels, through: :student_levels, 
                      dependent: :destroy
  end
end
module LivingMuayThai
  class Level < ApplicationRecord
    has_many :student_levels
    has_many :students, through: :student_levels, 
                        dependent: :destroy
  end
end
module LivingMuayThai
  class StudentLevel < ApplicationRecord
    belongs_to :student
    belongs_to :level
  end
end
class CreateLivingMuayThaiStudentLevels < ActiveRecord::Migration[7.0]
  def change
    create_table :living_muay_thai_student_levels do |t|
      # We need to explicitly tell Rails which table here
      t.belongs_to :student, null: false, foreign_key: { to_table: :living_muay_thai_students }
      t.belongs_to :level, null: false, foreign_key:  { to_table: :living_muay_thai_levels }

      t.timestamps
    end
  end
end

The differece here may just seem like a stylistic choice but its definately not. By using the module keyword you're re-opening the module and changing the module nesting so that Ruby will lookup other constants in the LivingMuayThai module.

Then you're making a very common beginner misstake and trying to call an instance method on a whole collection of records.

student_level = student1.living_muay_thai_levels.level_name

It's very unclear what you're actually trying to accomplish here.

If you want to show the levels of a student you need to iterate over the collection:

student1.levels.each {|l| puts l.level_name }

Or if you wanted to show the highest level you would need to get a single record:

student1.stundent_levels.last.level_name