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...
Related
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'.
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
(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
I am trying to add a "following" like functionality to my site but I am having trouble finding the right way to use a polymorphic association. A user needs to be able to follow 3 different classes, these 3 classes do not follow the user back. I have created a user following user in the past but this is proving to be more difficult.
My Migration was
class CreateRelationships < ActiveRecord::Migration
def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :relations_id
t.string :relations_type
t.timestamps
end
end
end
My Relationship model is
class Relationship < ActiveRecord::Base
attr_accessible :relations_id
belongs_to :relations, :polymorphic => true
has_many :followers, :class_name => "User"
end
In my User model
has_many :relationships, :foreign_key => "supporter_id", :dependent => :destroy
and in the other 3 models
has_many :relationships, :as => :relations
Am I missing something with setting up this association?
You basically have it right, except for a few minor errors:
attr_accessible :relations_id is redundant. Remove it from your Relationship model.
Both Relationship and User models call has_many to associate with each other. Relationship should call belongs_to because it contains the foreign key.
In your User model, set :foreign_key => "follower_id".
Here is how I would do it.
Have a Follow middle class with polymorphic association on the followable content side and has_many on the follower user side (user has many follows).
First, create a follows table:
class CreateFollows < ActiveRecord::Migration
def change
create_table :follows do |t|
t.integer :follower_id
t.references :followable, :polymorphic => true
t.timestamps
end
end
end
Replace Relationship model with a Follow model:
class Follow < ActiveRecord::Base
belongs_to :followable, :polymorphic => true
belongs_to :followers, :class_name => "User"
end
Include in User model:
has_many :follows, :foreign_key => :follower_id
Include in your three followable classes:
has_many :follows, :as => :followable
You can now do this:
TheContent.follows # => [Follow,...] # Useful for counting "N followers"
User.follows # => [Follow,...]
Follow.follower # => User
Follow.followable # => TheContent
Is there a way to directly reference (using rails directly, without resorting to a lot of custom SQL) a relation that is nested behind a polymorphic relation? In the below example, is there a way to define a has_many relation in User that references LayerTwo?
I'd like to do (in User)
has_many :layer_twos, :through => layer_ones
but this approach doesn't take into the account of the previously specified has_many relations through the polymorphic relation. Any suggestions? It may not be possibly through the existing rails conventions, but figured I'd defer the question to people smarter then myself.
class CreateOwners < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.timestamps
end
create_table :owners do |t|
t.timestamps
t.references :owned, :polymorphic => :true
t.references :user
end
create_table :layer_ones do |t|
end
create_table :layer_twos do |t|
t.references :layer_one
end
end
end
class Owner < ActiveRecord::Base
belongs_to :user
belongs_to :owned, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :owners
has_many :layer_ones, :through => :owners, :source => :owned, :source_type => 'LayerOne'
end
class LayerOne < ActiveRecord::Base
has_many :owners, :as => :owned
has_many :layer_twos
end
class LayerTwo < ActiveRecord::Base
belongs_to :LayerOne
end
It should be noted now that Rails 3.1 has nested has_many :through associations built in. an ASCIIcast on this
As far as I know, ActiveRecord does not support :through a :through relationship. You can work around this using some tricks and hacks, such as creating a VIEW which remaps the relationship into a more direct one, which can simplify your ActiveRecord model at the expense of database complexity.
Polymorphic associations are particularly ornery.
I'm not sure of its support for nesting through polymorphic asociations but it might be worth checking out the nested_has_many_through plugin, which from the README:
…makes it possible to define
has_many :through relationships that
go through other has_many :through
relationships, possibly through an
arbitrarily deep hierarchy. This
allows associations across any number
of tables to be constructed, without
having to resort to find_by_sql (which
isn't a suitable solution if you need
to do eager loading through :include
as well).
Try this (Rails 3):
class LayerOne < ActiveRecord::Base
class << self
def layer_twos
LayerTwo.where(:layer_one_id => all.map(&:id))
end
end
end