Rails 3 - Devise Gem - How to Manage Users through CRUD interface - ruby-on-rails

Devise gem is used for authentication. I need to have a user index and show view. Found this how to at the devise wiki, however played around, can not make it work.
Devise - How To: Manage users through a CRUD interface
Method 1: Remember to remove the :registerable module from the Devise model (in my case it was the User model) Make sure to put your map.resources :users below the map.devise_for :users route (devise_for :users and resources :users for Rails 3).
Method 2: devise_for :users, :path_prefix => ā€˜dā€™ resources :users to isolate the devise logic from your crud user controlle
My question:
1. if you remove registerable in the model, then how does devise work for user?
2. If you done this, can you provide a sample?
Thanks in advance

The following worked for me.
users_controller.rb
class UsersController < ApplicationController
...
def create
#user = User.new(params[:user])
if params[:user][:password].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end
respond_to do |format|
if #user.save
format.html { redirect_to users_path, notice: 'User was successfully created.' }
else
format.html { render action: "new" }
end
end
end
...
end
routes.rb
...
devise_for :users, :path_prefix => 'd'
resources :users
...
I hope this can help you.
Regards,
Giacomo

By placing the appropriate new.html.erb, edit.html.erb, _form.html.erb etc files in the User Views folder, adding/editing/deleting Users should work no differently as any other CRUD, as Devise's User is another model like any. I won't post the new or edit erbs (simple enough, and a scaffold can show you the rest), but an example of my User _form.html.erb...
<%= form_for(#user) do |f| %>
<%= render :partial => 'shared/errors', :locals => { :content => #user } %>
<div class="field">
<%= f.label :email %><br />
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %>
</div>
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In mine, I think I left :registerable in and I'm still able to administer and update users via the CRUD. If you leave it out, then users can't register themselves, but you can still add and edit users yourself (would protect the Users controller with load_and_authorize_resource to keep regular users out, of course).

Related

Create Devise User from Admin Namespace in Rails 5.1

I have an admin namespaced route for a custom dashboard in my app. I have at least 8 models working, with error free crud operations--all except one. I am using the Devise Gem for user management and a User model. I have the User model in the admin namespace and the only operation I can get to work is changing role and destroy, but I can't create a new user from the dashboard. When I try to create a new user; I get the error "You are already signed in.".
controllers/admin/users_controller.rb
class Admin::UsersController < ApplicationController
before_action :authenticate_user!
before_action :admin_only, :except => :show
def index
#users = User.all
end
def show
#user = User.find(params[:id])
unless current_user.admin?
unless #user == current_user
redirect_to root_path, :alert => 'Access denied.'
end
end
end
def create
#user = User.new
end
def update
#user = User.find(params[:id])
if #user.update_attributes(secure_params)
redirect_to users_path, :notice => 'User updated.'
else
redirect_to admin_users_path, :alert => 'Unable to update user.'
end
end
def destroy
#user = User.find(params[:id])
user.destroy
redirect_to admin_users_path, :notice => 'User deleted.'
end
private
def admin_only
unless current_user.admin?
redirect_to root_path, :alert => 'Access denied.'
end
end
def secure_params
params.require(:user).permit!
end
end
models/user.rb
class User < ApplicationRecord
enum role: [:user, :admin]
def set_default_role
self.role ||= :user
end
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
views/admin/users/new.html.erb
<%= form_for User.new do |f| %>
<div class="form-group">
<%= f.label :first_name %><br/>
<%= f.text_field :first_name, autofocus: true %>
</div>
<div class="form-group">
<%= f.label :last_name %><br/>
<%= f.text_field :last_name %>
</div>
<div class="form-group">
<%= f.label :email %><br/>
<%= f.email_field :email %>
</div>
<div class="form-group">
<%= f.label :password %>
<% if #minimum_password_length %>
<em>(<%= #minimum_password_length %> characters minimum)</em>
<% end %><br/>
<%= f.password_field :password, autocomplete: "off" %>
</div>
<div class="from-group">
<%= f.label :password_confirmation %><br/>
<%= f.password_field :password_confirmation, autocomplete: "off" %>
</div>
<div class="form-group" style="margin-top: 1em">
<%= f.submit 'Sign up', class: 'btn btn--primary type--uppercase inner-link' %>
</div>
<% end %>
views/admin/users/new.html.erb (other method)
<%= form_for([:admin, #user]) do |f| %>
This is how all my name spaced forms are setup but this gives me the error First argument in form cannot contain nil or be empty
routes.rb
...
namespace :admin do
get '', to: 'dashboard#index', as: '/'
resources :pages
resources :articles
resources :categories
resources :tags
devise_for :users
resources :users
resources :albums
resources :banners
resources :products do
resources :variations
end
end
...
using the registration#create action is the appropriate architecture for this?
Because that action is built on a different concept, that the user is not logged in and there are a series of checks for that.
views/admin/users/new.html.erb
you are using registration#new controller action, which does not have the #admin valorized.
def new
#admin = Admin.find(params[:admin_id])
....
end
That is why you get the error, also this approach is not correct.
User will need to register with the traditional devise router, then you will also have to create a nested router for admins to create new users.
this is your link to the admin user registration#new
Your current view/controller#action has the #admin = Admin.find(params[:id]) valorized and the view has the following link
link_to new_admin_user_path(:admin, :user)
the new_admin_user_path is for url /admin/:admin_id/users/sign_up(.:format) that you define in your nested routes.
Step 2) decide which controller#action to use
Do you want to use the standard users/registrations#create or use a new action for this?
I believe you should enhance users/registrations#create by generating the controllers actions in your app as described in devise guide
then in devise controller registrations
def new
#admin = Admin.new(:params[:admin_id]) if params[:admin_id].present?
end
your registrations#new and #create will still trigger errors, you will have to read how devise creates this users by reading their amazing sourcecode and rdoc documentation, modify the process accordingly so that admins can create users by using that action otherwise the less DRY alternative is creating a new controller#action and using it to call an existing User method or a method you will create in the User model to create users. In the end it a User is just an entry in your users database table. Just creating in a similar fashion to other users. As the admin is creating the temporary password, encryption/security issues are not anymore that important. The User will have to change the password anyway.

No route matches {:action=>"partner_update", :controller=>"users"}

i'm new in rails so i tried to do this to add a role, making a form to update and if the user update succsessfully add the role.
note: i'm using devise and rolify with cancancan
routes
get 'users/becomepartner' => 'users#becomepartner' do
collection do
patch 'update_partner'
end
end
and this is my users controller
def becomepartner
#user = current_user
end
def update_partner
#user = User.find(current_user.id)
if #user.update_with_password(partner_params)
self.add_role(:partner)
# Sign in the user by passing validation in case their password changed
bypass_sign_in(#user)
redirect_to root_path, notice: 'now you can create'
else
render :becomepartner
end
end
private
def partner_params
# NOTE: Using `strong_parameters` gem
params.require(:user).permit(:name, :style, :current_password, :email)
end
and this is my view becomepartner.html
<div>
<%= form_for(#user, :url => { :action => "update_partner" } ) do |f| %>
<div class="field">
<%= f.label :style %>
<%= f.text_field :style %>
</div>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :email %>
<%= f.email_field :email %>
</div>
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i>
<%= f.password_field :current_password, autocomplete: "off" %>
</div>
<% end %>
</div>
In form action you have mentioned action as partner_update but you don't have that action. Change it to update_partner.
You are using the collection route for another individual route, that is get 'users/becomepartner'. So this will generate the collection route with url like:
users/becomepartner/update_partner
You can confirm this by running rake routes.
But your forms action url will be different.
What you can do is create this collection route inside users resource route like:
resources :users do
collection do
patch 'update_partner'
end
end
Then in form_for url option, use update_partner_users_path
If you don't want the users resources routes, then just define a single route like:
get 'users/becomepartner' => 'users#becomepartner'
patch 'user/update_partner' => 'users#update_partner'
I have not ran any of this code, so if there is any syntax error then post it in comment.
In routes.rb,change
patch 'update_partner' to update 'update_partner' and remove collection as you are not operating on a collection.you have added it in a do block,instead add a one liner route path.
in the form,url must be update_partner and NOT partner_update .

Ruby app using devise for authentication

I am building a simple app that allows members to create a trip. Only one trip. I wanted to get some practice using the devise gem so that setting up the application with sign in, sign out, etc is easy and efficient, but I need some help. Right now I have two models: member & trip, neither of which have a controller.
This is my sign up form and when I click submit, somehow a member is created in the database even without a member controller? How do I redirect this to a different page after submit is pressed?
I'm just a little confused on what I should be adding since devise is doing a lot of the work behind the scenes.
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "off" %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off" %></div>
<div><%= f.submit "Sign up" %></div>
<% end %>
You create a controller to do this.
class UserController < Application::Base
def new
#user = User.new
end
def create
#user = User.new(email: params[:email], password: params[:password], password_confirmation: params[:password_confirmation])
if #user.save
redirect_to <page you want to redirect to>
else
render new
end
end
The user(or in you case member) controller simply talking resides in devise gem internals.
To redirect a user to user/home you may define user_root route in your routes.rb file in this way:
get 'user/home', as: 'user_root'
Read more here.
Devise offers some macros that you can override to choose what paths you want to redirect to.
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
'/an/example/path'
end
end
Source
EDIT:
Create a file in your controller folder called registrations_controller.rb. Put this code into that file.
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
'/an/example/path'
end
end
Replace /an/example/path with whatever path you want to redirect to after someone signs up. Then modify your config/routes.rb to point to that controller by adding this line:
devise_for :members, :controllers => { :registrations => "registrations" }
Then, you may need to edit your config/application.rb by adding this line
config.paths['app/views'] << "app/views/devise"
If you encounter a "MissingTemplate" error.

how to call devise methods

i create controller:
class SigninController < ApplicationController
def index
end
def create
#user = User.find_by_email(params[:user][:email])
if #user
....
sign_in(:user, #user)
else
#user = User.new(params...)
#user.save
...
end
end
end
Correctly i call sign_in method?
signin/index.html.erb:
<h2>Sign in</h2>
<%= form_for(User.new, :as => :user, :url => "signin") do |f| %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
<br>
<div><%= f.submit "Sign in" %></div>
<% end %>
How to call devise sign_in and sign_up methods of devise?
Try this in app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
def after_create_path_for(resource)
sign_in(:user, resource)
end
end
That's just kind of a guess. I don't know if it will work.
You may but in most cases you should not create custom controller to sign in / out user. Devise has sessions_controller which implements this functionality for you. View devise documentation and wiki, it has plenty of documentation. Also you can found out there how to customise views and controller behaviour.
Update
If need to place both forms (login and registrations) on one view you can customise devise view. Form actions may point to different standard controllers. I suggest to use users/registrations/new form for base form and add uses/sessions/new form to it.

Redirecting when editing/updating a user (with error)

I want to update/edit a devise user from my own form in my project, but the problem is that I can't redirect on update on the "request.referer".
I've read that but it didn't work for me: https://github.com/plataformatec/devise/wiki/How-To:-Customize-the-redirect-after-a-user-edits-their-profile
...And the others wiki pages.
Okay, so my code is:
#/views/backend/perso.html.erb
<%= form_for(#user, :url => registration_path(#user), :html => { :method => :put }) do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(form.errors.count, "error") %> prohibited this data from being saved:</h2>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
# Other fields ...
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email %>
</div>
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
So the user is on that page, and updates his data. Problem, I get redirected to /users/
I've tried to add that to routes:
#config/routes.rb
devise_for :users do
get "users", :to => "backend#perso", :as => :user_root # Rails 3
end
Or even to the application controller:
#/controllers/application_controller.rb
private
def after_update_path_for(resource)
backend_perso_path
end
But still not working.
Thanks for anyone trying to help me out !
Edit
When the update generates no error, I get redirected to the page I want (by adding after_update_path_for in my application controller), but when errors exist, it displays /view/devise/registration/edit.html.erb
Update
Ok, so I overwrited the Devise controller following that:
https://github.com/plataformatec/devise/wiki/How-To%3a-Allow-users-to-edit-their-account-without-providing-a-password
So my code in registrations_controller looks like that
#controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def update
# Devise use update_with_password instead of update_attributes.
# This is the only change we make.
if resource.update_attributes(params[resource_name])
set_flash_message :notice, :updated
# Line below required if using Devise >= 1.2.0
sign_in resource_name, resource, :bypass => true
redirect_to after_update_path_for(resource)
else
clean_up_passwords(resource)
redirect_to backend_perso_path # That's the line I need to change
end
end
end
I can now redirect to the page I wanted, but I don't know how to show that errors happened !
simple answer via Rails Routing from the Outside In using the "not quite as elegant" hardcoded user_root as described in How To: Customize the redirect after a user edits their profile
#config/routes.rb
match 'user_root' => redirect("/wherever/you/want")
I didn't get any useful answer (thanks Laurent for your solution though!) so here's my code so far.
Do the job but isn't very clean.
class RegistrationsController < Devise::RegistrationsController
def update
# Devise use update_with_password instead of update_attributes.
# This is the only change we make.
if resource.update_attributes(params[resource_name])
set_flash_message :notice, :updated
# Line below required if using Devise >= 1.2.0
sign_in resource_name, resource, :bypass => true
redirect_to after_update_path_for(resource)
else
clean_up_passwords(resource)
redirect_to after_update_path_for(resource), :flash => { :alert => "Error message" }
end
end
private
# Redirect to the URL after modifying Devise resource (Here, our user)
def after_update_path_for(resource)
my_path
end
end
I don't use devise, but I've been encountering a similar problem on omniauth. OmniAuth has a referer or origin method, but it wasn't working for me for account creation. So what I did is a dirty hack storing the source page in a session variable, and checking it after the action was performed.

Resources