Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I access an ActiveRecord grandparent association through a parent association that has not yet been saved?

I have a situation where I would like access to an associated grandparent before the parent object is saved. I can think of several hacks, but I'm searching for a clean way to accomplish this. Take the following code as an illustration of my problem:

class Company < ActiveRecord::Base
  has_many :departments
  has_many :custom_fields
  has_many :employees, :through => :departments
end
class Department < ActiveRecord::Base
  belongs_to :company
  has_many :employees
end
class Employee < ActiveRecord::Base
  belongs_to :department
  delegate :company, :to => :department
end

company = Company.find(1)           # => <Company id: 1>
dept = company.departments.build    # => <Department id: nil, company_id: 1>
empl = dept.employees.build         # => <Employee id: nil, department_id: nil>

empl.company   # => Employee#company delegated to department.company, but department is nil

I'm using Rails 3.2.15. I understand what is happening here, and I understand why empl.department_id is nil; though I wish Rails held a direct reference to the prospective association prior to calling save, such that the last line could be delegated through the unsaved department object. Is there a clean work around?

UPDATE: I've tried this in Rails 4 as well, here is a console session:

2.0.0-p247 :001 > company = Company.find(1)
  Company Load (1.5ms)  SELECT "companies".* FROM "companies" WHERE "companies"."id" = ? LIMIT 1  [["id", 1]]
 => #<Company id: 1, name: nil, created_at: "2013-10-24 03:36:11", updated_at: "2013-10-24 03:36:11"> 
2.0.0-p247 :002 > dept = company.departments.build
 => #<Department id: nil, name: nil, company_id: 1, created_at: nil, updated_at: nil> 
2.0.0-p247 :003 > empl = dept.employees.build
 => #<Employee id: nil, name: nil, department_id: nil, created_at: nil, updated_at: nil> 
2.0.0-p247 :004 > empl.company
RuntimeError: Employee#company delegated to department.company, but department is nil: #<Employee id: nil, name: nil, department_id: nil, created_at: nil, updated_at: nil>
2.0.0-p247 :005 > empl.department
 => nil 

UPDATE 2: Here is a test project on github.

like image 345
Ryan Sandridge Avatar asked Oct 23 '13 21:10

Ryan Sandridge


2 Answers

Please look into the :inverse_of option for belongs_to and has_many. This option will handle two-way assignments when building and fetching associated records in different cases.

From Bi-directional associations for ActiveRecord::Associations::ClassMethods in the docs:

Specifying the :inverse_of option on associations lets you tell Active Record about inverse relationships and it will optimise object loading.

like image 95
kristinalim Avatar answered Nov 03 '22 22:11

kristinalim


I don't love this solution, but this seems to solve the problem:

empl = dept.employees.build { |e| e.association(:department).target = dept}

It turns out you can pass a block to build, and ActiveRecord will yield to the block with the newly created record. Who knows what ActiveRecord weirdness such will bring. I'm leaving the question open for now to see if there are better solutions.

like image 28
Ryan Sandridge Avatar answered Nov 03 '22 20:11

Ryan Sandridge