User roles with devise Modular Rails 4 - ruby-on-rails

I've been struggling with this issue for weeks. My goal is to create 3 different types of users. Regular, Page , Venue. Upon registration the user is able to select that role [Regular,Page or Venue]. depending on the role the user chooses they're redirected to a edit profile page where they have to fill in additional information. Depending on user role, the user is also provided with a specific layout and separate root.
Example : User chooses role via registration form once signed up the user is redirected to their edit form to fill in additional info like name, location. ( also trying to find a way to make that required, like before a user can interact with the platform they have to fill in additional information that was not required on the registration form. Any ideas on something for that would be amazing.)
so basically I would like to root the user by role to a specific view.
Also my application is being built within engines, using the modular wa
What I have now :
By following this tutorial I was able to set up the user roles very simply. and also wrapping my head around using cancan.
http://www.mashpy.me/rails/role-based-registration-with-devise-and-cancan-using-ruby-on-rails/.
User.rb
module M0ve
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :user_name, presence: true, length: { minimum: 4, maximum: 12 }
validates :email, presence: true
validates :role, presence: true
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
ROLES = %w[regular page venue]
def role_symbols
[role.to_sym]
end
Application Controller
module M0ve
class ApplicationController < ActionController::Base
before_filter :authenticate_user!
protect_from_forgery with: :exception
protected
def after_sign_up_path_for(resource)
if current_user.role? :regular
regular_index
end
end
end
end
Registration Controller
module M0ve
class M0ve::RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:user).permit(:email, :user_name, :password, :password_confirmation, :role)
end
def account_update_params
params.require(:user).permit(:email, :user_name, :password, :password_confirmation, :current_password)
end
end
end
Routes
M0ve::Core::Engine.routes.draw do
devise_for :users, class_name: "M0ve::User",module: :devise,:controllers => { registrations: 'm0ve/registrations' }
resources :posts do
resources :comments
end
end
Sorry if I missed some information please let me know where I need to clarify and I'll appreciate any help .

You could use the Rolify gem with Devise and Cancan or Cancancan to manage multiple role based authorization.
With some effort you should be able to create and manage roles as well.

Use Cancan Gem
here is the link to it, the documentation is quite simple
https://github.com/ryanb/cancan

Related

Using the same email for different accounts on a multi-tenant app with Devise

TL;DR: in a multi-tenant app with Devise, a user should be able to register with the same email on different tenants/accounts. How can I make Devise use the account_id in User.find_for_database_authentication?
I have a multi-tenant app. Each subdomain belongs to an account, with many users and admins (different tables, no inheritance between them).
class User < ApplicationRecord
belongs_to :account
devise :database_authenticatable, :confirmable, :recoverable, :rememberable
validates :email, uniqueness: true
end
class AdminUser < ApplicationRecord
belongs_to :account
devise :database_authenticatable, :confirmable, :recoverable, :rememberable
end
# config/routes.rb
scope module: 'auth' do
devise_for :users, path: ''
devise_for :admins, path: 'admin', class_name: 'AdminUser', ...
end
# /sign_in for users, /admin/sign_in for admins
# (you can log as both at the same time)
Thinks worked well, but I need to allow a user to sign in with the same email on different tenants/accounts.
First, I fixed the validation:
class User < ApplicationRecord
validates :email, uniqueness: { scope: :account_id }
end
The problem is that when a user sign up / log in, Devise will search for the first user with some email, regardless of the account_id, so if I share the same email address on subdomain1 and subdomain2, when I log into subdomain2, I get the user info from subdomain1 (which is wrong)
Devise documentation recommends to config request_keys and redefine User.find_for_database_authentication
# config/initializers/devise.rb
config.request_keys = [:subdomain]
# app/models/user.rb
def self.find_for_database_authentication warden_conditions
joins(:account).where(
email: warden_conditions[:email],
accounts: {subdomain: warden_conditions[:subdomain]}
).first
end
This kind of works, but I found two problems/disadvantages:
When I log out with the user, it does the same with the admin (kind of annoying for development)
I'd like to use account_id on User.find_for_database_authentication, and avoid the JOIN.
I don't know how to do it, as subdomain is handled automagicaly by Devise/Warden. warden_conditions keys should be email and account_id.
According to this Wiki
If you are using column name other than subdomain to scope login to subdomain, you may have to use authentication_keys. For example, if you have subdomains table and are using subdomain_id on your Devise model to scope User, you will have to add authentication_keys: [:email, :subdomain_id]
So I think you should
devise :database_authenticatable, :confirmable, :recoverable, :rememberable, authentication_keys[:account_id]
In addition, I think you should redefine find_first_by_auth_conditions instead of find_for_database_authentication in order to support password recovery

Can't add Company (from separate model) to Devise User

First-time poster, many-time finder-of-answers on the site (thank you!). I'm using Rails 5.2.3, ruby-2.6.2 and Devise gem 4.6.2. I have not been able to get an answer to work, even though there are plenty somewhat related questions here, here, here and here.
When a new User signs up, I want them to select their Company from a dropdown list (already created) in the sign-up form. (Eventually, this will be an admin role, but that's beyond the scope of this question.)
I created a registrations controller and added code per a number of the previous posts. Update, I was not extending Devise as I should have as indicated here: Extending Devise Registration Controller. This is my new Registrations controller.
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
def new
#companies = Company.all
super
end
def create
#companies = Company.all
super
end
protected
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:company_id])
end
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:company_id])
end
end
And created new files in views/registrations with new.html.erb and edit.html.erb that I copied the exact code from the devise/registrations views.
I updated my routes.rb file to include:
devise_for :users, :controllers => { registrations: 'users/registrations', sessions: 'users/sessions' }
My User model is:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
belongs_to :company
accepts_nested_attributes_for :company
end
My Company model is:
class Company < ApplicationRecord
has_many :users
end
In the new user registration form, this works to provide the dropdown, but when I try to create the new user, it says: 1 error prohibited this user from being saved: Company must exist.
<%= f.collection_select :company, #companies, :id, :name, prompt: true %>
I thought this post would have the answer, but that appears to use Rails 3 and attr_accessible, which was deprecated in Rails 4.
I don't really understand what accept_nested_attributes_for :company does. The only thing in the Company model is the name.
Thank you in advance!
Welcome to StackOverflow.
In order to add more parameters to devise's sign up form, you'll need to sanitize the corresponding parameters using devise's sanitizer.
You should do that like this:
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:company_id])
end
end
You can find more information about parameter sanitizing and adding custom fields in this section of devise's readme
If you also want to add a select field including all the existing companies, you should add a collection select:
<%= f.collection_select :company_id, Company.all, :id, :name %>
Got it!
To extend the Devise controller, follow the help here: Extending Devise Registration Controller
The User models must also be updated to include the optional: true because here https://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
belongs_to :company, optional: true
accepts_nested_attributes_for :company
end

Rails - Devise - Add profile information to separate table

I am using Devise to build a registration/authentication system into my application.
Having looked at quite a few resources for adding information to the devise model (e.g. username, biography, avatar URL, et cetera..) [resources include Jaco Pretorius' website, this (badly formed) SO question, and this SO question.
That's all fine and well -- it works. But my problem is that it's saving to the User model, which, according to database normalizations (also referencing this SO question), it should in fact be saving to a sub-model of User which is connected via has_one and belongs_to.
Thus far, I have created a User model via Devise. I have also created a UserProfile model via the rails generate script.
user.rb (for reference)
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable
has_one :user_profile, dependent: :destroy
end
user_profile.rb
class UserProfile < ActiveRecord::Base
belongs_to :user
end
timestamp_create_user_profiles.rb
class CreateUserProfiles < ActiveRecord::Migration
def change
create_table :user_profiles do |t|
t.string :username, null: false
t.string :biography, default: ""
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
add_index :user_profiles, [:user_id, :username]
end
end
My question, now, is, how does one collect the information for both of these models and ensure, via the devise registration form, that it all ends up in the right places?
I've seen resources about creating state machines (AASM, and the answer to this SO question. I've also seen information about creating a wizard with WICKED, and an article on the same topic.
These all seem too complicated for my use-case. Is there some way to simply separate the inputs with devise and make sure the end up in the right place?
I think, instead of simply commenting on an answer that led me to the final answer, I'll archive the answer here in case someone in the future is trying to also find this answer:
I will be assuming that you have some sort of setup as I do above.
First step is you need to modify your User controller to accept_nested_attributes_for the profile reference as well as add a utility method to the model so when requested in code, the application can either retrieve the built profile model or build one.
The user model ends up looking like so:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable
has_one :user_profile, dependent: :destroy
accepts_nested_attributes_for :user_profile
def user_profile
super || build_user_profile
end
end
Secondly, you will need to modify your sign up/account_update form to be able to pass the attributes for this secondary model into the controller and eventually to be able to build the profile for the parent model.
You can do this by using f.fields_for.
Add something like this to your form:
<%= f.fields_for :user_profile do |user_profile_form| %>
<%= user_profile_form.text_field :attribute %>
<% end %>
An example of this in my specific case is:
<%= f.fields_for :user_profile do |user_profile_form| %>
<div class="form-group">
<%= user_profile_form.text_field :username, class: "form-control", placeholder: "Username" %>
</div>
<% end %>
Finally, you will need to tell Devise that it should accept this new hash of arguments and pass it to the model.
If you have created your own RegistrationsController and extended Devise's, it should look similar to this:
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:user).permit(:email, :password, user_profile_attributes: :username)
end
end
(Of course, make the proper changes for your specific use-case.)
If you have simply added the Devise sanitization methods to your application controller, it should look similar to this:
class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) {|u|
u.permit(:email, :password, user_profile_attributes: :username)}
end
end
(Again, make the proper changes for your specific use-case.)
A small note on user_profile_attributes: :username:
Note this is a hash, of course. If you have more than one attribute you are passing in, say, as an account_update (hint hint), you will need to pass them like so user_profile_attributes: [:attribute_1, :attribute_2, :attribute_3].
Please check out the RailsCasts.com web-site.
There are a couple of interesting railscasts about nested model forms:
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
http://railscasts.com/episodes/196-nested-model-form-revised
Also check out accepts_nested_attributes_for
Or check out this question:
Profile model for Devise users?
Also note that for Devise 4.2 the '.for' method for the devise_parameter_sanitizer is deprecated in favor of '.permit'
From the documentation:
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_in) do |user_params|
user_params.permit(:username, :email)
end
end

Devise on ginjo-rfm

I'm using the ginjo-RFM gem for connecting to a Filemaker Database, and want to use Devise for authentication. The problem with this is the fact that Devise requires my User model to inherit from ActiveRecord::Base - and RFM requires, for it to access the database at all, to inherit from Rfm::Base. Is it possible to let my User class inherit from both Rfm::Base and ActiveRecord::Base?
User.rb (model)
class User < Rfm::Base
require 'rfm'
rolify
config :layout => 'XXXXXX' #Connecting Rails to the FileMaker layout
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :role_ids, :as => :admin
attr_accessible :name, :email, :password, :password_confirmation, :remember_me
end
This will fail 'cause devise relies on ActiveRecord::Base and vice versa. Any ideas?
Ginjo-rfm: http://rubygems.org/gems/ginjo-rfm
Devise: https://github.com/plataformatec/devise
Devise actually relies on orm_adapter rather than directly on ActiveRecord. (Take a look here.) So in theory, it would be trivial to use Devise with any class that has an OrmAdapter implemented.
Since ginjo-rfm implements ActiveModel, it probably wouldn't be too difficult to write an OrmAdapter for it. (Look at the ActiveRecord OrmAdapter - it almost fits on one screen.)
To summarize my approach:
1.Write an OrmAdapter for ginjo-rfm (and hopefully open-source it as a gem!).
2.Then add the following to your application (or release it as a gem so others can benefit):
require 'orm_adapter/adapters/rfm'
Rfm::Base.extend Devise::Models
Your best bet might be to create a new class that you can delegate the filemaker calls to.
The User class is how they auth to your website, and you manage roles.
class User < ActiveRecord::Base
rolify
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :role_ids, :as => :admin
attr_accessible :name, :email, :password, :password_confirmation, :remember_me
end
The FileMakerUser class is for accessing Filemaker
class FileMakerUser < Rfm::Base
require 'rfm'
config :layout => 'XXXXXX' #Connecting Rails to the FileMaker layout
# other filemaker stuff
end
You could use a file_maker_user? column in user to signify that they have a filemaker account. (if that made sense, like if not all users of your web app have to have filemaker accounts in order to use the system)

Rails : Adding an admin role using devise who can see all the users

I need to create an admin role using devise for my app. I've created basic authentication using devise . I have a devise user model in my app but now i need an admin who can show edit and destroy all the users. I tried following the tutorials but none of them helped.
I am using rails 3.0.10 and ruby 1.9.2-p290.
You just define role.rb first by creating migratioin
rails g model role name:string
then in role.rb
class Role
has_one:user
end
And in user model
class user
belongs_to :role
end
Insert two roles into DB
1.admin
2.user
Then check by this
if user.role.name == "admin"
# can do all your logic
else
your logic
end
Make sure insert role_id:integer into user model
Try it.......
I have similar requirement as yours and also don't want any user to be able to signup. All that will be taken care by Admin. Her what I've done.
I added another devise model called Admin
rails generate devise MODEL
Disable 'Registerable' for User model so that user cannot singup by themself
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :registerable, :timeoutable and :omniauthable
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :role, :admin
# attr_accessible :title, :body
end
Enable CRUD for user by using sample from here: https://gist.github.com/1056194
Finally protect protect the users controller like so
users_controller.rb
# Add this
before_filter :authenticate_admin!
Hope this helps.

Resources