Confused between .count, .size and .length

I was seeing some weird issues with my tests so I wrote a quick debug test to check some results.

I have teachers, students and school_classes.

debug_spec.rb:

feature 'Debug school classes' do
  before(:each) do
    @teacher = create(:teacher)
    @student = create(:student)
    @teacher.school_classes << @student.private_class
    @admin = create(:admin)
#     visit sign_in_path
#     fill_in 'Email', with: @admin.email
#     click_button 'Sign in'
  end

  scenario 'DEBUG' do
    puts 'S length: ' + @student.school_classes.length.to_s
    puts 'T length: ' + @teacher.school_classes.length.to_s
    puts 'S size:   ' + @student.school_classes.size.to_s
    puts 'T size:   ' + @teacher.school_classes.size.to_s
    puts 'S count:  ' + @student.school_classes.count.to_s
    puts 'T count:  ' + @teacher.school_classes.count.to_s
    puts 'S first.name: ' + @student.school_classes.first.name.to_s
    puts 'T first.name: ' + @teacher.school_classes.first.name.to_s
    puts 'T students: ' + @teacher.students.to_s
  end
end

In the before block Iā€™m creating a teacher and a student from factories and assigning the student to the teacher. I get the following output:

Debug school classes                                                                                                                          
S length: 1                                                                                                                                   
T length: 1                                                                                                                                   
S size:   1                                                                                                                                   
T size:   1                                                                                                                                   
S count:  1                                                                                                                                   
T count:  1                                                                                                                                   
S first.name: Rocio Fisher                                                                                                                    
T first.name: Rocio Fisher                                                                                                                    
T students: [#<Student id: 1, first_name: "Rocio", last_name: "Fisher", family_id: 2, school_id: 1>]

This is exactly what I expect to see based on the associations and factories.

However when I uncomment the visit sign_in_path line I see different results:

Debug school classes                                                                                                                          
S length: 1                                                                                                                                   
T length: 1                                                                                                                                   
S size:   1                                                                                                                                   
T size:   1                                                                                                                                   
S count:  0                                                                                                                                   
T count:  0                                                                                                                                   
S first.name: Jerad Gerhold                                                                                                                   
T first.name: Jerad Gerhold                                                                                                                   
T students: []

Note that Iā€™m only including the visit line, Iā€™m not even signing the admin in yet.

What happened to .count and why is my .students relation empty?

Hello, Dan!
In such cases I find it very useful to walk through source code of Rails.

Here is source for the size method fromActiveRecord gem under lib/active_record/associations/collection_association.rb.

def size
  if !find_target? || loaded?
    if association_scope.distinct_value
      target.uniq.size
    else
      target.size
    end
  elsif !loaded? && !association_scope.group_values.empty?
    load_target.size
  elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
    unsaved_records = target.select { |r| r.new_record? }
    unsaved_records.size + count_records
  else
    count_records
  end
end

And the count method

def count(column_name = nil, count_options = {})
  # TODO: Remove count_options argument as soon we remove support to
  # activerecord-deprecated_finders.
  column_name, count_options = nil, column_name if column_name.is_a?(Hash)

  relation = scope
  if association_scope.distinct_value
    # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
    column_name ||= reflection.klass.primary_key
    relation = relation.distinct
  end

  value = relation.count(column_name)

  limit  = options[:limit]
  offset = options[:offset]

  if limit || offset
    [ [value - offset.to_i, 0].max, limit.to_i ].min
  else
    value
  end
end

The difference is that size makes a bunch of cached objects checks and only if it doesnā€™t find cached relation it actually hits the database. On the other hand count hits database every time with this value = relation.count(column_name).

From information Youā€™ve provided I can guess that @teacher.school_classes << @student.private_class loads your @student.school_classes and @teacher.school_classes relations and later calls on size and length methods are actually calls on cached objects. But introduced visit sign_in_path must have some side effects on the database as when You hitting databse with count after visit it is different from size and length. Try to reload association and the results from count and size should be the same.

1 Like

Hi @weavermedia,

I havenā€™t tried this out with your example, but I would revise your setup for this test by being more explicit with your @teacher object, changing it to

@student = create(:student)
@teacher = create(:teacher, school_classes: [@student.private_class])

It would seem you are pushing the student private class into the teachers school classes array which isnā€™t has having the association persisted. I might be wrong about this, but give it a go and see if helps.

Thanks for the info @lompy - that helped to clear up the difference between the methods.

@pedromoreira - that didnā€™t actually change the result but itā€™s helpful to know another approach to creating associations, itā€™ll be useful in future debugging.

In fact the cause of my issue was once again my multi-tenancy method. Iā€™m using default_scope and an around_filter in application_controller, therefore any visit to any page was causing a tenant to be set which changed the query results.

I think for every question I post on here from now on I should preface it with ā€˜I use default_scope multi-tenancy, which is probably the cause of this issue!ā€™ :smile: