Tuesday, July 24, 2018

Model instance method returning wrong result the first time it is run, and the correct result subsequently

Leave a Comment

I have three models, related with has_many :through associations:

class Account < ApplicationRecord   has_many :account_owners   has_many :employees, through: account_owners    def is_owned_or_belongs_to_team_of_employees(employee)     employee.team.any? { |m| employees.include?(m) }   end end  class AccountOwner < ApplicationRecord   belongs_to :account   belongs_to :employee end  class Employee < ApplicationRecord   has_many :account_owners   has_many :accounts, through: :account_owners    def team     self.class.where(       'id IN (?)',       self. class.find_by_sql(['WITH RECURSIVE search_tree(id, path) AS (                                   SELECT id, ARRAY[id]                                     FROM employees                                     WHERE id = ?                                   UNION ALL                                   SELECT employees.id, path || employees.id                                     FROM search_tree                                     JOIN employees ON employees.manager_id = search_tree.id                                     WHERE NOT employees.id = ANY(path)                                 )                                 SELECT id FROM search_tree ORDER BY path',                              self.id])     ).order(:id)   end end 

I'm manually testing, in the Rails console in my development environment (using some fixtures that I first loaded on the database), the Account#is_owned_or_belongs_to_team_of_employees method.

When I run the method in the console this is what happens:

> a = Account.first  => #<Account id: 534788375, name: "Sales Rep 1 (elena)-owned account", code: "EEE", created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55"> > e = Employee.find_by(first_name: 'Elena')  => #<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil> > e.team  => #<ActiveRecord::Relation [#<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>]> > a.is_owned_or_belongs_to_team_of e  => nil > a.is_owned_or_belongs_to_team_of e  => true 

As you can see, the method returns nil (wrong!) the first time, and returns true (correct!) the following times.

The amazing thing is that I can correct the problem if I define the method like this:

def is_owned_or_belongs_to_team_of employee   puts "employees are #{employees.inspect}"   employee.team.any? { |m| employees.include?(m) } end 

Now the execution is correct, and the method returns consistently the same result (true in my example):

> a = Account.first  => #<Account id: 534788375, name: "Sales Rep 1 (elena)-owned account", code: "EEE", created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55"> > e = Employee.find_by(first_name: 'Elena')  => #<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil> > e.team  => #<ActiveRecord::Relation [#<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>]> > a.is_owned_or_belongs_to_team_of e  => true > a.is_owned_or_belongs_to_team_of e  => true 

If I remove the puts statement, we are back to square one: the method returns nil the first time, and true the following times.

And, amazingly, if I keep the puts statement but remove the inspect (that is, I just do puts "employees are #{employees}" we are also back to square one: nil the first time, and true the following times.

Any idea? What is going on here?

By the way, I'm running Ruby 2.5.1 y Rails 5.2.0.

1 Answers

Answers 1

I'm glad I stumbled upon this Unicorn of a bug!

After debugging this for hours, I found out the following:

  1. any? had new changes in rails 5.2 release that was supposed to delegate it to Enumerable
  2. the surprising thing, that if you put a binding.pry in the implementation of any? and call super it returns true even the first time and then the method returns nil. ~/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/relation.rb @ line 228 ActiveRecord::Relation#any?:

  3. if you add to employee.team .to_a everything works consistently.

  4. if you put any? { |_| true } it returns true.
  5. If you check for the value inside the block for include? it returns true but any? still returns nil!!!
  6. If you avoid resolving the has_may through association (by calling .to_a before the block) or even using a different association inside the any? block everything works as expected.
  7. using any other ruby version fixes the problem.

Summary

The problem was introduced in ruby 2.5.1 rails v5.2.0 when ActiveRecord::Relation started to include Enumerable.It happens with %w(none? any? one? many?) while trying to resolve a has many through association in its block.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment