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.
Related
I'm getting this error message from trying to do #jobs = #user.clients.jobs.where(tutor_id: #user). What's wrong with my architecture?
Could not find the source association(s) :client_id in model Arrangement. Try 'has_many :clients, :through => :client_arrangements, :source => '. Is it one of tutor, client, or tutorials?
user.rb
has_many :client_arrangements, class_name: 'Arrangement', foreign_key: :tutor_id
has_many :clients, through: :client_arrangements, class_name: 'User', source: :client_id
has_many :jobs, foreign_key: :client_id
arrangement.rb
belongs_to :tutor, class_name: 'User'
belongs_to :client, class_name: 'User'
controller:
def jobs
#jobs = #user.clients.jobs.where(tutor_id: #user)
end
job.rb
belongs_to :client, class_name: 'User'
in the jobs table:
t.integer "client_id"
t.integer "tutor_id"
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
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
I have a simple app and I have to describe the relationship student-parent as many-to-many where the following would work:
current_user.parents
current_user.children
Currently I have the following
class User < ApplicationRecord
has_many :parent_students
has_many :students, through: :parent_students, class_name: "User"
has_many :parents, through: :parent_students, class_name: "User"
end
class ParentStudent < ApplicationRecord
belongs_to :student, class_name: "User", foreign_key: "student_id"
belongs_to :parent, class_name: "User", foreign_key: "parent_id"
end
class CreateParentStudents < ActiveRecord::Migration[5.1]
def change
create_table :parent_students do |t|
t.references :student, index: true, references: :users
t.references :parent, index: true, references: :users
t.timestamps
end
end
end
Any idea how this would work? Thank you!
probably this can help, the declaration inside User
class User < ActiveRecord::Base
# as parent
has_many: student_relations, foreign_key: :parent_id, class_name: "ParentStudent"
has_many: students, through: :student_relations, source: :student
# as student
has_many: parent_relations, foreign_key: :student_id, class_name: "ParentStudent"
has_many: parents, through: :parent_relations, source: :parent
end
class ParentStudent < ActiveRecord::Base
belongs_to :student, foreign_key: "student_id", class_name: "User"
belongs_to :parent, foreign_key: "parent_id", class_name: "User"
end
I have the following schema:
I want to have the option to call proposals for both foreign_keys (author_id and editor_id) as well for separate ones (for example author_proposals and editor_proposals) and I need to have the option to lazy or eager load them (for example User.includes(:proposals) or without it with joins).
Update:
#I have the scopes which is like this:
class User < ActiveRecord::Base
has_many :author_proposals, class_name: 'Proposal', foreign_key: :author_id
has_many :editor_proposals, class_name: 'Proposal', foreign_key: :editor_id
end
class Proposal < ActiveRecord::Base
belongs_to :author, class_name: 'User', foreign_key: :author_id
belongs_to :editor, class_name: 'User', foreign_key: :editor_id
end
But I need a universal one which it will give me all the proposals (both author_proposals and editor_proposals) which it will also eager load them. Should I use conditions on has_many?
I would do something like this:
class User < ActiveRecord::Base
has_many :authored_proposals, class_name: 'Proposal', foreign_key: :author_id
has_many :editored_proposals, class_name: 'Proposal', foreign_key: :editor_id
def proposals
Proposal.where('author_id = :id OR editor_id = :id', { id: id }).distinct
end
end
class Proposal < ActiveRecord::Base
belongs_to :author, class_name: 'User', foreign_key: :author_id
belongs_to :editor, class_name: 'User', foreign_key: :editor_id
def users
User.where(id: [author_id, editor_id].uniq)
end
end
You can do something like:
class User < ActiveRecord::Base
has_many :authored_proposals, class_name: 'Proposal', foreign_key: :author_id
has_many :editored_proposals, class_name: 'Proposal', foreign_key: :editor_id
def proposals
authored_proposals | editored_proposals
end
end
class Proposal < ActiveRecord::Base
belongs_to :author, class_name: 'User', foreign_key: :author_id
belongs_to :editor, class_name: 'User', foreign_key: :editor_id
def users
author | editor
end
end
You can eager load proposals by doing: User.includes(:authored_proposals, :editored_proposals). This is not pure rails way, but seems cleaner to me.
You can also do :
class User < ActiveRecord::Base
has_many :authored_proposals, class_name: 'Proposal', foreign_key: :author_id
has_many :editored_proposals, class_name: 'Proposal', foreign_key: :editor_id
has_many : proposals, finder_sql: proc { "SELECT * FROM proposals WHERE (proposals.author_id = #{id} or proposals. editor_id = #{id})" }
end
Set your associations like this:
class User < ActiveRecord::Base
has_many :author_proposals, :class_name => "Proposal", :foreign_key => "author_id"
has_many :editor_proposals, :class_name => "Proposal", :foreign_key => "editor_id"
end
class Proposal < ActiveRecord::Base
belongs_to :author, :class_name => 'User', :foreign_key => "author_id"
belongs_to :editor, :class_name => 'User', :foreign_key => "editor_id"
end