How to find out all the association of a model? - ruby-on-rails

Say I have following models
class User < ApplicationRecord
devise :database_authenticable,:registerable
:recoverable, :trackable, :validatable, :rememberable
belongs_to :loginable, polymorphic: true
end
class Customer < ApplicationRecord
has_one :user, as: :loginable, dependent: :destroy
end
There are many models similar to Customer. How do I find out all such models from User model itself? I tried User.reflections. But it does not show the association with Customer. Is there a method say User.relationships that will list Customer and all models similar to Customer? If not how can I go about find out such models?

If the question is to look for all classes that User can belong to, then that's literally every model in your code. This is what polymorphic does.
If the question is what models User currently belongs to, then use the database to figure it out.
User.distinct.pluck(:loginable_type)
If the question is what models define a has_one :user relationship, then you'll have to look through all the models and ask that question from their perspective using the .reflections method you already found.

belongs_to :loginable, polymorphic: true yields loginable_id and loginable_type (contains class name) fields in User model. Linked model does not have to have reverse relation, so you can only find such models by carefully examining the code.
Also production data may contain links to models that already are not present in application at all (were deleted from app, but not from data, fetching these result in an error), get User.distinct.pluck(:loginable_type) from your production for a list of used ones (but because of above - list is not guranteed to be complete).

Related

How to share a Rails model with other Devise users in read-only mode?

In a Rails 5 application, I have a shortlist model in a HABTM relationship with a user model, with the users controlled by Devise. This is all working as expected, where each User can see their own Shortlists (only).
class Shortlist < ApplicationRecord
has_and_belongs_to_many :users
...
class User < ApplicationRecord
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
has_and_belongs_to_many :shortlists
...
class ShortlistsController < ApplicationController
def index
#shortlists = current_user.shortlists
...
Now I wish to allow users to "share" shortlists with other users, who will have read-only access to the shared shortlists.
I could add the second user to the shortlist (#shortlist.users << User.second) but this would give them full read-write access.
The cancancan gem looks promising, but I'm not sure how to configure the abilities, to allow User1 full control over their shortlists, and to give User2 read-only access to shortlists that User1 shares with them.
How can I restrict the second user to having read-only access?
Your solution in adding a owner_id column to the shortlists table is actually a good one as it lets you efficiently eager load the association and is a lot less cumbersome to deal with.
If you wanted to keep track of the user which creates the record too through a join table thats possible through a roles system but it would be a lot more clunky. The Rolify gem is one such as example of a generic roles system.
One thing you should do is use has_many through: instead of the near useless has_and_belongs_to_many:
class Shortlist < ApplicationRecord
belongs_to :owner,
class_name: 'User'
has_many :shares
end
# rails g model user:belongs_to shortlist:belongs_to
class Share < ApplicationRecord
belongs_to :user
belongs_to :shortlist
end
class User < ApplicationRecord
has_many :shortlists,
foreign_key: :owner_id
has_many :shares
has_many :lists_shared_with_me,
through: :shares,
source: :shortlists
end
This lets you add additional columns to the table like for example flags that will let you grant other users the rights to edit the shortlist or an automatic expiry date. You could also generate unique URLs for each share to keep track of which "share" is being used. None of that is possible with HABTM.
As a workaround I added an owner flag on the shortlists, which is calculated to current_user.id when the shortlist is created. This allows me to distinguish between the owner and viewers.
Before saving a shortlist I check whether #shortlist.owner == current_user.id and show an error message if not.
This works well enough but I'd be keen to hear if there's a method that's less hacky.

Rails model associations and destroy method

I need some help to solve my problem.
I'm working on a project where I develop the manager's functions of the application. My problem is related with two Destroy functions and how they related with themselves:
Simplifying, I have 3 models (Test, Application and JoinTestJob) described below in code sample.
I want the manager be able to destroy a JoinTestJob's object only if this is related with any Application's objects. I need consistency in Application's objects. That is the reason I created a before_destroy method in JoinTestJob model. Until here is working fine.
I also want the manager be able to destroy a Test's object, and with that, the associated objects should be destroy too, as declared in Test model. So here start the problem:
When I delete a Test's object I can see in log that all Application's objects associated are deleted first. After that, should be JoinTestJob's objects to be deleted, but I got a rollback. I know the reason of the rollback is the before_destroy method in JoinTestJob (I can see it in log). Apparently, this method still can find Application's objects, even though I saw in log that they were deleted. In this case, it seems they really would be deleted after the transaction is done.
So, how can I accomplish to having those two features working?
Model:
class Test < ApplicationRecord
has_many :applications, :dependent => :destroy
has_many :persons, through: :applications
has_many :join_test_jobs, :dependent => :destroy
has_many :jobs, through: :join_test_jobs
class Application < ApplicationRecord
belongs_to :join_test_job
belongs_to :person
class JoinTestJob < ApplicationRecord
belongs_to :test
belongs_to :job
before_destroy :check_relation_between_application_and_join_test_job
def check_relation_between_application_and_join_test_job
if Application.find_by(join_test_job_id: "#{self.id}")
self.errors.add(:base, "It's not possible to delete this item, because there are some applications related with. Please, delete them first.")
throw(:abort)
end
end
Edit 1:
About the log requested in comments, it's really basic. I will ask to you guys ignore some details in the image. I had to translate the problem to English, so there are some Portuguese words in the image. What is important to observe in the log:
All Application's objects related to Test are deleted first. Then, when rails started to look up for JoinTestJob's objects you can see the rollback. Before the rollback, there is a last request from Application trigged by the before_destroy method, as you can see in Log's image. The throw(:abort) method is called in this last request.
Word's Translation:
Application/applications => Inscricao/inscricaos
JoinTestJob/join_test_jobs => JoinConcursoCargo/join_concurso_cargos
To clarify things, DadosPessoalCandidato and DadosPortadorDeficiencia are models associated with Application and they hold personal informations about who is applying for, like address and phone. They can be ignored.
Edit 2:
Adding more information about what represent the models, so you guys can understand the problem better.
Test model: It's like a Civil service exam, where people would apply for getting a job. Who gets the best score in the exam would be selected for the job. This model hold information as: when this exam will happen, documents related with the exam's rules, when the result of the exam will be release and etc.
JoinTestJob model: It joins the concept of a Job and the Test that are offering these jobs. Here, we could find information as: specificity of the job, like the period of work, how much hours of work per day, salary and etc.
Application model: This model hold information related with the person who is applying for the job and the Test that are offering the job. So, we could find here information as: what job the person apply for, how much he/she payed for applying, the date of the Test (exam) will be happening, personal informations as age, name, address. A person could apply for more than one job for the same Test.
I hope I can help you guys with these new informations.
I think you should change your models:
class Test < ApplicationRecord
has_many :applications, :dependent => :destroy
class Application < ApplicationRecord
has_one :join_test_job, dependent: :destroy
class JoinTestJob < ApplicationRecord
belongs_to :application
validates :application, presence: true
so, if a Test is destroyed, then applications relates with destroyed test will be destroyed, and then JoinTestJob relates with destroyed applications are also destroyed.
I assume JoinTestJob model is stored in join_test_jobs model and have application_id.
You can make field store application_id is NOT NULL at Database level, and add validates :application, presence: true code make sure that a JoinTestJob always has one related application.

Rails_Admin Multiple 'User' Models + Devise

So I have installed Devise and rails_admin in my current setup. I'm kinda trying out rails right now for administration scenarios.
Since I figured many administration required multiple 'user' models, I ran into trouble in figuring out the right way to design and arrange their relationships.
So right now I have a User (devise) model.
For the User Models, I decided to separate the models I need (Admin, Student(example), Professor(example)).
I read around and found out that STI seems to be the solution here, so I set them up to be
class User < ActiveRecord::Base
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable
end
class Student < User
end
class Professor < User
end
In rails_admin, User CRUD is basically done, and using this setup I managed to find the configuration I want. Creating a Student, for example, will be recognized as a user. However, what I'm having problems is now on whether I have the correct setup or not since I might actually need a Student to have their own attributes (like student_id or major? just throwing things here). Using the previous setup I mentioned will only enable me to create them using the User model attributes, not the Student itself.
I also read from somewhere that I need to have a column 'type' in User that can be filled with either Student or Professor, but I'm having trouble in combining all of this solutions.
Any pointers/suggestions on how I should proceed now? Much appreciated.
If you decide to use Single Table Inheritance:
the type attribute is used by Rails to reach the appropriate model and its done automatically for you. i.e. when you do a Student.new, the type attribute is set to "Student"
the attributes of all the inherited classes ( Student, Professor, etc) are all stored in the users table. This means that both Student and Professor will have major, fees_schedule, etc (which are not normally applicable to professors).
Here's the documentation about Single Table Inheritance
On the other hand, you might want to consider Polymorphic Associations where each table is separate and associated along the lines of:
class User < ActiveRecord::Base
belongs_to :member, polymorphic: true
...
end
class StudentMember < ActiveRecord::Base
has_one :user, as: :member
...
end
class ProfessorMember < ActiveRecord::Base
has_one :user, as: :member
...
end
Read more about Polymorphic Associations here
Polymorphic Associations seems more appropriate in your case as there are probably many different attributes for students, professors, admin staff, etc, and it will look pretty messy if you dump all of them into the users table.

Is it possible to have multiple has_many associations with one specific model in Rails?

I have two models with the following associations:
organization.rb
class Organization < ActiveRecord::Base
has_one :user, as: :identifiable
has_many :speakers
#has_many :cast_items
end
speaker.rb
class Speaker < ActiveRecord::Base
has_one :user, as: :identifiable
#has_many :cast_items
end
As you can see, I've commented out an association with the CastItem model.
I want a Speaker to add multiple CastItems. Also, an Organization must be able to add multiple CastItems. When an Organization adds a CastItem, it does not necessarily belongs to a Speaker who is associated with an Organization. In other words an organization must be able to add a CastItem to itself or to a Speaker who is associated with him.
Will it be completely valid to put the has_many :cast_items in both models, or are there more practical design options?
Yes, you can do that. Remember to add organization_id and speaker_id to your cast_items model.
You can check out this link, http://guides.rubyonrails.org/association_basics.html , some useful information regarding many to many and one to many associations.
Personally, in your case, I will use has_many :through
You can definitely do that. I can't think of any reason that would be bad and it's often necessary.
You may want to look up the 'delegate' method for when you're creating CastItems, and have them always created by Organizations.
Also, make sure that if you have a :speaker_id on your CastItem that it can accept nil or false.

Is the has_may relationship on rails necessary or the belong_to is enough?

I have a model with a var reference to another one.
User -> Profile
When I have generated the Profile model I used the references
feature so it has generated the corresponding migration
....
t.references :user
....
My question is do I have to add a relationship on the User model too?
has_one :Profile
Yes, you need both code in two models and the migration you mentioned.
class User < AR
has_one :profile
end
class Profile < AR
belongs_to :user
end
has_one and belongs_to are just methods which adds some more methods to your model. This means, you can have belongs_to defined on one model and no has_one on the other. The only problem is that you would be able to call profile.user, but no user.profile.
It is absolutely up to you which methods you want to be defined and which you don't need. If you never ever want anyone to call profile.user, but want user.profile just call has_one :profile. In general those method shares nothing except that their using same foreign key column.
It is however worth mentioning, that this is usually advised to declare reverse association - it is not needed for things to work though.

Resources