Destroy user - with custom authentication (rails) - ruby-on-rails

Quick question: I was following this tutorial where they built user authentication system instead of using devise.
My issue is the tutorial misses the destroy action in which devise has ready and does so well.
My create action is
User_controller.rb
def create
#user = User.create(user_params)
session[:user_id] = #user.id
if #user.valid?
flash[:notice] = "You've successfully Created Your Account! Welcome!"
redirect_to root_path
else
flash[:notice] = "Opps Something went bad, :/ Try again please"
render action: 'new'
end
end
I really hope this is not a total nuub question event though I am one. But can somebody offer some tips for a destroy action ? and also how would that action appear in routes and through a link_to method. I want to create a deactivate page that gives a send off and the user is able to cancel their account. Any cool tips toward the deactivate page on the side will be much appreciated.

The Hartl rails tutorial covers this quite well IMO. Once you have the destroy action defined in your controller, you could create a link to deactivate their account calling the destroy action and redirect to the home page, or a goodbye page. As long as users is listed as a resource in your routes, you shouldn't need to modify your routes as DELETE is a standard CRUD command.
https://www.railstutorial.org/book/updating_and_deleting_users
for example:
user_controller
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
view
<%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>

For the deactivate page, maybe you can add a boolean column in your users table, say is_active,
and another controller action for deactivation, say deactivate, which will just set the is_active column as false for that user.
see sample routes.rb for the route.
#ncarroll 's sample is correct, for the routes, if you have in your routes.rb:
Rails.application.routes.draw do
resources :users do
put :deactivate
end
end
This will automatically create routes for the RESTful actions, which includes destroy.

Related

Feedback on Admin set user as mod in rails

i'm a beginner to Rails and is following Michael Hartl's book. In chapter 9, where he set up an Admin role, and admin can delete users. I want to extend this feature by allowing admin to set users to Mod as well.
The plan is this:
1.An logged in admin go to the users page, where he sees a list of users(users_url)
2.And next to their name, the admin have an option of 'Set Mod', when the admin clicks that, it sets the user's mod attribute to true.
$ rails g migration add_mod_to_users mod:boolean
then in the migrated file, set default to false, and
$ rake db:migrate
In the routes.rb
get 'setmod' => 'users#setmod'
and in users_controller.rb
def setmod
if logged_in? && current_user.admin?
#user = User.find(params[:id])
#user.update_attribute(:mod, true)
flash[:success] = "User ID #{#user.id} is now a mod!"
redirect_to users_url
else
flash[:warning] = "You can't do that!"
redirect_to users_url
end
end
In the list of users view file:
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "Set Mod", setmod_path(:id => user.id) %>
Is this the right approach to it? I'm a beginner so i'd be glad if i can get some feedback on this. Does my code have any vulnerable spots? What's a better way doing it?
Also, i tried to do some test on this
in test/controllers/users_controller_test.rb
# This test passes
test "should redirect setmod when not logged in" do
get :setmod
assert_redirected_to users_url
end
# This test failed
test "should redirect setmod when logged in as a non-admin" do
log_in_as(#other_user)
assert_no_difference '#user.mod' do
get :setmod, id: #user
end
assert flash.empty?
assert_redirected_to users_url
end
I know the second test's code is wrong, but i can't figure out the right way to do it.
You're not passing a user id into the setmod route.
Change to this:
get 'setmod/:id' => 'users#setmod'
Now, navigating to http://localhost:3000/setmod/1 will attempt to set the user (with id=1) to mod.
However, you can use Rails' built-in resources to handle this just fine.
routes.rb
resources :users
your view
link_to("Promote the user to MOD", user_path(#user, status: 1), method: :put)
Clicking that link will update the single attribute for a user shown on the page. You would simply authenticate the admin in the users_controller.rb update action.

why does redirecting with an instance variable take you to a show view(Log-in)

How does the following piece of code redirect_to #userin my create action know how to redirect to my show view?
I don't get how it would possibly know this. I know this is a newb question but I just can't understand why #user works. It would make sense if it was something like redirect 'show' but why is #user used?
Here is my controller code:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
redirect_to #user
is the short hand for:
redirect_to user_path(#user)
You can either use the long-hand or the short-hand. They will both work the same way.
by convention, redirect_to #user maps to:
GET /users/:id(.:format) users#show
That's how it knows to redirect you to the show page.
The show action is the only one (to my knowledge) that has a short-cut.
If you do rake routes, you'll see the complete map:
user_path GET /users/:id(.:format) users#show
If you run rake routes, you will able to see this route which is referring to users#show
GET /users/:id users#show
In your case redirect_to #user which is equivalent of
redirect_to :action => 'show', :id => #user which matches the /users/:id convention
OR
It can also written as redirect_to user_path(#user) which also returns /users/:id
So,the controller just ends up redirecting to the show page.
These Guides will help you understand in detail.
Hope it helps!
Infact in syntax
redirect_to #user
#user variable here gives you the id of that user and if you see your routes (using rake routes) you would see a route users/:id which exactly maps to users#show. So that why it goes to show method or users controller.
You can change this functionality if you want to. There are two approaches to it, a good one and a bad one.
First approach is:
Change you routes which maps users/:id to some other action of your controller
Second bad approach is to redirect to some other path within show action of users controller.
Hope this explanation is enough for your understanding
When you redirect_to an object in Rails (which is what you're doing), the framework has a lot of built-in functionality to take care of things for you
One of these in-built features is the ability to detect & redirect to the object itself
Objects
When you pass #user, you are passing a singular object, which Rails takes as meaning that you want to go to the show view (you want to view the object, right?)
You need to remember that Ruby / Rails is Object Orientated. You're calling them #instance variables, but really they are objects. Those objects contain a lot more data than your standard data - they contain information from the model & db
This means that if you build your #instance variable in the conventional Rails way (using Model data), you're going to get an object
--
Login
It seems to me that you're redirecting to the #user object. However, I would imagine this will be covnered by your authentication system, hence why you're receiving the login page
To fix this, you should try redirecting to specific paths, like this:
redirect_to users_path
Everyone has pointed out that controller interprets
redirect_to #user or redirect_to user_path(#user)
as
GET /users/:id(.:format) users#show
Question arises how Rails comes to know that out of so many routes it needs to go on to users#show
As you can see in above image that 3 paths are associated with
user_path & it requires id which you have passed using #user
So now controller ask which HTTP method GET/PATCH/PUT/DELETE
Since we have not specified any method it will automatically consider GET method & result is
users#show

how to prevent a DELETE HTTP request from succeeding in this situation?

Rails beginner here..
I have a users resource where I implemented a callback that's supposed to prevent an admin user from deleting herself.
before_filter :admin_no_delete, only: :destroy
def admin_no_delete
admin_id = current_user.id if current_user.admin?
redirect_to root_path if params[:id] == admin_id
end
If this looks familiar to some, it's from Michael Hartl's rails tutorial, exercise #10 here but I tried to do it differently, not as he suggested.
My (lame) test for this fails
describe "deleting herself should not be permitted" do
before do
delete user_path(admin)
end
it { should_not redirect_to(users_path) }
end
But exposing a delete link for the admin user just to test and clicking on that link, it seems like the callback actually succeeds in executing (redirecting to root_path).
I was able to invoke the destroy action using jQuery to delete the record being protected by the callback (using Web Inspector's javascript console):
$.ajax({url: 'http://localhost:3000/users/104', type: 'DELETE', success: function(result){alert(result)} })
Looking for ideas on how to prevent a DELETE HTTP request from succeeding in this situation.. also any ideas on how to properly test for this kind of situation?
Thanks.
Simple: params[:id] is a string, while admin_id is a Fixnum. You can just change it as follows and it should work:
redirect_to root_path if params[:id].to_i == admin_id
The logic you're using seems a little odd to me, though. Why use a before filter if it's just for one action, and why change the redirect? I think the logic should be directly in the destroy action and look something like this:
def destroy
unless current_user.admin? && current_user.id == params[:id].to_i
User.find(params[:id]).destroy
flash[:success] = "User destroyed."
end
redirect_to users_path
end
You're comparing admin_id, an integer with params[:id]. Values in params are always strings (or arrays/hashes containing more strings) so the comparison will always fail.

flash notice is lost on redirect, how to find out what's removing it?

There are many posts on SO about this ( respond_with redirect with notice flash message not working Why is :notice not showing after redirect in Rails 3, among others) , I've read at least 4 and still can't solve this issue.
I've got a portion of my site that lets people do some things before they create an account. I prefer this from a UX perspective. So they're allowed to do X and Y then they get redirected to the "Create account" page (uses Devise).
The redirect looks like:
if userIsNew
... stow information in a cookie to be retrieved later ...
redirect_to "/flash", flash[:notice]
=> "Ok, we'll get right on that after you sign up (we need your email)."
and return # this has to be here, since I'm terminating the action early
end
So "/flash" is a plain page that I made to test this. It doesn't do anything, has no markup of its own, just has the basic html from the application.html, which has this line in the body:
<% if flash[:notice] %>
<p><%= notice %></p>
<% else %>
No notice!
<% end %>
It says 'No notice' every time.
I have tried:
adding in a flash.keep to my before_filter in the static controller
using :notice => instead of flash[:notice] =>
putting the notice in a cookie and pulling that text out of the cookie and into a flash in the before_filter of my application controller
redirect_to :back with the flash[:notice] =>
It's either
flash[:notice] = 'blablabla'
redirect_to foo_url
or
redirect_to foo_url, notice: 'blablabla'
I'm overriding ApplicationController#redirect_to to call flash.keep so that any messages are persisted on redirect without having to explicitly call flash.keep in my controller actions. Works well so far. Haven't had a scenario yet where unwanted messages are persisted.
class ApplicationController < ActionController::Base
def redirect_to(*args)
flash.keep
super
end
end
Let me know if there are any scenarios where this isn't a good solution.
I have been fighting with the same problem for some time and none of the posts seemed to help.
It turns out that - like usually it happens - the the problem was in my code. I did have a "redirect_to" that I forgot about, which was clearing the flash.
Namely, "root_path" for me was served by the StaticPagesController's home method. "home" was doing some checks and then redirecting you to the user_path.
In my code I had in numerous places
redirect_to root_path, :flash => {error: #error}
These redirects were never displaying the flash because my hidden "home" controller serving the "root_path" was making another redirect that cleared the flash.
Therefore my problem was solved when i added the "flash.keep" in my "home" controller method
def home
if current_user
#user = current_user
flash.keep
redirect_to #user unless #user.no_role?
end
end
Faced the same problem, flash just disappeared after any redirect, nothing helped, then, I found that it was switched off...
Check your /config/application.rb for this:
config.middleware.delete ActionDispatch::Flash

What to do if more than one view needs to link to a destroy action?

I'm not sure what to do here. I have two scaffolds: Groups and Users. In two different Group views I'm listing group users and calling the Destroy method of the users_controller.
Since a 2nd view is now calling the destroy action, I need some way of detecting what view called the Destroy action because I need a different redirect and custom flash notice for each of the two group views.
Is there a simple way of solving this, or would the solution be something like making a copy of the Destroy method and mapping a new route for it?
-thanks!
edit: maybe this is a stupid idea, but I was thinking... For the two involved views, what if I stored their view names in the session when the views are generated (as a flag for the Destroy action to know which view to redirect to and what custom flash notice to send back)?
Pass the parameter with the links and check those parameters in your action.
Like ,
link_to "Delete", :controller => "groups", :action => "destroy", :pass_par => "view1"
link_to "Delete", :controller => "groups", :action => "destroy", :pass_par => "view2"
Controller:
def destroy
if params[:pass_par] == "view1"
redirect_to view1
else
redirect_to view2
end
end
The destroy method is not very long, so yes, go ahead and copy it.
If it looks like this:
# DELETE /users/1
def destroy
#user = User.find(params[:id])
#user.destroy
redirect_to users_url
end
It should be not repeating yourself at all, and it will make your code simpler to read in the end.

Resources