Rails associations within same model depending on attribute - ruby-on-rails

I am building a rails app with devise and cancancan and I am trying to create associations within the same model. I have a User model, a role model and an appointments model. The user can have the role of Doctor or Patient. I want to create an association so a Patient can create appointments with the Doctor. I managed to create the associations but I do not know how to make the patient only create appointments with a doctor and only in that way.
My models are like this:
User model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
belongs_to :role, optional: true
validates :name, :DOB, presence: true
has_many :doctor_user_appointments, class_name: 'Appointment', foreign_key: 'doctor_user_id', dependent: :destroy
has_many :patient_user_appointments, class_name: 'Appointment', foreign_key: 'patient_user_id', dependent: :destroy
before_save :assign_role
scope :doctor_user, -> {where("role_id = ?", 1)
scope :patient_user, -> {where("role_id = ?", 2)
def admin?
role.name == 'Admin'
end
def doctor?
role.name == 'Doctor'
end
def patient?
role.name == 'Patient'
end
def assign_role
self.role = Role.find_by name: 'Patient' if role.nil?
end
end
Role model
class Role < ApplicationRecord
has_many :users, dependent: :restrict_with_exception
end
Appointment model
class Appointment < ApplicationRecord
belongs_to :doctor_user, class_name: 'User'
belongs_to :patient_user, class_name: 'User'
end
With these models, I can create appointments between doctor_user and patient_user, both users, but the associations do not distinguish who has the role "doctor" and who has the role "patient". I have tried with scopes but it is not working.
What I basically want is that doctor_user could only be a user with role "doctor" or user with role_id=1 and a patient_user only could be a user with role_id=2 or role "patient"
Any help would be welcome since I am quite stuck
Thanks in advance

Simply updating with the following lines to your Appointment controller will do the job:
belongs_to :doctor_user, -> { doctor_user }, class_name: 'User'
belongs_to :patient_user, -> { patient_user }, class_name: 'User'

I have fixed the issue by adding these lines to the Appointment model
belongs_to :doctor_user, -> {where("role_id = ?", 1)}, class_name: 'User'
belongs_to :patient_user, -> {where("role_id = ?", 2)}, class_name: 'User'

Related

Identifying the right scope joins for pundit

I'm currently implementing pundit, where I am trying to identify whether or not a user has an admin role.
Issue
I'm trying to avoid creating a join_table between discounts and users, by leveraging the relationship between
discounts and attraction (a discount belongs to an attraction)
attractions and park (a park has_many attractions)
parks and users (many to many relationship, via a join_table).
--> However, I get the error message: undefined local variable or method `attraction' for #<DiscountPolicy::Scope:0x00007fa012ec6b70>
Question
I was wondering:
if it's even possible what I'm trying to do and if so
how will I be able to access the user?
Code
discount controller
def index
#user = current_user
if params[:attraction_id]
#attraction = Attraction.find(params[:attraction_id])
#discounts = #attraction.discounts
#discounts = policy_scope(#discounts)
else
#discounts = []
end
end
discount policy
class DiscountPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
# scope.where(user: user)
scope.joins(attraction: :discounts).where(discounts: { attraction_id: attraction.id }).joins(park: :attractions).where(attractions: { park_id: park.id }).joins(park: :user_parks).where(user_parks: { user_id: user.id })
else
raise Pundit::NotAuthorizedError
end
end
end
def index?
user.admin?
end
end
models
class Discount < ApplicationRecord
belongs_to :attraction
has_many :reservations
end
class Attraction < ApplicationRecord
belongs_to :park
has_many :discounts, dependent: :destroy
accepts_nested_attributes_for :discounts, allow_destroy: true
end
class Park < ApplicationRecord
has_many :attractions, dependent: :destroy
has_many :discounts, through: :attractions
has_many :user_parks, dependent: :destroy
has_many :users, through: :user_parks
accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end
class UserPark < ApplicationRecord
belongs_to :park
belongs_to :user
end
class User < ApplicationRecord
has_many :user_parks, dependent: :destroy
has_many :parks, through: :user_parks
enum role: [:owner, :admin, :employee, :accountant]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :admin
end
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :invitable
end
You need to have nested association joins. Here's what your scope should look like:
scope.joins(attraction: [park: :user_parks]).where(user_parks: { user_id: user.id })
You can go through the documentation to understand better.

Querying for values from several models

I have a Course and Lesson models. Course has several lessons. I want to find all the lessons for currently logged in student to generate kind of timetable.
I have a method that returns all the courses that this student is studying. Now I want to get all lessons from all those courses in #courses into #lessons, something like:
def index
#courses = current_student.find_courses
#lessons = #courses.lessons
end
Is it possible to do it somehow simple on one line?
The find_courses method is implemented as following:
def find_courses
Course.where("id IN (?)", StudentAssignment.select("course_id").where('student_id == (?)', self.id))
end
The Models:
class Student < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :student_assignments
has_many :courses, :through => :student_assignments
....
class Lesson < ApplicationRecord
belongs_to :room
belongs_to :teacher
belongs_to :course
....
class Course < ApplicationRecord
has_many :lessons, dependent: :destroy
has_many :teacher_assignments
has_many :teachers, :through => :teacher_assignments
has_many :student_assignments
has_many :students, :through => :student_assignments
...
class Student < ApplicationRecord
has_many :courses
def active_lessions
Lession.joins(course: :students).where(students: {id: self.id})
end
end
In this way you can directly get all active lesssions for current_user
current_student.active_lessions
Try:
#lessons = #courses.flat_map(&:lessons)
It takes each course in #courses list and gets the list of lessons for that course.

How do I make my task assignment associations?

I have a User model, a TodoList model, which has many todoItems. My models are :
User Model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
has_many :todo_lists
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
TodoList Model
class TodoList < ActiveRecord::Base
has_many :todo_items
belongs_to :user
end
ToItem Model
class TodoItem < ActiveRecord::Base
include AASM
belongs_to :todo_list
def completed?
!completed_at.blank?
end
#belongs_to :user
#belongs_to :friend, class_name: 'User', foreign_key: 'friend_id'
aasm :column => 'state', :whiny_transitions => false do
state :not_assigned, :initial => true
state :assigned
state :taskCompleted
end
I am trying to modify my models in such that any user can request to be assigned a taskItem and the user whom the task belongs to can accept or deny the requests. Once a an assignment request is approved, I want the task to be also associated to the user assigned to it.
How do I go about that with my model associations and relationships ? Thanks in advance for the help .
You could use an assignments association table, in a many-to-many relationship between User and TodoItem. Your association table would have an additional boolean attribute, indicating whether the item owner has accepted the request. Something like:
class TodoItem < ActiveRecord::Base
...
has_many :users, through: :assignments
...
end
For User:
class User < ActiveRecord::Base
...
has_many :todo_items, through: :assignments
...
end
And finally the association table:
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :todo_item
end
Your migration to create the association table would be something like this:
class CreateAssignments < ActiveRecord::Migration
def change
create_table :assignments do |t|
t.belongs_to :user, index: true
t.belongs_to :todo_item, index: true
t.boolean :request_accepted, default: false, null: false
t.timestamps null: false
end
end
end

2 references to same model without has_and_belongs_to_many

class Message < ActiveRecord::Base
belongs_to :conversation
belongs_to :user
end
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :conversations
has_many :conversations, class_name: "Conversation", foreign_key: :user2_id
end
class Conversation < ActiveRecord::Base
belongs_to :user
belongs_to :user2, class_name: "User", foreign_key: :user2_id
has_many :messages
end
spec:
require 'rails_helper'
RSpec.describe Message, type: :model do
it "should be available to 2 users" do
u = User.create(email: 'x#y.com', password: '8888888888')
u2 = User.create(email: 'z#w.com', password: '8888888888')
c = Conversation.create(user_id: u.id, user2_id: u2.id)
expect(u2.conversations.count).to eq 1
expect(u.conversations.count).to eq 1
end
end
It's this line:
expect(u.conversations.count).to eq 1
that fails.
Presumably because of my second has_many
But if I remove it, then expect(u2.conversations.count).to eq 1 fails.
Yes, I explicitly want only 2 users in a conversation. I was trying to avoid doing HABTM.
How do I make this work?
has_many :conversations
has_many :conversations, class_name: "Conversation", foreign_key: :user2_id
You can only have 1 has_many of a set name. Your first has_many is being overwritten by the second as the ruby class loads. I can't think of a better name but you should name one of them different, maybe something to demonstrate the person initiating the conversation.
You could use a has_many :through with a join model. Here is an explanation of how to set that up: http://www.tweetegy.com/2011/02/setting-join-table-attribute-has_many-through-association-in-rails-activerecord/.

Get values from a has_many through relation

My setup:
class ServiceUser < ActiveRecord::Base
belongs_to :user
belongs_to :service
enum status: [ :unavailable, :available ]
end
class Service < ActiveRecord::Base
has_many :service_users
has_many :users, :through => :service_users
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :service_users
has_many :services, :through => :service_users
end
From the ServiceUser model i can call the status field, in my case available and unavailable.
In my app i'm listing all the users for a specific service with:
#service.users
But now i want to show the status for each user from the ServiceUser model. What is the best way to do is?
Also i want to make a form for the ServiceUser model to make a new relation between the service and the current user:
ServiceUser.new(service_id: #service.id, user_id: current_user.id, status: 1)
What do i need to specify in the form for?
Construct a hash with user_id as the key and value as the status
user_status = Hash[*#service.service_users.map{|su|[su.user_id, su.status] }.flatten]
Now you can get the status for each user by
#service.users.each do |user|
p user_status[user.id]
end

Resources