active_record-acts_as and devise integration - ruby-on-rails

i'm writing my first rails application using active_record-acts_as and devise. I have an User (devise authentication and actable) , Client (act_as) and Owner (act_as).I overrided the registration_controller to add some extra fields in the devise form, fields like name, surname etc. that i need for Client and Owner. I'm trying to sign up both Client and Owner using the devise authentication of the User but adding other fields. I thought to define the authentication on the User to avoid duplicates in the schema. Is it possible to do this without define the devise controller of both Client and Owner?
client.rb
class Client < ActiveRecord::Base
acts_as :user
end
owner.rb
class Owner < ActiveRecord::Base
acts_as :user
end
user.rb
class Utente < ActiveRecord::Base
actable
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
routes.rb
Rails.application.routes.draw do
devise_for :users, :controllers => { registrations: 'registrations' }
resources :clients
resources :users
resources :owners
end

I think what you should understand about the gem you are using is that, it buys you the idea of having empty columns. However it is necessary you note that, each details goes to its respective table.
Having said that, you can use rails authentication on any table where you hope to set authentication on.
Now based on the later part of your question, where you asked to avoid duplicates on your table, you set validation in your model, such that any of your model becomes:
class Utente < ActiveRecord::Base
actable
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :email, :uniqueness => true
end
You could see documentation of how you set validations on active_record-acts_as here
Since its MTI with Devise, respective data goes to where it's suppose to be right out of the box, then I will suggest you set validations on other tables that acts_as :user, but on attributes that are not on the actable devise table. For example:
class Client < ActiveRecord::Base
acts_as :user
validates :school, :uniqueness => true
end

Related

Why do I keep getting this ActiveRecord::AssociationTypeMismatch error?

I have two models that have a has_many association to the same object. I have a User, an Admin, and Visits.
Users has_many Visits
Admins has_many Visits
Every time I create a Visit with a User it works, but when I do it with an Admin it gives me an error:
ActiveRecord::AssociationTypeMismatch.
The full error is this:
User(#70282715553200) expected, got #<Admin id: 2, email: "admin#gmail.com", created_at: "2019-02-07 12:08:40", updated_at: "2019-02-07 12:08:40"> which is an instance of Admin(#70282709528720)
def create
#visit = #service.visits.new(visit_params)
if user_signed_in?
#visit.user = current_user
else
#visit.user = current_admin
end
if #visit.save
redirect_to service_visits_path(#service)
else
redirect_to #services
end
end
==============================
class Admin < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and
# :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :visits, dependent: :destroy
end
==============================
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and
# :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :visits, dependent: :destroy
end
==============================
class Visit < ApplicationRecord
belongs_to :user
belongs_to :admin
belongs_to :service
end
Unless you've got some kind of polymorphism going on, which isn't documented, I'd try and change this:
if user_signed_in?
#visit.user = current_user
else
#visit.user = current_admin
end
To this:
if user_signed_in?
#visit.user = current_user
else
#visit.admin = current_admin # this line
end
Your Visit model says a visit has both one User, and one Admin, so you have to assign the current_admin to the #visit.admin, not #visit.user.
If you're using Rails 5, you'll also need to update your model as below:
class Visit < ApplicationRecord
belongs_to :user, optional: true
belongs_to :admin, optional: true
belongs_to :service
end
As I note in my comment below, the suggestion from #bo-oz should be given considerable consideration. I haven't seen User and Admin tables typically split out as you've done in production applications. The concept of 'admin' is typically handled as a separate Role model (the rolify gem is good for this), or more simply as a boolean on the User model.
I think what you are trying to do us just plain wrong. An Admin is a User as well. You should remove the Admin model entirely. If you need to assign additional capabilities / rights to someone, you should either create an additional attribute (admin boolean yes/no), or create some kind of Role Based model. Have a look at Rolify gem.
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and
:omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :visits, dependent: :destroy
def is_admin?
// return true if user is admin
end
end
class Visit < ApplicationRecord
belongs_to :user
belongs_to :service
end
def create
#visit = #service.visits.new(visit_params)
if user_signed_in?
#visit.user = current_user
else
if #visit.save
redirect_to service_visits_path(#service)
else
redirect_to #services
end
One warning though.... the user is mandatory is this relationship, so this would break if someone is not logged in! Think about it... either don't create a Visit, or create an Anonynous visit.

Dynamic root for authenticated polymorphic attribute

I have three models as follows:
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :role, polymorphic: true
validates_presence_of :first_name, :last_name, :email, :password,
:password_confirmation
end
student.rb
class Student < ActiveRecord::Base
has_one :user, as: :role, dependent: :destroy
accepts_nested_attributes_for :user
end
teacher.rb
class Teacher < ActiveRecord::Base
has_one :user, as: :role, dependent: :destroy
accepts_nested_attributes_for :user
end
I have user registration and login working as expected. However, I cannot figure out how to direct users to the appropriate homepage when they're logged in.
I handle routing for authenticated users as follows:
authenticated :user do
root to: "students#home", as: :user_root
end
Ideally if current user's role attribute is a student, then it sets students#home as :user_root. If it's a teacher, then it sets teachers#home as :user_root. Is there any way to handle this purely in routes?
They way you've structured it seems confusing to me. How much do teachers and students really have in common? Won't students and teachers need different associations, fields, and everything?
Why not have completely separate Student and Teacher models with different devise scopes? See devise wiki configuring multiple models: https://github.com/plataformatec/devise#configuring-multiple-models
For shared functionality you can have both inherit from an abstract model or use concerns / modules for shared functionality.
module DeviseConcern
extend ActiveSupport::Concern
included do
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates_presence_of :first_name, :last_name, :email, :password,
:password_confirmation
end
end
While playing around with AJcodez's solution, some things like user authentication were less than pleasant to implement. I ended up opting for my original implementation.
One lesser known things about routes is that they can have lambas in them. Our routes ended up looking like the following:
authenticated :user, lambda { |u| u.role_type == "Student" } do
root to: "students#home", as: :student_root
end
authenticated :user, lambda { |u| u.role_type == "Teacher" } do
root to: "teachers#home", as: :teacher_root
end

Devise: Foreign key columns for roles in user_id

I used rails composer to create a starter app for my rails project. It is using devise to create and manage roles
I have following roles for my User: Recruiter, Applicant
A User can one or both of [Recruiter, Applicant]
I looked at the User model , but it doesnt have any foreign key role_id column. I added that column myself,and I am facing following issues
1] The app only assigns role_id=1 for every user I sign up
2] For user who is both a recruiter and an applicant, would there be 2 roles in User column with different Ids [1 and 2] , how would/should this model be handled.
This is my User model:
class User < ActiveRecord::Base
rolify
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :role_ids, :as => :admin
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :user_id, :role_ids
validates_presence_of :email
has_many :applications
has_many :jobs
end

Changing Model associations after running migration

I have a Devise User model with the following contents for which I did run migration.
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :token_authenticatable, :confirmable, :lockable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :role
# attr_accessible :title, :body
ROLES = ['admin', 'network/system admin', 'manager', 'programmer']
def role?(base_role)
ROLES.index(base_role.to_s) <= ROLES.index(role)
end
end
later on , I added the below two lines to the same model and run migration for Ticket, Projects and Assignments.
has_many :projects, :through => :assignments
has_many :tickets
Does the above update the association of user with the Tickets and Projects? Is there any problem in changing associations in model after running migration for the same? I want to know it as I am developing a Rails app now.
Thanks :)-
You should also have association...
has_many :assignments
in your user model.
no other updation is required.

Mass assignment problem when instantiating a model

I have a User model (Devise) and have created a Profile model with has_one / belongs_to relationship. I'm trying to automatically create a profile when the User is created as follows :
class User < ActiveRecord::Base
has_many :videos, :dependent => :destroy
has_one :profile, :dependent => :destroy
after_create :create_profile
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
protected
def create_profile
Profile.create :user_id => self.id
end
end
The profile gets created but the user id does not get populated. I get a Mass Assignment warning with regard to setting :user_id on the profile because I have attr_accessible exposing a few fields on the Profile.
I dont want to remove the attr_accessible but dont understand why setting one field is considered mass assignment. I figured this might be to do with passing a hash so Ive tried the following as a workaround :
#profile = Profile.create
#profile.user_id = self.id
This removes the warning but the user_id is still not getting set on the profile. What is the correct way to go about solving this ?
Any clarity much appreciated ! :)
Are you calling #profile.save at the end of your workaround?
Maybe you can try this:
def create_profile
self.build_profile.save
end

Resources