I want to create multi user application. Admin user can create new users. how can i do this using devise. Because when after login as admin user i want add new user devise show error that "you are already signed in". How i do this using devise.
I was able to create the Admin User and logged in
User-Controller
class UsersController < ApplicationController
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
end
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "Successfully created User."
redirect_to root_path
else
render :action => 'new'
end
end
def user_params
params.require(:user).permit(:email, :username, :password, :password_confirmation,:propic)
end
end
Admin Controller
class ClientsController < ApplicationController
skip_before_filter :authenticate_user!, only: [:index, :new, :create]
def new
#client = Client.new
#client.build_owner
render layout: 'sign'
end
def index
#clients = Client.all
render layout: 'welcome'
end
def create
#client = Client.new(client_params)
if #client.valid? then
Apartment::Tenant.create(#client.subdomain)
Apartment::Tenant.switch(#client.subdomain)
#client.save
redirect_to new_user_session_url(subdomain: #client.subdomain)
else
render action: 'new'
end
end
private
def client_params
params.require(:client).permit(:name, :subdomain, owner_attributes: [:email, :username, :password, :password_confirmation,:propic])
end
end
Application Controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :load_tenant
before_filter :authenticate_user!
#rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found
render 'record_not_found'
end
def load_tenant
Apartment::Tenant.switch(nil)
return unless request.subdomain.present?
client = Client.find_by(subdomain: request.subdomain)
if client then
Apartment::Tenant.switch(request.subdomain)
else
redirect_to root_url(subdomain: false)
end
end
def after_signout_path_for(resource_or_scope)
new_user_session_path
end
end
Anyone? I am super new to Ruby on Rails. All the code is the result of hefty trial and errors.
Related
I got this error today when I tried to use some helper methods for the users controller:
AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and
at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need
to do something like "redirect_to(...) and return".)
I put this following helpers in application_controller.rb :
class ApplicationController < ActionController::Base
def current_user
User.find_by :id=>session[:user_id]
end
def log_in?
!!session[:user_id]
end
def log_in_first
if !log_in?
session[:error]="You have to log in first to continue your operation"
redirect_to("/login") and return
end
end
def correct_user?
if !(current_user.id.to_s==params[:id])
session[:error]="You have no right to do this operation."
redirect_to "/"
return
end
end
end
and here is the user_controller.rb:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id]=#user.id
redirect_to user_path(#user)
else
render 'new'
end
end
def show
log_in_first
#user = User.find_by id: params[:id]
correct_user?
if #user
render 'show'
else
redirect_to '/login'
end
end
private
def user_params
params.require(:user).permit(:name,:password,:email,:email_confirmation)
end
end
As you can see I tried to use both return and and return in log_in_first and correct_user?to fix the problem but it still doesn't work. Does anyone have any ideas?
The problem is in the show action, log_in_first redirects then the show action does whatever it wants, which is redirect or render. This is causing the error.
A better solution is to use before_action for your authentication and authorization and just let the user controller actions do their thing. Something like the below.
class ApplicationController < ActionController::Base
def current_user
User.find_by :id=>session[:user_id]
end
def log_in?
!!session[:user_id]
end
def authenticate_user!
if !log_in?
session[:error]="You have to log in first to continue your operation"
redirect_to("/login")
end
end
def authorize_user!
unless current_user&.id.to_s==params[:id]
session[:error]="You have no right to do this operation."
redirect_to "/"
end
end
end
class UsersController < ApplicationController
before_action :authenticate_user!, only: [:show]
before_action :authorize_user!, only: [:show]
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id]=#user.id
redirect_to user_path(#user)
else
render 'new'
end
end
def show
#user = User.find_by id: params[:id]
render 'show'
end
private
def user_params
params.require(:user).permit(:name,:password,:email,:email_confirmation)
end
end
In my app, when a user logins he/she is redirected to the users profile page. Say he/she is redirected to http://localhost:3000/users/1
If he/she replaces 1 with any other number I want them to redirect to there
current profile no matter if users exits in the database or not
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
log_in user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
#current_user = nil
reset_session
redirect_to root_path
end
end
User Controller:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:new, :show, :edit, :update]
before_action :correct_user, only: [:new, :show, :edit, :update]
def index
#users = User.all
end
def new
#user = User.new
end
def create
#user = User.new(set_params)
if #user.save
redirect_to new_sessions_path
else
render 'new'
end
end
def show
#user = User.find(params[:id])
#posts = #user.posts
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(update_params)
redirect_to #user
else
render 'edit'
end
end
private
def set_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def update_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
Currenty if user type in search bar localhost:3000/users/5 and user with id 5 does not exists in database it shows error
ActiveRecord::RecordNotFound in UsersController#show
Couldn't find User with 'id'=3
but I want to simply redirect to currently logged in users profile page.
If users type in search bar localhost:3000/users/3 and user with this id exists in db , currenty it show an error that firefox is not able to process this request but i want it redirect to its default page i.e,,user's profile page.
Create another controller call it UserController and don't depend on id. Instead figure out the current user from the session and display that user. So the show method for this controller would look like this:
def show
#user = User.find(session["user_id]")
#posts = #user.posts
end
Also, you might want to protect your UsersController by validating if the current user has access to view / update the user being queried for.
Just change your UsersController#correct_user to catch ActiveRecord NotFound exception:
class UsersController < ApplicationController
...
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
rescue ActiveRecord::RecordNotFound
redirect_to(root_url)
end
end
I would use "where" and ".take" in Users show method. The find method brakes the code when it does not find anything
def show
#user = User.where("id" => params[:id]).take
if #user.present?
#posts = #user.posts
else
redirect_to(root_url)
end
end
Or you can redirect instead of root_url to a more friendly error view that shows User not found
I use Rails 4, and have four methods at my application_controller.rb for setting flash messages in Rails
def exclusion_info_for model_name
flash[:notice] = "#{model_name.to_s.capitalize} has been deleted."
end
def creation_notice_for model_name
flash[:notice] = "#{model_name.to_s.capitalize} has been created."
end
def update_notice_for model_name
flash[:notice] = "#{model_name.to_s.capitalize} has been updated."
end
def error_notice
flash[:error] = "An unexpected error has it occurred"
end
But the flash setting at exclusion_notice_for is lost after redirection of the action destroy. The others methods works normally.
The Controller
class CustomersController < ApplicationController
respond_to :html
def new
respond_with #customer = customer
end
def create
if #customer = Customer.create(customer_attrs)
creation_notice_for :customer
else
error_notice
end
respond_with #customer, location: "/"
end
def show
respond_with #customer = customer
end
def index
respond_with #customers = Customer.all
end
def edit
respond_with #customer = customer
end
def update
if #customer = customer.update(customer_attrs)
update_notice_for :customer
else
error_notice
end
respond_with #customer, location: "/"
end
def destroy
if #customer = customer.destroy()
exclusion_info_for :customer
else
error_notice
end
respond_with #customer, location: "/"
end
private
def customer
id ? Customer.find(id) : Customer.new
end
def customer_attrs
params.require(:customer).permit(:name)
end
end
This is the application destroy button currently genereted
This is the application.rb file
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
# protect_from_forgery with: :exception
include FormattedTime
def form_parent
ObjectSpace._id2ref(params[:form_parent_object_id].to_i) if params[:form_parent_object_id]
end
helper_method :form_parent
def root
render "layouts/application", layout: false
end
protected
def id
params[:id]
end
# refactored
def info_flashed model_name, action
flash[:notice] = "#{model_name.to_s.capitalize} has been #{action}."
end
def error_notice
flash[:error] = "An unexpected error has it occurred"
end
end
Flashing Method will work all of your actions simply add 'created' or 'updated' string like in destroy method.
You had error Can't verify CSRF token authenticity
You can put this method in application.rb file to fix it all.
protect_from_forgery with: :exception, if: Proc.new { |c| c.request.format != 'application/json' }
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
protected
def info_flashed (model_name, action)
flash[:notice] = "#{model_name.to_s.capitalize} has been #{action}."
end
In Controller
def destroy
if #customer = customer.destroy
info_flashed (#customer, 'deleted')
else
error_notice
end
respond_with #customer, location: "/" # you need to redirect correct path.
end
I'm using Rails 3.2 and Authlogic. I have the following code:
class ApplicationController < ActionController::Base
private
def store_location
session[:return_to] = request.url
end
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
end
class UserSessionsController < ApplicationController
before_filter :require_no_user, :only => [:new, :create]
before_filter :require_user, :only => :destroy
def new
#user_session = UserSession.new
#header_title = "Login"
end
def create
#user_session = UserSession.new(params[:user_session])
if #user_session.save
flash[:success] = "Login successful!"
redirect_back_or_default root_url
else
render 'new'
end
end
def destroy
current_user_session.destroy
flash[:success] = "Logout successful!"
redirect_back_or_default root_url
end
end
This code is quite generic. When we use the before_filter:
before_filter :require_user, :only => [:new, :edit, :update, :create]
It will automatically store_location and redirect us back to the proper page. However, how do I do this:
I'm in posts/1 which doesn't require_user.
I click the login link on my top navigation bar.
It shows the login page.
Once login, I will be redirected back to posts/1 instead of the root_url.
Place a direct call to store_location in the sessions controller new action.
# user_sessions_controller.rb
def new
store_location if session[:return_to].blank?
#user_session = UserSession.new
#header_title = "Login"
end
This will first check for an existing return_to pair in the sessions hash. You don't want to overwrite it in case, for example, a user is redirected to the new action because of a bad password.
This will also skip store_location if it was already called from require_user.
After a successful redirect, you have to delete the return_to pair from the sessions hash; setting it to nil is not enough:
# application_controller.rb
def redirect_back_or_default(default)
redirect_to(session.delete(:return_to) || default)
end
I added a store_referrer_location to make it work:
# application_controller.rb
class ApplicationController < ActionController::Base
private
def store_referrer_location
session[:return_to] = request.referrer
end
end
# user_sessions_controller.rb
class UserSessionsController < ApplicationController
def new
store_referrer_location if session[:return_to].blank?
#user_session = UserSession.new
#header_title = "Login"
end
...
def destroy
store_referrer_location if session[:return_to].blank?
current_user_session.destroy
flash[:success] = "Logout successful!"
redirect_back_or_default root_url
end
end
I have a standard RESTful controller that uses strong parameters.
class UsersController < ApplicationController
respond_to :html, :js
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
end
def create
#user = User.new(safe_params)
if #user.save
redirect_to #user, notice: t('users.controller.create.success')
else
render :new
end
end
def update
#user = User.find(params[:id])
if #user.update_attributes(safe_params)
redirect_to #user, notice: t('users.controller.update.success')
else
render :edit
end
end
def destroy
#user = User.find(params[:id])
if current_user != #user
#user.destroy
else
flash[:error] = t('users.controller.destroy.prevent_self_destroy')
end
redirect_to users_url
end
private
def safe_params
safe_attributes =
[
:first_name,
:last_name,
:email,
:password,
:password_confirmation,
]
if current_user.is?(:admin)
safe_attributes += [:role_ids]
end
params.require(:user).permit(*safe_attributes)
end
end
In my config/initializers I have the file strong_parameters.rb
ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)
When I add a simple call to CanCan's load_and_authorize_resource I get
1) UsersController POST create with invalid params re-renders the 'new' template
Failure/Error: post :create, user: #attr
ActiveModel::ForbiddenAttributes:
ActiveModel::ForbiddenAttributes
# ./spec/controllers/users_controller_spec.rb:128:in `block (4 levels) in <top (required)>'
Where #attr in the test is defined as
before(:each) do
#attr =
{
first_name: "John",
last_name: "Doe",
email: "user#example.com",
password: "foobar",
password_confirmation: "foobar"
}
end
In the tests I have it all setup properly to login the user and give them the necessary roles for being an administrator so I know it's not that. I don't know why this is causing ForbiddenAttributes to trigger. I'm sure it's something simple I've overlooked. Has anyone else encountered this problem and found a solution to it?
I believe this is because CanCan will use its own getter method for the requested resource if you don't pre-load it with a before_filter. So you could add this to the controller and it should work:
class UsersController < ApplicationController
before_filter :new_user, :only => [:new, :create]
load_and_authorize_resource
def new_user
#user = User.new(safe_params)
end
end
(And then do the same for the edit/update actions.)
before_filter do
params[:user] = safe_params
end
load_and_authorize_resource