Rails ActiveRecord Associations with one to many - ruby-on-rails

So, I"m not sure how to setup my associations. First, I have a User model (Devise) which has email and password.
class User < AR::Base
end
After that, I have multiple types of User models which contain more details information about the users:
class Doctor < AR::Base
belongs_to: User
end
And:
class Nurse < AR::Base
belongs_to: User
end
And:
class Therapist < AR::Base
belongs_to: User
end
So, I'm not sure how the User model should relate to the others. Is my design flawed?
Thanks for helping a noob.

The easiest way to implement what you are trying to achieve is to have a column on user to assign a role. So you can call the methods like this:
User.add_role(:doctor)
User.has_role?(:doctor)
You can do that using this gem https://github.com/mcrowe/roleable
Another way to implement it is by using ActiveRecord Enum:
http://api.rubyonrails.org/v5.1/classes/ActiveRecord/Enum.html The implementation would look like this:
User.role # => :doctor
User.doctor? # => true
User.therapist! # => true
User.role # => :therapist
I personally prefer using enums.
A complex way to do it would be to use polymorphism. Where you can put User as a polymorphic model. This blog post explains it in great detail. https://robots.thoughtbot.com/using-polymorphism-to-make-a-better-activity-feed-in-rails

Rails DB Associations Documentation Link
add these has_many's to user.rb
#user.rb
has_many :doctor
has_many :nurse
has_many :therapist
and You need to add user_id to doctor, nurse and therapist.
Such as like:
rails g migration add_user_id_to_nurses user_id:integer
rails g migration add_user_id_to_doctors user_id:integer
rails g migration add_user_id_to_therapits user_id:integer
Do not forget rake db:migrate at the end.

The best way to set setup a one-to-many association between these different types of users, with minimal duplicates, would be by setting up User to belong_to these other models Doctor, Nurse and Therapist
First, setup has_many association between these models to the User model
# app/models/doctor.rb
class Doctor < ActiveRecord::Base
has_many: :users
end
# app/models/nurse.rb
class Nurse < ActiveRecord::Base
has_many: :users
end
# app/models/therapist.rb
class Therapist < ActiveRecord::Base
has_many: :users
end
Then, add migrations to add doctor_id:integer, nurse_id:integer and therapist_id:integer to users table.
Then, setup belongs_to association to other ActiveRecord models.
# app/models/user.rb
class User < ActiveRecord::Base
belongs_to: :doctor
belongs_to: :nurse
belongs_to: :therapist
end
With this setup, you can access ActiveRecord data of these models as follows:
# get doctor associated to User.last
User.last.doctor
# get all the users who are patients of Doctor.last
Doctor.last.users
# get the nurse associated to User.last
User.last.nurse
# get all the users who are patients of Nurse.last
Nurse.last.users

Related

Active Record includes with a belongs_to and has_many doesn't return associations

I have the following models:
user.rb
class User < ApplicationRecord
has_many :invitations
end
organization.rb
class Organization < ApplicationRecord
has_many :invitations
end
invitation.rb
class Invitation < ApplicationRecord
belongs_to :user
belongs_to :organization
end
I'm trying to query Active Record in the following way:
user = User.find(params[:id])
user.invitations.includes(:organization)
I want to be able to get all invitations for the user and also have the invitations include attributes of their related organization. However, I am only getting the invitation and none of the organization's attributes.
Even if I try:
Invitation.includes(:organization)
I'm still not getting each invitation's associated organization.
Any and all help is greatly appreciated!
includes method provides eager loading. This solves the N + 1 queries problem. You can access the loaded organization like user.invitations.first.organization. There will be no new queries here.
If you want to combine invitation and organization attributes, you can use joins and select.
user.invitations
.joins(:organization)
.select('invitations.*, organizations.foo, organizations.boo as blabla')
#demir has already answered your question.
Just as an addition - probably, you mixed up includes for models with include option of to_json \ as_json.
If you want to return some JSON result (e.g. in your API response), then you can do user.as_json(include: [invitations: { include: :organization }])

Which is the best use of discard (or act_as_paranoid) in rails-admin?

I'm using the gem discard and also rails-admin. I would like to know which is the best approach to list my active users in a shop in rails admin, taking into consideration the users kept
I've created a method in the model shop:
class Shop < ApplicationRecord
include ShopRailsAdmin
has_many :users
def active_users
users.kept
end
end
In rails admin I'm using:
field :active_users do
label 'Users'
end
But I'm receiving an AssociationRelation instead of a CollectionProxy so in the view, the association looks like
#<User::ActiveRecord_AssociationRelation:0x00007f9c34c1f8e0>
Is there another way to do this so I can avoid defining the method in the model shop?
PD: the tag should have been also discard but it does not exist and I could not create it.
Thanks!
You need to define it as an scoped association
class Shop < ApplicationRecord
include ShopRailsAdmin
has_many :users
has_many :active_users, -> lambda {
where(discarded_at: nil)
}, class_name: 'User'
end
I'm assuming you did not personalize the discard_column.
Rails admin should display them right.

Rails Converting a has_many relationship into a has and belongs to many

I have a Rails app with the following relationship:
region.rb
class Region < ActiveRecord::Base
has_many :facilities
end
facility.rb
class Facility < ActiveRecord::Base
belongs_to :region
end
I want to expand functionality a bit so that facilities can belong to more than one region at a time. I believe I can do this with a has_many_through relationship but I'm needing some guidance on converting the existing has_many into a has many through. I understand how to create and wire up the join table, but how would I take existing data and translate it?
So for instance. On a facility object there is region_id, since the facilities can belong to more than one region I'd probably need a region_ids field and shovel the collection of regions into that column which should then populate the other side of the association via the join table. I have this part pretty much figured out as far as moving forward and wiring up the association. But I'm unsure as to how to take existing data and translate it over so the app doesn't break when I change the model association.
Any advice would be greatly appreciated.
I suggest you to always use has_many :through instead of HBTM.
To establish this kind of relation you'll need the following set up:
# region.rb
class Region
has_many :facility_regions
has_many :facilities, through: :facility_regions
end
# facility.rb
class Facility
has_many :facility_regions
has_many :regions, through: :facility_regions
end
# facility_region.rb
class FacilityRegion
belongs_to :facility
belongs_to :region
end
Also, of course, you'll need to create a migration:
rails g migration create_facility_regions facility_id:integer region_id:integer
# in this migration create a uniq index:
add_index :facility_regions, %I(facility_id region_id), name: :facility_region
rake db:migrate
UPD
As to migration from one database state to another one.
I think it should not be a problem.
1) Do not delete the relations you had before (leave has_many :facilities and belongs_to :region in models).
2) When new table is created and new associations added to the classes (which I showed) create a new migration:
rails g migration migrate_database_state
3) Write the script, which will create new records in db (to reflect the current state of things):
ActiveRecord::Base.transaction do
Facility.where.not(region_id: nil).find_each do |facility|
next if FacilityRegion.find_by(falicity_id: facility.id, region_id: facility.region_id)
FacilityRegion.create!(facility_id: facility.id, region_id: facility.region_id)
end
end
4) Put this script into last created migration and run it (or in console without migration, effect would be the same).
5) After script is successfully run, create new migration in which you delete region_id from facilities table and remove these associations definitions (has_many :facilities and belongs_to :region) from models.
It must be it. I might have made some typos or so, make sure I did not miss anything and
You need to add another model, a "middle guy" called FacilityRegion.rb, like this:
facility.rb
class Facility < ActiveRecord::Base
has_many :falicity_regions
has_many :regions, through: falicity_regions
end
facility_region.rb
class FacilityRegion < ActiveRecord::Base
belongs_to :region
belongs_to :facility
end
region.rb
class Region < ActiveRecord::Base
has_many :falicity_regions
has_many :facilities, through: falicity_regions
end
If you want to use belongs_and_has_many relationship, you need to:
rails g migration CreateJoinTableRegionsFacilities regions facilities
Then,
rake db:migrate
Now, your relationships should be:
Region.rb:
class Region < ApplicationRecord
has_and_belongs_to_many :facilities
end
Facility.rb
class Facility < ApplicationRecord
has_and_belongs_to_many :regions
end
In order to populate the new join table, you will need to in your console:
Region.all.find_each do |r|
Facility.where(region_id: r.id).find_each do |f|
r.facilities << f
end
end
Now, you can either leave the columns region_id and facility_id in Facility and Region table, respectively, or you can create a migration to delete it.

Rails association in concerns

I am using concerns for my rails application. I've different kind of users so I have made a loggable.rb concern.
In my concern I have
included do
has_one :auth_info
end
because every of my user that will include the concern will have an association with auth_info table.
The problem is, what foreign keys I need to put in my auth_info table?
E.G
I've 3 kind of users:
customer
seller
visitor
If I had only customer, in my table scheme I would have put the field
id_customer
but in my case?
You can solve this with polymorphic associations (and drop the concern):
class AuthInfo < ActiveRecord::Base
belongs_to :loggable, polymorphic: true
end
class Customer < ActiveRecord::Base
has_one :auth_info, as: :loggable
end
class Seller < ActiveRecord::Base
has_one :auth_info, as: :loggable
end
class Visitor < ActiveRecord::Base
has_one :auth_info, as: :loggable
end
Now you can retrieve:
customer.auth_info # The related AuthInfo object
AuthInfo.first.loggable # Returns a Customer, Seller or Visitor
You can use rails g model AuthInfo loggable:references{polymorphic} to create the model, or you can create the migration for the two columns by hand. See the documentation for more details.
Since user has roles 'customer', 'seller', 'visitor'.
Add a column called role to Users table.
Add a column called user_id to auth_infos table.
class AuthInfo < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :auth_info
end
you can do
user = User.first
user.auth_info
Now you a additional logic to your concerns.

Report/Reports on model in rails

Hi I just create a model with relations with other once but I'm surprised about the pluralize option of rails. I mean.
I create the model like this:
rails g model Report name:string....
like I did with:
rails g model Patient name:string...
rails g model Doctor name:string....
Doctor has many Patients so I can go to the console and type:
patient.doctor => gives me the doctor from a patient
doctor.patients => gives me all patients from a doctor (note patients in plural)
And here is the strange thing, I did exactly the same with report and I expect to have the command:
patient.reports (note plural)
But instead of this if I want to retrieve the patient reports I have to do:
patient.report (note singular)... AND IT WORKS!
Does anyone can illuminate my blindness?
The methods to retrieve the related object(s) depends on how you've declared it in the Model.
Some examples:
class Patient < ActiveRecord::Base
belongs_to :doctor # singular
end
class Doctor < ActiveRecord::Base
has_many :patients # plural
end
Then you can do:
patient.doctor # => return the associated doctor if exists
doctor.patients # => return the patients of this doctor if exist
I think you've declared your relation in singular:
# What I think you have
class Patient < ActiveRecord::Base
has_many :report
end
But you should use plural here:
# What I think you should use
class Patient < ActiveRecord::Base
has_many :reports
^
# Make it plural
end

Resources