Rails Association multiple foreign key same table - ruby-on-rails

I am learning Rails and its Active Records, and I want to set notificatios and send them to a User and register who send it, I have something like this:
Notification Model (I don't know if it is correct to set the ':sender' and ':reciever' like I did):
class Notification < ApplicationRecord
belongs_to :sender, class_name: 'User', foreign_key: 'sender_id'
belongs_to :reciever, class_name: 'User', foreign_key: 'reciever_id'
end
User Model:
class User < ApplicationRecord
has_many :notifications
end
I can do
user.notifcations.new(:message => "New notification", :sender => User.first)
but when I save (user.save) it shows:
ActiveModel::MissingAttributeError: can't write unknown attribute 'sender_id'

Achieved by adding indexes in the model migration and keeping my models as following:
Migration:
class CreateNotifications < ActiveRecord::Migration[5.1]
def change
create_table :notifications do |t|
# Adding index
t.integer :sender_id
t.text :message
t.boolean :seen
t.boolean :deleted
t.timestamps
end
add_index :notifications, :sender_id
end
end
User Model:
class User < ApplicationRecord
has_many :notifications, foreign_key: 'user_id'
has_many :notifications_sended, class_name: 'Notification', foreign_key: 'sender_id'
end
Notification Model:
class Notification < ApplicationRecord
belongs_to :reciever, class_name: 'User', foreign_key: 'user_id'
belongs_to :sender, class_name: 'User', foreign_key: 'sender_id'
end
Also did the AddUserToNotification Migration:
rails g migration AddUserToNotification user:references
So I can do:
User.first.notifications.new(:message => "Hi", :sender => User.second)
And:
User.first.notifications # Shows the notification
User.second.notifications_sended # Shows the same notification

Related

Ruby on Rails: has_and_belongs_to_many connection to inherited subclass

I am trying to connect two classes (conversation and user) by a many-to-many relationship in Ruby on Rails. I set them both up and added a connection table called conversations_custom_users to connect them and it was working. Once we needed our User model to inherit from another User model, setting conversations in a user object was failing and looking for a connection table with the parent class.
My classes and the conversation migration looks like below (I haven't modified the User migration for the many-to-many relationship):
class CustomUser < Spree::User
serialize :resources, Array
has_and_belongs_to_many :conversations, :foreign_key => :conversation_ids, class_name: 'Conversation'
end
class Conversation < ApplicationRecord
has_and_belongs_to_many :receiver, :foreign_key => :receiver_id, class_name: 'CustomUser'
end
class CreateConversations < ActiveRecord::Migration[6.1]
def change
create_table :conversations do |t|
t.timestamps
end
create_table :conversations_custom_users, id: false do |t|
t.belongs_to :conversation, foreign_key: 'conversation_id', index: true
t.belongs_to :custom_user, foreign_key: 'receiver_id', index: true
end
end
end
I think I shouldn't need to add another table called conversations_spree_users, but I also tried adding one. It didn't solve the problem since then Rails was looking for a spree_user_id field. I also tried adding the spree_user_id field to the conversations_spree_users table, but it wouldn't migrate because it was a duplicate column name!
I think I'm missing something about many-to-many relations or inheritance or both in Ruby. If someone can help with this issue I'd really appreciate it.
you could use polymorphic associations to build many-to-many association, the benefit of this approach is that you can use only one join-table for all user's hierarchy inheritance.
class CreateConversationals < ActiveRecord::Migration[6.1]
def change
create_table :conversationals do |t|
# ...
t.references :contributor, polymorphic: true, null: false
t.integer :conversation_id
t.timestamps
end
end
end
class Conversational < ApplicationRecord
belongs_to :contributor, polymorphic: true
belongs_to :conversation
end
class Conversation < ApplicationRecord
has_many :conversationals, :foreign_key => :conversation_id
has_many :custom_users, :through => :conversationals, :source => :contributor, :source_type => 'CustomUser'
has_many :other_users, :through => :conversationals, :source => :contributor, :source_type => 'OtherUser'
end
class CustomUser < Spree::User
has_many :conversationals, as: :contributor
has_many :conversations, :through => :conversationals, :as => :contributor
end
# i assume you use STI
class OtherUser < CustomUser
end
then
user1 = CustomUser.create(...)
user2 = OtherUser.create(...)
conversation = Conversation.create(...)
conversational1 = Conversational.create(..., conversation_id: conversation.id, contributor: user1)
conversation1 = Conversational.create(..., conversation_id: conversation.id, contributor: user2)
# many-to-many
user1.conversations
user2.conversations
conversation.custom_users
conversation.other_users

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

How to add a reference to primary key of same table twice in a table in postgres in rails5

I am using Rails 5.0.1 for web development and deploying my app on Heroku. I am using postgreSQL as my database.
I have a table called Users and a table called Friends.
In friends table I want to store the user Id twice, for example to indicate that user id 1 is friend of user id 2.
How can I do this?
So far I have tried following things with no success:
Attempt 1:
migration file:
create_table :friends do |t|
t.references :user, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
Model:
class Friend < ApplicationRecord
belongs_to :friend1, :foreign_key => :friend1_id, :class_name => 'User'
belongs_to :friend2, :foreign_key => :friend2_id, :class_name => 'User'
end
Attempt 2:
migration file:
create_table :friends do |t|
t.references :friend1_id, foreign_key: true
t.references :friend2_id, foreign_key: true
t.timestamps
end
Model:
class Friend < ApplicationRecord
belongs_to :friend1, :foreign_key => :user_id, :class_name => 'User'
belongs_to :friend2, :foreign_key => :user_id, :class_name => 'User'
end
Is there something wrong with the way I am modeling it? or, can this be done in a better/easier way?
My requirement is to store information about a user and his friends. A user can have multiple friends.
PS: Before deploying on Heroku/PostgreSQL, I was running the application on my local system using sqlite3, the scripts seemed to be working fine there, but they don't work on Heroku/PostgreSQL now.
I think you have mostly the right pieces, but need to recombine them eg this should work:
class Friend < ApplicationRecord
belongs_to :friend1, :foreign_key => :friend1_id, :class_name => 'User'
belongs_to :friend2, :foreign_key => :friend2_id, :class_name => 'User'
end
if your columns are friend_1/friend_2
create_table :friends do |t|
t.integer :friend1_id
t.integer :friend2_id
t.timestamps
end
You may need to manually add foreign-keys though, as I don't think rails supports the differently-named keys.
Note: these other two things you tried definitely won't work because Rails finds user_id to be ambiguous... or repeated.
I did a search in Github, and found this. Hope helpful:
app/models/user.rb
has_and_belongs_to_many :friends, join_table: :friends_users
has_many :outgoing_friend_requests, class_name: FriendRequest
has_many :incoming_friend_requests, class_name: FriendRequest,
foreign_key: :friend_id
app/models/friend.rb
class Friend < ActiveRecord::Base
self.table_name = :users
has_and_belongs_to_many :users, join_table: :friends_users
end
app/models/friend_request.rb
class FriendRequest < ActiveRecord::Base
belongs_to :user
belongs_to :friend
end
Here is the Repo Link

Has one and has many of itself

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)

Rails 4 has_many through - Message Sender & Receiver

I'm trying to achieve the following, relationships and object calls
A User can send many messages (user.sent_messages)
A Message can have one Sender (message.sender)
A User can receive many messages (user.received_messages)
A Message can have many receivers (message.receivers)
My schema looks like this:
create_table "activities", force: true do |t|
t.integer "sender_id"
t.integer "message_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "receiver_id"
end
create_table "messages", force: true do |t|
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "read", default: false
end
My Models look like this:
class User < ActiveRecord::Base
has_many :activities, class_name: 'Activity', foreign_key: 'sender_id', dependent: :destroy
has_many :sent_messages, through: :activities
has_many :reverse_activities, class_name: 'Activity', foreign_key: 'receiver_id'
has_many :received_messages, through: :reverse_activities
end
class Activity < ActiveRecord::Base
belongs_to :sent_messages, class_name: 'User'
belongs_to :received_messages, class_name: 'User'
belongs_to :message
end
class Message < ActiveRecord::Base
has_many :activities, foreign_key: 'sender_id'
has_one :sender, through: :activities, foreign_key: 'sender_id', class_name: 'User'
has_many :reverse_activities, foreign_key: 'receiver_id', class_name: 'User'
has_many :receivers, through: :reverse_activities, source: :receiver
end
The methods sent_messages & received_messages work, however they point straight back to the User table and return the details of that user, not the message.
I haven't yet tried to get the Message model working as the User model is incorrect.
Thanks!
Thanks to both suggestions i've got the following working
class User < ActiveRecord::Base
has_many :activities, class_name: 'Activity', foreign_key: 'sender_id'
has_many :sent_messages, through: :activities, foreign_key: 'message_id', class_name: 'Message', source: :sender
has_many :reverse_activities, class_name: 'Activity', foreign_key: 'receiver_id'
has_many :received_messages, through: :reverse_activities, foreign_key: 'message_id', class_name: 'Message', source: :receiver
end
class Message < ActiveRecord::Base
has_one :sent_activities, class_name: 'Activity', foreign_key: 'message_id'
has_one :sender, through: :sent_activities, foreign_key: 'sender_id', class_name: 'User'
has_many :receiver_activities, class_name: 'Activity', foreign_key: 'message_id'
has_many :receivers, through: :receiver_activities, foreign_key: 'receiver_id', class_name: 'User'
validates :body, presence: true
end
class Activity < ActiveRecord::Base
belongs_to :sender, class_name: 'User'
belongs_to :receiver, class_name: 'User'
belongs_to :receiver, class_name: 'Message'
belongs_to :sender, class_name: 'Message'
end
As a result the method's i desired are working.
Now just to get the create actions working!
Too complicated
Why don't you try this:
#app/models/message.rb
Class Message < ActiveRecord::Base
belongs_to :sender, class_name: "User", primary_key: "sender_id"
belongs_to :recipient, class_name: "User", primary_key: "recipient_id"
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :sent_messages, class_name: "Message", foreign_key: "sender_id"
has_many :received_messages, class_name: "Message", foreign_key: "recipient_id"
end
users
id | name | email | created_at | updated_at
messages
id | sender_id | recipient_id | title | body | created_at | updated_at
This will allow you to load the data like this:
#message.sender
#message.recipient
#user.sent_messages
#user.received_messages
To save the data, you can use:
#app/controllers/messages_controller.rb
def new
#message = Message.new
end
def create
#message = Message.new(message_params)
end
private
def message_params
params.require(:message).permit(:recipient_id, :title, :body).merge(sender_id: current_user.id)
end
#app/views/messages/new.html.erb (user has to be logged in)
<%= form_for #message do |f| %>
<%= f.collection_select(:recipient_id, User.all, :id, :name) %>
<%= f.text_field :title %>
<%= f.text_area :body %>
<%= f.submit %>
<% end %>
There are some mistakes in Model Message:
class Message < ActiveRecord::Base
Shouldn't the foreign_key be : "message_id"?
has_many :activities, foreign_key: 'sender_id'
same problem in foreign_key: 'sender_id', and other likely thing in following
has_one :sender, through: :activities, foreign_key: 'sender_id', class_name: 'User'
has_many :reverse_activities, foreign_key: 'receiver_id', class_name: 'User'
has_many :receivers, through: :reverse_activities, source: :receiver
end
You looks like use message's id to join other table's sender id and receive id.
class Activity < ActiveRecord::Base
The class name is ‘User', so sent_messages, received_messages return user information
belongs_to :sent_messages, class_name: 'User'
belongs_to :received_messages, class_name: 'User'
belongs_to :message
end
Hope it help.

Resources