Rails: Many to many relation to self - ruby-on-rails

I am having problems creating this association: Consider a model "Entry". I want entries to have many entries as parents and I want entries to have many entries as children. I want to realize this relation via a model I called "Association", so here is what I tried:
Migration:
class CreateAssociations < ActiveRecord::Migration[5.0]
def change
create_table :associations do |t|
t.integer :parent_id
t.integer :child_id
end
end
end
Association model:
class Association < ApplicationRecord
belongs_to :parent, class_name: 'Entry'
belongs_to :child, class_name: 'Entry'
end
so far it works. But now how do I use this to create two many-to-many relations on a model to itself?
class Entry < ApplicationRecord
# has many parent entries of type entry via table associations and child_id
# has many child entries of type entry via table associations and parent_id
end

This should work:
class Entry < ApplicationRecord
has_and_belongs_to_many :parents, class_name: 'Entry', join_table: :associations, foreign_key: :child_id, association_foreign_key: :parent_id
has_and_belongs_to_many :children, class_name: 'Entry', join_table: :associations, foreign_key: :parent_id, association_foreign_key: :child_id
end

Related

Ruby on Rails 6 ActiveRecord associations, one model with multiple references to another model

I am building a Ruby on Rails 6 application where I have a model Archive, and a another model ArchiveAgencies. In the Archive model I must have a sender agency and a receiver agency, which should represent ArchiveAgencies.
After going through the Rails docs, and some StackOverflow QA:
How do I add migration with multiple references to the same model in one table? Ruby/Rails
Defining two references to the same column in another table
Ruby on Rails: two references with different name to the same model
I came up with this approach:
Models
class Archive < ActiveRecord::Base
belongs_to :sender_agency, class_name: ArchiveAgencies, foreign_key: "sender_agency_id"
belongs_to :receiver_agency, class_name: ArchiveAgencies, foreign_key: "receiver_agency_id"
end
class ArchiveAgency < ActiveRecord::Base
has_many :archives, inverse_of: 'sender_agency'
has_many :archives, inverse_of: 'receiver_agency'
end
Migration
class CreateArchiveAgencies < ActiveRecord::Migration[6.0]
def change
create_table :archives do |t|
t.string :name
t.timestamps
end
end
end
class CreateArchives < ActiveRecord::Migration[6.0]
def change
create_table :archives do |t|
t.references :sender_agency, foreign_key: { to_table: :archive_agencies }
t.references :receiver_agency, foreign_key: { to_table: :archive_agencies }
t.timestamps
end
end
end
Is this the best approach for this case?
Would, having two inverse_of statements in the model ArchiveAgency work?
If you declare multiple associations with the same name the last one will overwrite the others.
Instead you need to use unique names for each association and configure them properly:
class ArchiveAgency < ActiveRecord::Base
has_many :archives_as_sender,
inverse_of: :sender_agency,
foreign_key: :sender_agency_id,
class_name: 'Archive'
has_many :archives_as_receiver,
inverse_of: :receiver_agency,
foreign_key: :receiver_agency_id,
class_name: 'Archive'
end
class Archive < ActiveRecord::Base
belongs_to :sender_agency, # foreign_key can be derived from the name
class_name: 'ArchiveAgency', # needs to be a string - not the class itself
inverse_of: :archives_as_sender
belongs_to :receiver_agency,
class_name: 'ArchiveAgency'
inverse_of: :archives_as_receiver
end

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

Rails has_and_belongs_to_many relations with two types of Users and one type of Table

I have a problem related with this association. A pasted code is better than any title:
table.rb
class Table < ActiveRecord::Base
has_and_belongs_to_many :clients, class_name: 'User'
has_and_belongs_to_many :managers, class_name: 'User'
end
user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :tables
end
migration - join table
class UsersToTable < ActiveRecord::Migration
def change
create_table :tables_users, id: false do |t|
t.references :user, as: :client
t.references :user, as: :manager
t.references :table
end
end
end
Problem
tab = Table.new
tab.save
tab.clients.create
tab.clients.create
tab.clients.create
tab.managers.create
tab.managers.size # == 4
tab.clients.size # == 4
When I creating associated Objects(Users) they all are linked to both clients and managers.
I want to be able to create them separately - When creating a client - only number of clients rise, when creating manager, only number of managers rise.
In other words I want this:
tab.managers.size # == 1
tab.clients.size # == 3
Could you please help?
has_and_belongs_to_many :stuff, class_name: 'StuffClass' is just DSL for:
has_many "<inferred_join_table_name>"
has_many :stuff, through: "<inferred_join_table_name>"
It seems that since clients and managers are names for Users, the inferred join table get's to be "TablesUsers", and that is not right.
Try specifyng the join table for both and using different join tables for each relationship:
class Table
has_many :tables_clients
has_many :clients, through: :tables_clients
has_many :tables_managers
has_many :clients, through: :tables_managers
end
class TablesClients
belongs_to :client, class_name: 'User'
belongs_to :table
end
create_table :tables_clients, id: false do |t|
t.references :client, index: true
t.references :table, index: true
end
# and the same for tables_managers
Then the user belongs to Tables in too different ways:
class User
has_many :client_tables_users, class_name: 'TablesUsers', foreign_key: :client_id
has_many :tables_as_client, through: :client_tables_users, source: :table
has_many :managed_tables_users, class_name: 'TablesUsers', foreign_key: :manager_id
has_many :managed_tables, through: :managed_tables_users, source: :table
end

Not saving the HABTM Association when creating a record

I have those two Models, in a HABTM Relationship:
The Project is using Rails 4, so no attr_accessible tags
wine.rb
class Wine < ActiveRecord::Base
has_and_belongs_to_many :pairings, class_name: 'Food', join_table: 'foods_wines', association_foreign_key: 'food_id'
has_many :images, as: :attachable, class_name: 'Asset', dependent: :delete_all
end
food.rb
class Food < ActiveRecord::Base
has_and_belongs_to_many :wines, class_name: "Wine", join_table: "foods_wines", foreign_key: "food_id"
end
I created the Join Table with this migration:
create_table(:foods_wines, :id => false) do |t|
t.integer :food_id
t.integer :wine_id
end
add_index :foods_wines, [:food_id, :wine_id]
When I try to create the new Relation in the Rails Console, it does not seem to be saving the HABTM Relationship.
#wine.pairings.create(:name => "Seafood")
it does not seem to be saving the HABTM Relation -> When I restart the console, the relation is gone - I also checked inside the DB, where I get an empty table for the foods_wines table.
Am I missing something crucial here?
I think, you have to replace :
has_and_belongs_to_many :pairings, class_name: 'Food', join_table: 'foods_wines', association_foreign_key: 'food_id'
with :
has_and_belongs_to_many :pairings, class_name: 'Food', join_table: 'foods_wines', foreign_key: 'wine_id'
in wine.rb, because you have to specify the foreign key of this class (Wine).

How to update my Users/Products association

I'm trying to create an app to share or give products. So I have two models : User and Product.
A user can have many products, as an owner or as a borrower. A product has only one owner and only one borrower.
First I did something like that :
> rails generate model User name:string
class User
has_many :owned_products, class_name: "Product", foreign_key: "owner_id"
has_many :borrowed_products, class_name: "Product", foreign_key: "borrower_id"
end
> rails generate model Product name:string owner_id:integer borrower_id:integer
class Product
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :borrower, class_name: "User", foreign_key: "borrower_id"
end
I added in my Product controller a security filter that enable the update method only for product's owner. But when I want to change the product's borrower, I have some kind of a problem, because the borrower is never the owner, so the product can not be updated.
So now I'm wondering if I should not take the foreign_key out of my products model, in order to dissociate the update action of a user on his own product, and the update action of a user to borrow a product that don't belongs to him...
> rails generate model User name:string
class User
has_many :properties
has_many :loans
has_many :owned_products, through: :properties
has_many :borrowed_products, through: :loans
end
> rails generate model Property owner_id:integer owned_product_id:integer
class Property
belongs_to :owner, class_name: "User", foreign_key: "user_id"
belongs_to :owned_product, class_name: "Product", foreign_key: "product_id"
end
> rails generate model Loan borrower_id:integer borrowed_product_id:integer
class Loan
belongs_to :borrower, class_name: "User", foreign_key: "user_id"
belongs_to :borrowed_product, class_name: "Product", foreign_key: "product_id"
end
> rails generate model Product name:string
class Product
has_one :property
has_one :loan
has_one :owner, through: :property
has_one :borrower, through: :loan
end
What do you think about it ?
Since borrowed products and owned products are the same type of object with the same list of attributes, but differ only in behavior, I would use single table inheritance for Product.
Migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
# ...
t.timestamps
end
end
end
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.integer :ownerable_id
t.string :ownerable_type
# ...
t.timestamps
end
end
end
Models:
class User < ActiveRecord::Base
has_many :products, :as => :ownerable
end
class Product < ActiveRecord::Base
belongs_to :user, :polymorphic => true
end
class OwnedProduct < Product
end
class BorrowedProduct < Product
end
The benefit of this approach is that you can just define the appropriate behavior in each model without asking it if it's "owned" or "borrowed." Just tell your models what to do and leave the decisions up to each object to do the right thing.

Resources