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?
Related
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
I have a User model and a Task model. All users are the same and each user can create a new task and assign that task to another user. In the Task model I have an assigned_by column and an assigned_to column, so that anyone can create a new task and assign it to anyone else. Later I want to be able for each User to view all tasks assigned to them and all tasks they have assigned to someone else. To do this, I want to setup an association. Is it okay to do something like this?
class Task < ApplicationRecord
belongs_to :user, :foreign_key => 'assigned_by'
belongs_to :user, :foreign_key => 'assigned_to'
end
Where I have two foreign keys in the same model. Then in the User model I have:
class User < ApplicationRecord
has_many :tasks
end
Is this the proper way to do something like this?
What you probably want is to setup three tables:
class User < ApplicationRecord
has_many :assignments, foreign_key: 'assignee_id'
has_many :assignments_as_assigner, foreign_key: 'assignee_id'
has_many :tasks, through: :assignments
has_many :assigned_tasks, through: :assignments_as_assigner
has_many :created_tasks, class_name: 'Task'
foreign_key: 'creator'
end
class Task < ApplicationRecord
belongs_to :creator, class_name: 'User'
has_many :assignments
end
class Assignment < ApplicationRecord
belongs_to :assignee, class_name: 'User'
belongs_to :assigner, class_name: 'User'
belongs_to :task
end
This creates a one to many association so that a task can be assigned to many users.
Each association in the model has to have a unique name - otherwise you will overwrite the previous association.
The approach you suggested will not work as you can't define 2 methods with the same name (in this case both will be called user).
A better way would be calling the relation by what it actually.
For example
class Task < ApplicationRecord
belongs_to :assigned_by, class_name: 'User'
belongs_to :assigned_to, class_name: 'User'
end
You may also need to add a foreign_key option or call the foreign key in the DB assigned_by_id and assigned_to_id
Also, you will need to change your User model as tasks method is ambiguous.
class User < ApplicationRecord
has_many :tasks_delegated, foreign_key: 'assigned_by_id', class_name: 'Task'
has_many :tasks_assigned, foreign_key: 'assigned_to_id', class_name: 'Task'
end
Try to do like this to prevent overwriting.
class User < ApplicationRecord
has_many :owned_tasks, class_name: "Task", foreign_key: "owner_id"
has_many :assigned_tasks, class_name: "Task", foreign_key: "assignee_id"
end
class Task < ApplicationRecord
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end
I have a relationship model in which two Users can enter into a Trade for the exchange of two Items.
class User < ActiveRecord::Base
has_many :owned_items, class_name: "Item"
has_many :trades_received, class_name: "Trade", through: :owned_items, source: :trades
has_many :trades
has_many :wanted_items, class_name: "Item", through: :trades, source: :item
end
class Item < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: :user_id
has_many :trades, dependent: :destroy
has_many :trade_requesters, through: :trades
has_many :trade_recipients, through: :trades
end
class Trade < ActiveRecord::Base
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item", foreign_key: :wanted_item_id
belongs_to :collateral_item, class_name: "Item", foreign_key: :collateral_item_id
end
The migration on my Trades table looks like this:
create_table :trades do |t|
t.belongs_to :trade_requester
t.belongs_to :trade_recipient
t.belongs_to :wanted_item
t.belongs_to :collateral_item
end
The stack trace leads to a helper method I'm using to list all Trade requests. That line says #trades = current_user.trades_received.requested.count, and then on down to the model association on User where has_many :owned_items, class_name: "Item". From my understanding, it looks like the trades_received method, which is called through: :owned_items and source: :trades should be referencing the :wanted_item_id foreign key in the migration. But it is not. It works if I create a migration to add item_id, but a Trade needs two items, and so I've split it up into the two wanted_item and collateral_item associations. How do I set that User association up so that it references the Item that is being requested by another User? Should Items has_many :trades, the way I have it, or should Items belongs_to :trades?
Full error:
PG::UndefinedColumn: ERROR: column trades.item_id does not exist
LINE 1: ...LECT COUNT(*) FROM "trades" INNER JOIN "items" ON "trades"."...
^
: SELECT COUNT(*) FROM "trades" INNER JOIN "items" ON "trades"."item_id" = "items"."id" WHERE "items"."user_id" = $1 AND "trades"."approved" IS NULL
tldr: I need to track a bunch of complex has_many :through associations, I don't think my data model is correct, and need help understanding why. Thank you.
You're setting up two has_many :through relationship between User and Item, with Trade as the join table for both. You got some relationship confused. Here is a setup based on your migration:
class User < ActiveRecord::Base
has_many :received_trades, class_name: "Trade", foreign_key: "trade_recipient"
has_many :requested_trades, class_name: "Trade", foreign_key: "trade_requester"
has_many :collateral_items, through: :received_trades
has_many :wanted_items, through: :requested_trades
end
class Item < ActiveRecord::Base
has_many :collateral_items, class_name: "Trade", foreign_key: "collateral_item"
has_many :wanted_items, class_name: "Trade", foreign_key: "wanted_item"
has_many :trade_requesters, through: :wanted_items
has_many :trade_recipients, through: :collateral_items
end
class Trade < ActiveRecord::Base
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item"
belongs_to :collateral_item, class_name: "Item"
end
##migration
create_table :trades do |t|
t.belongs_to :trade_requester
t.belongs_to :trade_recipient
t.belongs_to :wanted_item
t.belongs_to :collateral_item
end
Some explanation:
Item has_many :collateral_item ## item_id in table collateral_items
Item has_many :collateral_item, class_name: "Trade", foreign_key: "collateral_item"
##collateral_item_id in trades table.
Ok, well. The problem is here:
has_many :trades, dependent: :destroy
And in your Trade model:
belongs_to :wanted_item, ...
belongs_to :collateral_item, ..
Rails cannot handle this automatically.
You need to do one of this steps (depending on what you need in your app):
If you need separate associations:
class User < ActiveRecord::Base
has_many :trades_received, class_name: "Trade", through: :owned_items, source: :wantable_trades
end
class Item < ActiveRecord::Base
has_many :wanted_trades, class_name: 'Trade', inverse_of: :wanted_item, dependent: :destroy
has_many :collateral_trades, class_name: 'Trade', inverse_of: :collateral_item, dependent: :destroy
end
If you need all trades as single association:
Well, you will have a pain in the ass :) In this case you should either select associations manually, or rethink your data model.
I have many to many association through UserTask table. I need to get user with type creator from task (task.creator). It should be like task.user_tasks.where(user_type: 0) but with rails associations. Is it possible?
Here is my User model
class User < ApplicationRecord
has_many :user_tasks, foreign_key: 'user_email', primary_key: 'email'
has_many :tasks, through: :user_tasks
end
Here is my Task model
class Task < ApplicationRecord
has_many :user_tasks, dependent: :destroy
has_many :users, through: :user_tasks
end
Here is my UserTask model
class UserTask < ApplicationRecord
belongs_to :user, foreign_key: 'user_email', primary_key: 'email'
belongs_to :task
enum user_type: [:creator, :allowed]
end
You can get it using
User.joins(:user_tasks).where(user_tasks: { user_type: 0 })
I want to implement these models:
Doctor # maybe I should use STI Doctor < User
has_many :cases
Patient
has_many :cases
Case # each case has one pair of doctor and patient
belongs_to :doctor
belongs_to :patient
But I have User model right now with Devise authentication. What fields should have cases table and do I need to change something in associations to make that working?
Doctor and User should be able to sign in to the app.
So I think cases table should have something like patient_id and doctor_id?
Your Case needs a doctor_id and a patient_id. These could either reference Doctor and Patient as separate models:
class Doctor < ActiveRecord::Base
has_many :cases
end
class Patient < ActiveRecord::Base
has_many :cases
end
class Case < ActiveRecord::Base
belongs_to :doctor
belongs_to :patient
end
Or they could reference User in general (probably with some kind of role field):
class User < ActiveRecord::Base
has_many :doctor_cases, class_name: "Case", foreign_key: "doctor_id"
has_many :patient_cases, class_name: "Case", foreign_key: "patient_id"
end
class Case < ActiveRecord::Base
belongs_to :doctor, class_name: "User"
belongs_to :patient, class_name: "User"
end
Now a user could be a doctor in one case and a patient in another case :-)
I think class_name will work
class User < ActiveRecord::Base
has_many :doctors, :class_name => 'User', :foreign_key => 'doctor_id'
has_many :patients, :class_name => 'User', :foreign_key => 'patient_id'
end
class Case < ActiveRecord::Base
belongs_to :doctor, :class_name => 'User', :foreign_key => 'doctor_id'
belongs_to :patient, :class_name => 'User', :foreign_key => 'patient_id'
end