cheetoriko
cheetoriko

Reputation: 53

Rails: Accessing SQL alias attributes declared with ".select"

Let's say in my controller, I use a "where" clause on Teachers so I can put the alias, class_size, on the number of students

@teachers = Teachers.select("teachers.*, count(students.id) as class_size")

When I create a view page for indexing all the teachers and use @teacher.class_size, it returns the count of students like a charm...

<% for teacher in @teachers %>
  <% = teacher.name %>
  <% = teacher.class_size %>
  ...
<% end %>

So, I write up my index spec like so...

RSpec.describe "teachers/index" do
  let(:teacher) { create(:teacher) }
  let(:student) { create(:student) }

  before do
    assign(:teachers, [teacher])
    render
  end
...

But, when I run the spec, I get an error that says

     # --- Caused by: ---
     # NoMethodError:
     #   undefined method `class_size' for an instance of Teacher

How do I go about writing my test spec for the index page and not run into errors with the class_size alias? I want to maintain the alias I set up with the select method call

Upvotes: 0

Views: 75

Answers (2)

Andrea Salicetti
Andrea Salicetti

Reputation: 2483

The "magic" that added the class_size reader attribute to your @teaches instance has been done by AREL under the hood when you have written this:

@teachers = Teachers.select("teachers.*, count(students.id) as class_size")

In the spec you have mocked that variable, thus you won't have that reader accessor method, unless you mock it or, even better, you assign it to the same query result object:

before do
  assign(:teachers, Teachers.select("teachers.*, count(students.id) as class_size"))
  render
end

Upvotes: 0

max
max

Reputation: 102016

In order to select the additional column you need to actually fetch the records from the database instead of just passing an array of records instanciated in your factory to your view spec.

class Teacher < ApplicationRecord
  # ...
  scope :with_student_count, ->{ 
    left_joins(:students)
      .select("teachers.*, count(students.id) as class_size") 
  }
end
RSpec.describe "teachers/index" do
  let(:teacher) { create(:teacher) }
  let(:student) { create(:student) } # this isn't even associated with the teacher? 

  before do
    assign(:teachers, Teacher.with_student_count)
    render
  end
...

You could also use a counter-cache to avoid the need to fetch the count or mock the method for the purpose of the test.

Or you could just forgo the view spec altogether and test your view and the controller together though a feature/system spec.

Upvotes: 1

Related Questions