Scoping with two foreign keys - ruby-on-rails

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

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

Rails, model relationship child-parent

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

No such column for attribute in a join table

I'm trying to create an app where a user chooses volunteers to complete their task. The way that volunteers are considered participants is through the selected boolean attribute placed on the TaskVolunteer join table. Unfortunately when I try to find the participants of a particular class I get the following error:
task = Task.create
task.participants
SQLite3::SQLException: no such column: users.selected
Models
class User < ActiveRecord::Base
has_many :owned_tasks, class_name: "Task", foreign_key: :owner_id
has_many :task_volunteers, as: :volunteer
has_many :volunteered_tasks, through: :task_volunteers
end
class TaskVolunteer < ActiveRecord::Base
# task_id, volunteer_id, selected (boolean)
belongs_to :task
belongs_to :volunteer, class_name: "User", foreign_key: :volunteer_id
end
class Task < ActiveRecord::Base
# owner_id
has_many :task_volunteers
has_many :volunteers, through: :task_volunteers, source: :volunteer
has_many :participants, -> {where(selected: true)}, through: :task_volunteers, source: :volunteer
belongs_to :owner, class_name: "User"
end
The error is caused by a faulty foreign_key option in TaskVolunteer.
belongs_to :volunteer, class_name: "User", foreign_key: :volunteer_id
foreign_key here refers to the column on the users table not on tasks_volunteers. You can just remove the foreign key option.
class TaskVolunteer < ActiveRecord::Base
# task_id, volunteer_id, selected (boolean)
belongs_to :task
belongs_to :volunteer, class_name: "User"
end
Added
I have to say though by altering the naming a bit and using an enum to denote status you could cut the code and cognitive complexity quite dramatically.
class User < ActiveRecord::Base
has_many :participations, foreign_key: :participant_id
has_many :owned_tasks, class_name: "Task", as: :owner
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :participations
has_many :participants, through: :participations, source: :participant
# Dynamically generates relations such as 'selected_participants'
Participation.statuses.keys.each do |status|
has_many "#{status}_participants".to_sym,
-> { where(participations: { status: status.to_sym }) },
through: :participations,
source: :participant
end
end
class Participation < ActiveRecord::Base
belongs_to :task
belongs_to :participant, class_name: "User"
enum status: [:interested, :selected]
end
The enum macro gives you stuff like:
user.participations.selected
participation.selected?

Joining one table two times

My db schema:
tournaments(id, ...)
teams(tournament_id, id, ...)
matches(tournament_id, id, team_id_home, team_id_away, ...)
Models:
class Tournament < ActiveRecord::Base
has_many :teams, dependent: :destroy
has_many :matches, dependent: :destroy
...
end
class Team < ActiveRecord::Base
belongs_to :tournament
...
end
class Match < ActiveRecord::Base
belongs_to :tournament
has_many :teams
...
end
I would like to have the following data in my view:
match_id team_id_home team_id_away team_id_home_name team_id_away_name
So, I'm asking for help with the following query (I'm trying to get team's names, but having problem with joining):
#matches = #tournament.matches.where(:tournament => #tournament).joins(:teams).paginate(page: params[:page])
I'm fairly new to rails, but you should be able to setup your associations like this: (going from memory)
class Match < ActiveRecord::Base
belongs_to :tournament
has_one :home_team, :class_name => "Team", :foreign_key => "team_id_home"
has_one :away_team, :class_name => "Team", :foreign_key => "team_id_away"
end
#####
m = Match.first
m.away_team.team_name
m.home_tam.team_name
Or in your case:
#matches = #tournament.matches.paginate(page: params[:page])
I don't think you need the where function: the has_many association tells rails to only pull matching matches.
It is belongs_to, not has_one in Match model.
class Match < ActiveRecord::Base
belongs_to :tournament
belongs_to :home_team, :class_name => "Team", :foreign_key => "team_id_home"
belongs_to :away_team, :class_name => "Team", :foreign_key => "team_id_away"
end
class Team < ActiveRecord::Base
belongs_to :tournament
has_many :matches
end
Now I can use tournament.home_team.name in my view

Adding counter cache to self join table in rails

I am trying to add a counter cache on a a column in a self join association.
I have two models User and followings. User has followers and followees, who are from the user table itself.
User.rb
has_many :followings
has_many :followers, :through => :followings
has_many :followees, :through => :followings
Following.rb
class Following < ActiveRecord::Base
attr_accessible :followee_id, :follower_id
belongs_to :follower, :class_name => "User"
belongs_to :followee, :class_name => "User"
end
now i want to add counter cache on follower and followees. I have followers_count and followees_count columns in user table.
I tried
belongs_to :follower, :class_name => "User" , :counter_cache => true
But this doesn't return any data in the user table.
Any help would be appreciated.
It was long-long time ago, but
User.rb
class User < ActiveRecord::Base
has_many :followings_as_follower, class_name: 'Following', foreign_key: 'follower_id', dependent: :destroy
has_many :followings_as_followee, class_name: 'Following', foreign_key: 'followee_id', dependent: :destroy
has_many :followers, through: :followings_as_followee, source: :follower
has_many :followees, through: :followings_as_follower, source: :followee
def follow?(user)
followees.reload.include? user
end
def follow(user)
return if follow?(user)
followings_as_follower.create(followee: user)
end
def unfollow(user)
return unless follow?(user)
followings_as_follower.where(followee: user).first.destroy
end
end
Following.rb
class Following < ActiveRecord::Base
belongs_to :follower, class_name: 'User', counter_cache: :followees_count
belongs_to :followee, class_name: 'User', counter_cache: :followers_count
validates :follower, presence: true
validates :followee, presence: true
validates :followee, uniqueness: { scope: [:follower, :followee] }
end
Try this,
belongs_to :follower, foreign_key: 'the_id_of_foreign_key', class_name: 'User', counter_cache: :followers_count
You can use the column_name instead of true in counter_cache.

Resources