Has one and has many of itself - ruby-on-rails

I have a user instance that has many invitees but only one inviter.
I am trying to access the inviter instance associated with that user and also his invitees.
i.e:
user.inviter #=> return another user instance.
user.invitees #=> return a collection on user instances
User.rb
class User < ActiveRecord::Base
has_one :inviter, class_name: Invitation, foreign_key: :invitee_id
has_many :invitees, class_name: Invitation, foreign_key: :inviter_id
end
Invitation.rb
class Invitation < ActiveRecord::Base
belongs_to :inviter, class_name: User, foreign_key: :inviter_id
belongs_to :invitee, class_name: User, foreign_key: :invitee_id
end
migration
class CreateInvitations < ActiveRecord::Migration
def change
create_table :invitations do |t|
t.references :inviter, references: :user, index: true
t.references :invitee, references: :user, index: true
t.foreign_key :users, column: :inviter_id
t.foreign_key :users, column: :invitee_id
t.timestamps
end
end
end
This works half of the way because if I call user.inviter on a user that has an inviter it will return the invitation instance but not the user like I would like. Same for user.invitees returns a collection on invitation instances.
Do y'all have an idea of how to make it work ?

Your should use through option like this:
class User < ActiveRecord::Base
has_one :invitation, inverse_of: :inviter
has_one :inviter, through: :invitation
has_many :invitations, inverse_of: :invitee
has_many :invitees, through: :invitations
end
class Invitation < ActiveRecord::Base
belongs_to :inviter, class_name: User, inverse_of: :invitation
belongs_to :invitee, class_name: User, inverse_of: :invitations
end

user.invitees will give collection of invitation records.
Using IN query into User model with all invitee_id which reference to user model will give you collection of users.
user_ids = user.invitees.map(&:invitee_id)
User.where(id: user_ids)

Related

Create a relationship that allows a user to follow multiple types of records

I am currently working on a system that allows a user to follow different records (funds or wallets) but I am blocking on relationships.
I have three models: User, Fund and Wallet.
A user must be able to follow to multiple funds or wallets, and a fund or wallet must be able to have multiple followers.
The goal is to be able to do user.follows which would give me the list of records he follows (funds and wallets), and to do fund.followers or wallet.followers and get the list of users who follow these records.
I have created this table:
class CreateFollows < ActiveRecord::Migration[6.0]
def change
create_table :follows do |t|
t.integer :user_id
t.integer :followable_id
t.string :followable_type
t.timestamps
end
end
end
then I did:
class User < ApplicationRecord
has_many :follows, dependent: :destroy
end
class Follow < ApplicationRecord
belongs_to :follower, foreign_key: :user_id, class_name: 'User', inverse_of: :follows
belongs_to :followable, polymorphic: true
end
and this concern that I added to Fund and Wallet models:
module Followable
extend ActiveSupport::Concern
included do
has_many :followers, dependent: :destroy
end
end
I know it can't work as it is now, but I don't really understand what to put in the User model and in my concern. Can someone show me the path please?
I finally did the following:
class Follow < ApplicationRecord
belongs_to :follower, foreign_key: :user_id, class_name: 'User', inverse_of: :follows
belongs_to :followable, polymorphic: true
end
class User < ApplicationRecord
has_many :follows, dependent: :destroy
has_many :funds_followings, through: :follows, source: :followable, source_type: 'Fund'
has_many :wallets_followings, through: :follows, source: :followable, source_type: 'Wallet'
def followings
funds_followings + wallets_followings
end
end
module Followable
extend ActiveSupport::Concern
included do
has_many :follows, foreign_key: :followable_id, dependent: :destroy, inverse_of: :followable
has_many :followers, through: :follows, as: :followable
end
end
So I'm able to do everything I wanted.

Dependent destroy to tables with more than one references

I have a table Atribuition with 2 references from User table.
class Attribuition < ApplicationRecord
belongs_to :user, class_name: 'User', foreign_key: 'user_id'
belongs_to :not_rated, class_name: 'User', foreign_key: 'not_rated_id'
end
The User model:
class User < ApplicationRecord
has_many :attribuitions, dependent: :destroy
end
When i destroy an user marked in not_rated i want it to be destroyed, but it just happens when i destroied an user marked as user_id, then the attribute row is deleted. I wanna make dependent:: destroy to work for many references of same model. That is possible?
My migration is:
class CreateAttribuitions < ActiveRecord::Migration[5.2]
def change
create_table :attribuitions do |t|
t.references :user
t.references :not_rated, index: { unique: true }
t.timestamps
end
end
end
Edit:
First you do following change as rails use convention over configuration
class Attribuition < ApplicationRecord
- belongs_to :user, class_name: 'User', foreign_key: 'user_id'
+ belongs_to :user
end
Changes needed
When you mention has_many :attribuitions, dependent: :destroy by side of User model class_name will be Attribuition and foreign_key will be user_id stored in attributions table.
So if you need to destroy attribuitions related by foreign_key not_rated_id & user_id then you need following changes.
class User < ApplicationRecord
has_many :attribuitions, dependent: :destroy # default foreign_key is user_id
has_many :not_rated_attribuitions, foreign_key: 'not_rated_id', dependent: :destroy
end

has_many through - Notes sender and receiver

I'm working on a rails app which has three models.
class User < ApplicationRecord; end
class Share < ApplicationRecord; end
class Note < ApplicationRecord; end
create_table :users do |t|
t.timestamps
end
create_table :notes do |t|
t.integer 'user_id'
t.text 'title'
t.text 'short_description'
t.string 'name'
t.timestamps
end
create_table :shares do |t|
t.integer 'user_id'
t.integer 'receiver_id'
t.integer 'note_id'
t.timestamps
end
How can I create associations between them so, I can get
Notes which are shared by User A.
Notes which are received by User A.
Notes which are created by User A.
#Mehmet Adil İstikbal gives part of the answer so I'll try to complete it.
This is another way to do it using only associations :
class User < ApplicationRecord
has_many :created_notes, class_name: 'Note', foreign_key: :user_id
has_many :received_shares, foreign_key: :receiver_id, class_name: 'Share'
has_many :received_notes, through: :received_shares, source: :note
has_many :shares
has_many :shared_notes, through: :shares, source: :note
end
class Share < ApplicationRecord
# Optional
belongs_to :creator, class_name: 'User', foreign_key: :user_id
belongs_to :receiver, class_name: 'User', foreign_key: :receiver_id
# Mandatory
belongs_to :note
end
class Note < ApplicationRecord ; end
user_a = User.first
user_a.shared_notes
user_a.received_notes
user_a.created_notes
If you choose #Mehmet Adil İstikbal answer, please make sure to transform
user.shares.each {|share| share.note} to user.shares.map(&:note) (Use map and not each)
My answer uses has_many through association which allows you to go "through" join table.
In user model you can do like this:
has_many :shares, foreign_key: 'user_id', class_name: 'Share', dependent: :destroy
has_many :receives, foreign_key: 'receiver_id', class_name: 'Share', dependent: :destroy
and you can call like this:
User.first.shares.each {|share| share.note}
This will get all shares with first users id and all of their notes.
For receiver :
User.first.receives.each {|share| share.note}
In your share model you can also specify the opposite connection like this:
belongs_to :sender, foreign_key: 'user_id', class_name: 'User'
belongs_to :receiver, foreign_key: 'receiver_id', class_name: 'User'
With this you can call:
Share.first.receiver this will get you to user that receives this post
And for the notes which are created by user you can call:
User.first.notes
You may want to delete those dependents in order to your project.
Hope it helps

Is there a way to have two join tables associate the same two classes in a rails application?

Long time listener, first time caller. I'm trying to create two associations between the same database tables, Chatrooms and Users. What I have so far is a has_many through relationship where a Chatroom has many Users through Messages. This part works fine. What I want to do is to create a second join table that connects Chatrooms to Users, through a join table called Chatroom_players. So what I'd like is for Chatroom.first.users to get me users through the messages join table and Chatroom.first.players to get me everyone from the chatroom_players join table. The reason I want this is so that I can maintain user presence even if a user hasn't written any messages in the chat, also so that a user can leave the room but maintain his or her messages in the chat.
Here is what I have so far that does not work:
chatroom.rb:
class Chatroom < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :users, through: :messages
has_many :chatroom_players
has_many :users, through: :chatroom_players
end
message.rb:
class Message < ApplicationRecord
belongs_to :chatroom
belongs_to :user
validates :content, presence: true, length: {minimum: 2, maximum: 200}
end
chatroom_player.rb
class ChatroomPlayer < ApplicationRecord
belongs_to :chatroom
belongs_to :user
end
user.rb
class User < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :chatrooms, through: :messages
has_many :chatroom_players
has_many :chatrooms, through: :chatroom_players
end
chatroom_players migration:
class AddChatroomPlayers < ActiveRecord::Migration[5.0]
def change
create_table :chatroom_players do |t|
t.references :user, index: true, foreign_key: true, null: false
t.references :chatroom, index: true, foreign_key: true, null: false
t.boolean :creator, default: false
t.timestamps null: false
end
end
end
You need to use different names for the associations:
class Chatroom < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :users, through: :messages
has_many :chatroom_players
# this is a separate association to users through the
# chatroom_players table.
has_many :participants,
through: :chatroom_players,
source: :user, # what association on chatroom_players to use
class_name: 'User' # since it cannot be deduced automatically
end

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

Resources