Trying to implement bidirectional self-referential association - ruby-on-rails

(I'm translating the code as I write, so I apologize for any mistakes!)
I'm trying to implement a bidirectional self referential relation involving the Associates table and a join table Property. I tried following this post but something is still wrong.
The way it's supposed to work is, an associate can be the proprietor of zero or more associates, and consequently an associate may have zero or more proprietors (makes more sense in the context of the application).
So I created the Property model:
class CreateProperties < ActiveRecord::Migration
def change
create_table :properties do |t|
t.integer :proprietor_id
t.integer :property_id
t.timestamps null: false
end
end
end
So the table contains only the ids of one proprietor and one property, both associates, per entry.
Following the tutorial linked above, I came to this configuration:
Associate.rb:
...
has_many :properties
has_many :related_properties, :through => :properties
has_many :proprietors, :class_name => "Property", :foreign_key => "proprietor_id"
has_many :related_proprietors :through => :proprietors, :source => :associate
...
Property.rb:
belongs_to :associate
belongs_to :related_properties, :class_name => "Associate"
However when I try to use these relations (<% #associate.related_properties.each do |property| %>), I get this error:
PG::UndefinedColumn: ERROR: column properties.related_properties_id does not exist
LINE 1: ... INNER JOIN "propriedades" ON "associados"."id" = "proprieda...
^
: SELECT "associates".* FROM "associates" INNER JOIN "properties" ON "associates"."id" = "properties"."related_properties_id" WHERE "properties"."associate_id" = $1
Basically, the column names are wrong in the generated SQL: properties.related_properties_id should be properties.proprietor_id, and properties.associate_id should be properties.proprietor_id as well.
What have I done wrong, and how can I fix this code to get the correct relations?

You need to setup two seperate associations since the foreign key on Property depends on what the Associates role is.
class Associate
# defines relations where Associate is the "owning" party
has_many :properties_as_proprietor,
class_name: 'Property',
foreign_key: 'proprietor_id'
has_many :properties,
through: :properties_as_property,
source: :property # what to select on Property
# defines relations where Associate is the "owned" party
has_many :properties_as_property,
class_name: 'Property',
foreign_key: 'property_id'
has_many :proprietors,
through: :properties_as_proprietor,
source: :proprietor # what to select on Property
end
class Property
belongs_to :proprietor, class_name: 'Associate'
belongs_to :property, class_name: 'Associate'
end

Related

How to associate table column with another table

So basically I like to know what association i need to link my routes table with employees table. I have a routes table with an employees column (array type) which holds employee id's. I also have an employee table that has (first_name, last_name, phone_number).
A has_many :employees, foreign_key: :employees, class_name: :Employee does not work and gives an error. Any ideas?
This error is given
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column employees.employees does not exist)
LINE 1: SELECT "employees".* FROM "employees" WHERE "employees"."emp...
Using an array column here is a just bad idea:
Violates first normal form.
Doesn't let you use foreign keys to maintain referential integrity.
Doesn't work with ActiveRecord assocations that expect you to model your data in a sane way.
You will need to write queries by hand.
Instead you most likely want is a self-referential assocation:
class AddManagerToEmployees < ActiveRecord::Migration[6.1]
def change
add_reference :employees, :manager,
null: true,
foreign_key: { to_table: :employees }
end
end
class Employee < ApplicationRecord
belongs_to :manager,
class_name: 'Employee'
has_many :subordinates,
class_name: 'Employee',
foreign_key: :manager_id
end
If the manager-employee relation should be many to many instead of one to many use a join table.

Loose associations in ActiveRecord

I'm new to Ruby on Rails and I'm trying to build a relationship between the classes Club, Sponsor and Match.
The relationhip has to be like:
One Club has zero to many Sponsors
One Sponsor has zero to many Matches
One Match has zero to many Sponsors
My models look like this
class Match < ApplicationRecord
belongs_to :team
has_many :matchplayers
has_many :players, through: :matchplayers
has_many :sponsors
end
class Club < ApplicationRecord
has_many :teams
has_many :sponsors
accepts_nested_attributes_for :teams, :reject_if => :all_blank, :allow_destroy => true
end
class Sponsor < ApplicationRecord
belongs_to :club
end
and my migrations file for the Sponsor model looks like this:
class CreateSponsors < ActiveRecord::Migration[5.1]
def change
create_table :sponsors do |t|
t.text :name
t.text :url
t.text :imgUrl
t.references :club, foreign_key: true
t.timestamps
end
add_reference :matches, :sponsor, index: true
add_foreign_key :matches, :sponsor
end
end
I have no problems retrieving sponsors for each club instance but I'm having trouble retrieving the sponsors associated with each match.
In my matches_controller.rb I have this
def show
#match = Match.find(params[:id])
render :json => #match.to_json(:include => [:players, :sponsors])
end
But when I try to run it the script fails. with the error message "no such column: sponsors.match_id" as the script tries to run the following SQL statement
SELECT "sponsors".* FROM "sponsors" WHERE "sponsors"."match_id" = ?
What I'd really like it to do would be to run the following statement
SELECT "sponsors".*
FROM "sponsors"
LEFT JOIN "matches"
ON "matches"."sponsor_id" = "sponsors"."id"
WHERE "matches"."id" = ?
And placing the resulting array into the output JSON's "sponsors" attribute.
I have been looking into the different Active Record association types and I feel like the type of association I need for this task is looser than the ones described in the documentation.
You need many-to-many relationship. In rails where are 2 ways to do this. You can read about this here. In general you will need to add has_and_belongs_to_many to Sponsor and to Match. And create 'join-model' which will contain match_id + sponsor_id. In this way ActiveRecord will be able to create suitable SQL query due to 'join-table'.

How can I use two records from the same Rails model as foreign keys in a different Rails model?

I have two models: person.rb and relationship.rb
I need my :relationships table to reference two rows from the :people table as foreign keys.
Here are the migrations for both tables:
class CreatePeople < ActiveRecord::Migration[5.1]
def change
create_table :people do |t|
t.string :first_name
t.string :second_name
t.integer :age
t.string :gender
t.timestamps
end
end
end
class CreateRelationships < ActiveRecord::Migration[5.1]
def change
create_table :relationships do |t|
t.references :person_a
t.references :person_b
t.string :status
t.timestamps
end
end
end
The idea is the :person_a and :person_b fields will both be individual records from the :people table referenced as foreign keys, while the :status field will just be a description of their relationship ("Married", "Friends", "Separated", etc.)
I'm trying to find out:
1) What is the additional code I have to write in the CreateRelationships migration above in order to set :person_a and :person_b up as foreign keys from the :people table?
2) What code do I need to write in the model files (person.rb and relationship.rb) for both tables below to define the relationship structure I'm talking about?
class Person < ApplicationRecord
end
class Relationship < ApplicationRecord
end
I've found one other question on here that deals with this issue, but the answers given were conflicting, some incomplete, and others working with older versions of Rails. None of them have worked for me.
I'm using Rails 5.1.4
You have defined you migration correctly just add the following in your model to define the relationship between the model.
class Person < ApplicationRecord::Base
has_many :relationships, dependent: :destroy
end
class Relationship < ApplicationRecord::Base
belongs_to :person_a, :class_name => 'Person'
belongs_to :person_b, :class_name => 'Person'
end
This allows you to access the Person that a Relationship belongs to like this:
#relationship.person_a #user assigned as the first person.
#relationship.person_b #user assigned as the second person.
Hope this works.
EDIT: Apologies for a rushed and wrong answer.
Initially, I thought that simple has_many/belongs_to association is possible and sufficient.
class Person < ApplicationRecord
has_many :relationships #dependent: :destroy
end
class Relationship < ApplicationRecord
belongs_to :person_a, class_name: "Person"
belongs_to :person_b, class_name: "Person"
enum status: [:married, :friends, :separated]
end
As #engineersmnky pointed out, has_many association can't work here because there is no person_id column in relationships table. Since we can declare only one custom foreign key in has_many association, it's not possible to declare it here this way. belongs_to will work, but I don't think that's enough.
One way is to skip declaring has_many and stick to custom method for querying relationships:
class Person < ApplicationRecord
def relationships
Relationship.where("person_a_id = ? OR person_b_id = ?", id, id)
end
end
It will give you an ActiveRecord::Relation to work with, containing exactly the records you need. The drawbacks of this solution are numerous - depending on your needs, you will probably need more code for inserting data, starting with a setter method to assign relationships to people...
What could be a real solution, is to have a composite primary key in Relationship model - composed of :person_a_id and :person_b_id. ActiveRecord doesn't support composite primary keys, but this gem seems to fill the gap. Apparently it allows to declare such key and use it as a foreign key in a has_many association. The catch is that your person_a/person_b pairs would have to be unique across relationships table.
I had to do the same for a chat module, this is an example to how you can do it:
class Conversations < ApplicationRecord
belongs_to :sender, foreign_key: :sender_id, class_name: 'User'
belongs_to :recipient, foreign_key: :recipient_id, class_name: 'User'
end
class User < ApplicationRecord
...
has_many :conversations
has_many :senders, through: :conversations, dependent: :destroy
has_many :recipients, through: :conversations, dependent: :destroy
...
end
More explanations at complex-has-many-through
Hope it helps,
You can do like this
In relationship model write
belongs_to : xyz, class_name: "Person", foreign_key: "person_a"
belongs_to : abc, class_name: "Person", foreign_key: "person_b"
In Person model write
has_many :relationships, dependent: :destroy
hope it will help

Setting up self-referencing HABTM relationship in Rails 4

I'm looking to set up a self-referencing has_and_belongs_to_many relationship using rails 4, with a postgres db.
Basically I've got a Single Inheritance Table set up called Resource, which holds People, Places, and Things in it. (This works beautifully.)
create_table :resources do |t|
t.string :name
t.string :type
t.text :description
end
I'm trying to create a 'has_and_belongs_to_many' relationship so that each Resource can have a series of 'owners', which will be an Relation of People. Each Person, in turn, will have a series of 'possessions'. Since it's a single table, I'll need to join the Resource table to itself.
My migration for the join table looks like this:
create_table :owners_possessions, id: false do |t|
t.integer :owner_id # the id of a person
t.integer :possession_id # the id of a place/thing owned by that person
end
Person.rb:
class Person < Resource
has_and_belongs_to_many :possessions, class_name: :resources,
join_table: :owners_possessions,
foreign_key: :owner_id,
association_foreign_key: :possession_id
end
Resource.rb:
class Resource < ActiveRecord::Base
has_and_belongs_to_many :owners, class_name: :people,
join_table: :owners_possessions,
association_foreign_key: :possession_id,
foreign_key: :owner_id
end
However, when running Resource.find(x).owners in the console, I get the following error message:
ActiveRecord::StatementInvalid: Could not find table 'resources_resources'
Which is perturbing because everything I've searched so far has pointed toward the join_table option as a way to get it looking at the right table.
Furthermore, running `Person.find(x).possessions' yields
NameError: uninitialized constant Person::Possession
What might I be missing here?
I can't reproduce the errors you posted, I suppose you altered your code somewhat.
Anyway, the class_name option in your associations should be the exact name of the other model. So 'Person' in singular form rather than :people:
class Person < Resource
has_and_belongs_to_many :possessions,
class_name: 'Resource',
join_table: :owners_possessions,
foreign_key: :possession_id,
association_foreign_key: :owner_id
end
class Resource < ActiveRecord::Base
has_and_belongs_to_many :owners,
class_name: 'Person',
join_table: :owners_possessions,
association_foreign_key: :possession_id,
foreign_key: :owner_id
end
Notice I also swapped the :foreign_key and :association_foreign_key values so they return the appropriate records.

How do I share two different associations with the same object?

I have a belongs to and has_many relationship.
A child belongs_to a parent
A parent has_many children.
However, I also have another way a Parent can have a child, and that's through a join_table I use to create a grouping of a type of parent.
Here's my awful guess at how to do this :
# child.rb
belongs_to :parent
belongs_to :parent_group, :dependent => :destroy
delegate :parent, :to => :parent_group
# parent.rb
has_many :children
has_many :children, through: :parent_groups
Note, I don't actually use these naming conventions. These were just changed to keep my work anonymous.
Then my migrations look like this :
class CreateParentGroup < ActiveRecord::Migration
def self.up
create_table :parent_groups do |t|
t.integer :parent_id
t.timestamps
end
add_column :child, :parent_group_id, :integer
end
So my goal is to make it that if I type Parent.find(n).children, it will return Child objects that are either through a parent_group AND any children directly related to it.
Vice versa, if I were to select Child.find(n).parent, it would select its parent whether it was through a parent group or not.
And then finally, I would be able to select parent_groups and select collections of parents.
Any ideas?
First of all, if you're talking about a join table then I think your schema setup for this table isn't quite correct. It should be like this:
class CreateParentGroup < ActiveRecord::Migration
def self.up
create_table :parent_groups do |t|
t.integer :parent_id
t.integer :child_id
t.timestamps
end
end
end
So, now your table parent_groups is able to hold many children for many parents.
As for setting up your associations in your models: You can't/shouldn't name two different association with the same name. Ergo you can't do has_many :children and has_many :children, :through => :parent_groups at the same time in one model. Cause if you access the children by Parent.find(n).children Rails doesn't know which association to use.
I'd do something like this:
class Parent < AR
has_many :regular_children, :class_name => 'Child'
has_many :group_children, :class_name => 'Child', :through => :parent_groups
# implement a method that combines them both
def children
regular_children + group_children
end
end
class Child < AR
belongs_to :parent
belongs_to :parent_group, :dependent => :destroy
# forget the delegate, otherwise your invoke of Child.find(n).parent always
# gets delegated to parent_group which is/can be wrong due to data
end
I think that's more the way to go...

Resources