Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 441
Rodrigo Serrano Avatar asked Jul 15 '18 11:07

Rodrigo Serrano


1 Answers

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.

like image 113
khaled_gomaa Avatar answered Nov 11 '22 13:11

khaled_gomaa