arunt
arunt

Reputation: 386

after_create doesn't have access to associated records created during before_created callback?

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

Answers (1)

Jay-Ar Polidario
Jay-Ar Polidario

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

Related Questions