Rails_Admin Multiple 'User' Models + Devise - ruby-on-rails

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.

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.

How to find out all the association of a model?

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).

Should I mock AR associations?

I'm trying to test my Order AR model in "isolation", but because it lives into a complex "context", I need to create a lot of associated AR models before.
My models are:
class Order < ApplicationRecord
belongs_to :registration #it's required!!!
end
class Registration < ApplicationRecord
belongs_to :event #it's required!!!
end
class Event < ApplicationRecord
has_many :registrations
belongs_to :account #An account belongs_to an organization
#This model a couple of required associations also
end
Is some way to "mock" the associations of my Order model??? Should I?
Thanks in advance!
I think, that with proper factory_girl setup, there will be absolutely no issues to easily setup context, that you need. During my experience I haven't faced any cases when associations were stubbed.
From my point of view, if you have some complex method inside your Order model, then you should move it into separate entity (service object, separate library, decorator). Then you can easily test this logic, without any need to setup associations, objects, etc.
I don't remember the last time I really needed to test my modal in isolation after I start making correct design of my application. I suspect, that your issue is also like a bell for you about your design. I treat models only as a set of validations(tested inside rails), associations(tested inside rails) and trivial methods, so usually there is no need to test your models

Do I have to include user_id on every table I wanna access with Devise gem?(Rails)

I'm using Devise gem on rails for authentication and I have everything set up already and working. What I did not understand from the documentation is if I needed to add user_id field to every table using this gem. I have:
users table
candidates table
activities_candidates table
candidates_languages table
languages table
and some others like this
In which tables I need to include a user_id field???. Cause until now I was using the candidate_id as a foreign key for the tables I needed to have a relation, should I change it now to use the user_id instead of the candidate_id?
You don't need to change it, just set up devise to work with the candidate model, if that's what you want. You can use whatever name for the user account you like. Check out the devise documentation.
But, if you're new to Rails, using user all over is a common way to go.
No, you don't need to include user_id on each table.
user_id is a foreign key -- if you included it in all your other tables, it suggests an antipattern (your User model shouldn't exist).
The right way to fix it is to use Devise on your Candidate model:
Devise
Devise supports using any model you want:
#config/routes.rb
devise_for :candidates
#app/models/candidate.rb
class Candidate < ActiveRecord::Base
devise :invitable, :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable, :invitable
#Associations
has_and_belongs_to_many :languages
...
end
You'll have to change your candidates table to include the devise functionality (you can just rename your users table to candidates & drop the current table, or copy the column structure with a migration).
--
Foreign Keys
The other way to resolve it is to change the foreign_key switch for your associations (on the User model):
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :languages, join_table: :candidate_languages, foreign_key: :candidate_id
has_and_belongs_to_many :activities, join_table: :activities_candidates, foreign_key: :candidate_id
end
You'd basically have to add all the associations for the Candidate model, and define their foreign keys explicitly. And, yes, it will be as much work as it sounds.
As can be seen by the above code, it's glaringly obvious you should just replace the User model with Candidate.

Adding A LOT Attributes to Rails Devise

I am implementing a login system, which require to collect a lot of user data, for example:
college, course, graduate year, start year, hobby, .... about 20-30 of them.
Is it wise to put them all into Devise? Or create another Model to handle that?
Its not good idea to put so much of data in devise model. Devise model record is always fetched from database for every request.(You can see it from logs)
You can add it in another model and add association.
e.g. you can add profile model
Assuming you have User model as devise model.
You have to take care of creating profile record after either User creation or User logs in first time or as per your requirement.
class User < ActiveRecord::Base
has_one :profile, :dependent => :destroy
end
class Profile < ActiveRecord::Base
belongs_to :user
end
You can create a user_info model with this association, in user.rb
has_one :user_info
On, sign_in it should create an instance of user_info if its not present in the databse
This approach would be better if you want to add 20-30 columns

Resources