Rails, model relationship child-parent - ruby-on-rails

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

Related

Converting a has_one association to has_many

Wondering about a relationship I have and not sure wheter this is due to cause some issues in the future.
I have the following relationships with Users and Leases.
class User < ApplicationRecord
has_one :lease, foreign_key: "tenant_id"
has_many :leases, foreign_key: "landlord_id"
end
and
class Lease < ApplicationRecord
belongs_to :tenant, class_name: "User"
belongs_to :landlord, class_name: "User"
end
and I'm trying to convert the relationship with the tenant and the lease to has_many, but I don't know how to approach this the right way.
I got this to work with
class User < ApplicationRecord
has_many :leases_as_landlord, class_name: "Lease", foreign_key: "tenant_id"
has_many :leases_as_tenant, class_name: "Lease", foreign_key: "landlord_id"
end
and
class Lease < ApplicationRecord
belongs_to :tenant, class_name: "User", inverse_of: :leases_as_tenant
belongs_to :landlord, class_name: "User", inverse_of: :leases_as_landlord
end
but I don't like calling User.leases_as_landlord and User.leases_as_tenant. What I would like to do is just call User.leases to return the leases in which the User is either the landlord or the tenant.
You can add instance method:
class User < ApplicationRecord
has_many :leases_as_landlord, class_name: "Lease", foreign_key: "tenant_id"
has_many :leases_as_tenant, class_name: "Lease", foreign_key: "landlord_id"
def leases
leases_as_landlord.or(leases_as_tenant)
end
end
It will also return ActiveRecord_AssociationRelation and you can chain other ActiveRecord method on it.
Also I would recommend to follow Rails Convention and name your has_many associations in the plural.
class User < ApplicationRecord
has_many :landlord_leases, class_name: 'Lease', foreign_key: :tenant_id
has_many :tenant_leases, class_name: 'Lease', foreign_key: :landlord_id
def leases
landlord_leases.or(tenant_leases)
end
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

Multiple Associations With the Same Table rails

i have two class User and Bug there are two foreign keys in bug which are referencing to user_id ..the problem is that how i store user_id in foreign key column while creating the record.like for example if user enter bug then his id store in buger_id colunm.
class Bug
belongs_to :buger, class_name: "User", foreign_key: "buger_id"
belongs_to :developer , class_name: "User", foreign_key: "developer_id"
class user
has_many :created_bugs, class_name:"bugs"
has_many :developed_bugs, class_name:"bugs"
You need to add the foreign_key to the has_many declaration!
class User < ActiveRecord::Base
has_many :created_bugs, class_name: 'Bug' , foreign_key: :buger_id
has_many :developed_bugs, class_name: 'Bug' , foreign_key: :developer_id
end
class Bug < ActiveRecord::Base
belongs_to :buger, class_name: 'User'
belongs_to :developer , class_name: 'User'
end
See also: http://guides.rubyonrails.org/association_basics.html
You can specify class and foreign key on the has_many line as well.
has_many :created_bugs, class_name:"Bug", foreign_key: 'buger_id'
has_many :developed_bugs, class_name:"Bug", foreign_key: 'developer_id'
In Rails 5.1 or greater you can do it like this:
Migration
class CreateBug < ActiveRecord::Migration
def change
ccreate_table(:bugs) do |t|
t.references :bugger, foreign_key: { to_table: 'users' }
t.references :developer, foreign_key: { to_table: 'users' }
end
end
end
This will create the fields bugger_id, and developer_id and make the database level references to the users table
Models
class Bug < ActiveRecord::Base
belongs_to :bugger, class_name: "User"
belongs_to :developer, class_name: "User"
end
class User < ActiveRecord::Base
has_many :created_bugs, class_name: "Bug", foreign_key: "bugger_id"
has_many :developed_bugs, class_name: "Bug", foreign_key: "developer_id"
end
FactoryBot
If you use FactoryBot then your factory might look something like this:
FactoryBot.define do
factory :bug do
association :bugger, factory: :user
association :developer, factory: :user
end
end

Scoping with two foreign keys

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

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