'I am stumped by a seemingly simple active_record association
I have 2 tables. One contains genealogy data (families), the other contains people data. I want to create an association so that I can access the people data based on pointers from the genealogy table. For instance:
<% @families.each do |family| %>
...
<td> <%= family.father.first_name %></td>
Here is the conroller code:
def index
@families = Family.joins(:people)
end
Here are the 2 schemas:
CREATE TABLE "people" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name_prefix" varchar, "first_name" varchar, "middle_name" varchar, "last_name" varchar, "name_suffix" varchar, "date_of_birth" varchar, "date_of_death" varchar, "gender" varchar, "notes" varchar, "sync_outlook" varchar, "sync_phone" varchar, "flags" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "place_of_birth" varchar, "aliases" varchar);
CREATE TABLE "families" ("id" integer NOT NULL PRIMARY KEY, "fam_notes" varchar DEFAULT NULL, "fam_fatherid" integer DEFAULT NULL, "fam_motherid" integer DEFAULT NULL, "fam_weddingdate" varchar DEFAULT NULL, "fam_weddingplace" varchar DEFAULT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
Here is the model code:
class Family < ApplicationRecord
has_many :childlinks, foreign_key: "child_family"
has_many :people, through: :childlinks
#has_one :father, through: :families, source: "fam_fatherid"
has_one :father, foreign_key: "fam_fatherid"
has_one :person, through: :father
end
When I put a break in the web page, @families exists, but the associated father does not.
>> @families[0].father
NameError: uninitialized constant Family::Father
I have tried every combination of has_one, through, foreign_key, source, etc. that I can think of, and nothing has produced a 'family' object with a linked (or associated) 'father' object.
What am I doing wrong?
Solution 1:[1]
I'm going to assume here that we are just modeling biological assocations and not the whole can of worms that is the real world and I'm going to stick to the Rails conventions instead of unfuddling your schema.
If you want to put the mother/father combo as different columns on one table this is how you would do it:
class CreateFamilies < ActiveRecord::Migration[6.1]
def change
create_table :families do |t|
t.belongs_to :father,
null: false,
foreign_key: { to_table: :persons }
t.belongs_to :mother,
null: false,
foreign_key: { to_table: :persons }
t.timestamps
end
end
end
class Family < ApplicationRecord
belongs_to :father,
class_name: 'Person'
belongs_to :mother,
class_name: 'Person'
has_many :children,
class_name: 'Person',
inverse_of: :family
end
class Person < ApplicationRecord
# @todo add a family_id foreign key column to persons
# this is a persons link to their parents
# Must be nullable to avoid a chicken-vs-egg scenario
belongs_to :family,
optional: true,
inverse_of: :children
has_one :father, through: :family
has_one :mother, through: :family
end
Note that you don't actually need a join table between children and the family as the relationshop is one to many and not many to many. Each child can only belong to one set of parents. You also need to use belongs_to when the foreign key is on this models table.
So far its pretty simple. However once you start adding relations to a persons children it gets crazy:
class Person < ApplicationRecord
# this is a persons link to their parents
# Must be nullable to avoid a chicken-vs-egg scenario
belongs_to :family,
optional: true,
inverse_of: :children
has_one :father, through: :family
has_one :mother, through: :family
has_many :maternal_families,
class_name: 'Family',
foreign_key: :mother_id
has_many :maternal_children,
through: :maternal_families,
source: :children
has_many :paternal_families,
class_name: 'Family',
foreign_key: :father_id
has_many :paternal_children,
through: :paternal_families,
source: :children
end
The reason you need so many assocations is that each has_many assocation must point to a single foreign key in ActiveRecord.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
