Reputation: 386
I am running into a weird issue, and reading the callbacks RoR guide didn't provide me an answer.
class User < ActiveRecord::Base
...
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
has_many :user_teams, dependent: :destroy
has_many :teams, through: :user_teams
before_create :check_company!
after_create :check_team
def check_company!
return if self.companies.present?
domain = self.email_domain
company = Company.find_using_domain(domain)
if company.present?
assign_company(company)
else
create_and_assign_company(domain)
end
end
def check_team
self.companies.each do |company|
#do stuff
end
end
...
end
The after_create :check_team callback is facing issues because the line
self.companies.each do |company|
Here, self.companies is returning an empty array [] even though the Company and User were created and the User was associated with it. I know I can solve it by making it a before_create callback instead. But I am puzzled!
Why does the after_create callback not have access to self's associations after the commit?
Solution: Please read my comments in the accepted answer to see the cause of the problem and the solution.
Upvotes: 2
Views: 1173
Reputation: 6603
inside before_create
callbacks, the id
of the record is not yet available, because it is before... create... So it is not yet persisting in the database to have an id
. This means that the associated company_user
record doesn't have a user_id
value yet, precisely because the user.id
is still nil
at that point. However, Rails makes this easy for you to not worry about this "chicken-and-egg" problem, provided that you do it correctly:
I recreated your setup (Company
, User
, and CompanyUser
models), and the following is what should work on your case (tested working):
class User < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
before_create :check_company!
after_create :check_team
def check_company!
# use `exists?` instead of `present?` because `exists?` is a lot faster and efficient as it generates with a `LIMIT 1` SQL.
return if companies.exists?
## when assigning an already persisted Company record:
example_company = Company.first
# 1) WORKS
companies << example_company
# 2) WORKS
company_users.build(company: example_company)
## when assigning and creating a new Company record:
# 1) WORKS (this company record will be automatically saved/created after this user record is saved in the DB)
companies.build(name: 'ahaasdfwer') # or... self.companies.new(name: 'ahaasdfwer')
# 2) DOES NOT WORK, because you'll receive an error `ActiveRecord::RecordNotSaved: You cannot call create unless the parent is saved`
companies.create(name: 'ahaasdfwer')
end
def check_team
puts companies.count
# => 1 if "worked"
puts companies.first.persisted?
# => true if "worked"
end
end
Upvotes: 3