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:
any?had new changes in rails 5.2 release that was supposed to delegate it toEnumerablethe surprising thing, that if you put a binding.pry in the implementation of
any?and callsuperit returns true even the first time and then the method returnsnil.~/.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?:if you add to
employee.team.to_aeverything works consistently.- if you put
any? { |_| true }it returns true. - If you check for the value inside the block for
include?it returns true butany?still returnsnil!!! - If you avoid resolving the
has_may throughassociation (by calling.to_abefore the block) or even using a different association inside theany?block everything works as expected. - 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.
0 comments:
Post a Comment