Check if a user object is found before destroy - ruby-on-rails

I am trying to build a Rails 3.2 app and I have just a quick question when doing destroy.
First I make a find to find the user I need to delete but I do not want to make destroy
if it is not found.
This is my code and I feel something is missing on line 3 (if #user):
#user = User.find(params[:user_id])
if #user
#user.destroy
else
"User not found"
end

you can also do this with try:
if User.find_by_id(params[:user_id]).try(:destroy)
"User found and destroyed"
else
"User not found or was not successfully destroyed"
end

Your code will not work and will raise an exception, you should do:
#user = User.find_by_id(params[:user_id])
if #user
#user.destroy! #methods with bang raise an exception, I advise you to use them
#no flash msg?
else
flash[:error] = "User not found"
end

If the user is not found, you'd get an exception
If you don't want it, do :
#user = User.find_by_id(params[:user_id])
Then your test is correct

Related

ruby on rails undefined method 'valid' when using update

I am using the same commands that I previously used in successful projects but now I suddenly can't validate any updates to the object(in this case, User). Everything else works fine but any attempt to check my validations for an update results in this error-
'undefined method `valid?' for # '
It is finding successfully finding the user and if I skip the .valid? statement then it will update, just without checking any of my model validations. I recently switched from SQLite to PostgreSQL, I am not sure if that's giving me the problem. I am new to Ruby but I couldn't find anything on this specific problem.
Please let me know if I should include the entirety of my controller or any of my model but as my create works fine, I feel like all the relative code is just in this little section-
class UsersController < ApplicationController
def update
#user = User.find(params[:id])
puts "#Is this working???!! #{#user}" ///prints #Is this working???!! #<User:0x00000001f24468>
#user = User.update(user_params)
if #user.valid?
redirect_to "/users/#{#user.id}"
else
flash[:errors] = #user.errors.full_messages
redirect_to "/users/#{#user.id}/edit"
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password)
end
Your problem is here:
#user = User.update(user_params)
If you put in your check after, you would see: #Is this working???!! true, which would ironically enough inform you that it's not working.
That's because User.update(user_params) returns true or false depending on whether it is successful or not. This means your #user object is now simply either true or false, which you can't call valid on.
If you want to handle successfully updating / failing to do so, try:
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to "/users/#{#user.id}"
else
flash[:errors] = #user.errors.full_messages
redirect_to "/users/#{#user.id}/edit"
end
end
ActiveRecord update(id, attributes)
Updates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.
Then, you can do this checking if #user.update(user_params)
def update
#user = User.find(params[:id]) //Find user
if #user.update(user_params) // Update user if validations pass
redirect_to "/users/#{#user.id}"
else
flash[:errors] = #user.errors.full_messages
redirect_to "/users/#{#user.id}/edit"
end
end
Or, you can call the update method directly in your model class, but the first argument must be the user ID
User.update(params[:id], user_params)
Thank you both for your quick answers. I was replying to tell you that I already tried that and it worked but did not validate. But as two of you told me the same thing, I decided to test it out again and the validations did indeed work this time so thank you (although I definitely have a user with an email of 'asdf' from last time).
Intestering enough, I found another answer although I have no idea why it worked. I added another puts statement after the update and realized my object had been converted to an array so I came up with this other (worse) alternative answer-
def update
#user = User.find(params[:id])
puts "#Is this working???!! #{#user}"
#user = User.update(user_params)
puts "#Is this working???!! #{#user}" ///prints #Is this working???!! [#<User id: 2, name: "James Dean", etc..>]
if #user[0].valid?
redirect_to "/users/#{#user[0].id}"
else
flash[:errors] = #user[0].errors.full_messages
redirect_to "/users/#{#user[0].id}/edit"
end
end

Ruby - Assign variable and return if found in DB

I'm looking for a neater way to assign a variable and return if the value is found in the DB in a controller before_action method that does not require multiple DB calls:
if Plan.exists?(plan)
#plan = Plan.find(plan)
return
end
logger.error "User selected invalid plan."
redirect_to plan_path, warning: "Invalid plan selected!"
Would this be an appropriate place to use a rescue? E.g:
begin
#plan = Plan.find(plan)
rescue
logger.error "User selected invalid plan."
redirect_to plan_path, warning: "Invalid plan selected!"
end
It feels wrong... what is the best approach?
There is not a best approach I think. It's a matter of taste. I usually do something like your latter way in a Rails 5 API. Something like
def show
#book = Book.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: "Book not found" }
end
And if everything goes fine I have a view for the happy path
Some consider it an anti-pattern to use exceptions for flow control. Personally, I tend to agree. But, I suppose it is a matter of style and preference.
How about something like:
def foo_before_action
unless #plan = Plan.find_by(id: params[:id])
redirect_to plan_path, warning: "Invalid plan selected!"
end
end
Or perhaps as a one-liner:
def foo_before_action
redirect_to plan_path, warning: "Invalid plan selected!" unless #plan = Plan.find_by(id: params[:id])
end
The former is, perhaps, a bit more readable.

How to handle destroy edge cases

Say for example you have two users looking at a page. Both click to delete the same record. Is there a 'rails way' to handle these types of edge cases?
I haven't been able to find out how rails handles this but I know that, by default, whoever clicks later will end up with a record not found error. How can you catch this and know it came from a destroy action on a record that was already deleted?
Any approach that checks for the record's existence will be subject to a race condition. Eg.
#record = Record.find_by_id(params[:id])
if #record
# I got there first! Or did I?...
#record.destroy!
If the record is destroyed by another request after the existence test, destroy! will raise an exception.
You could simply handle the exceptions as they occur:
def destroy
#record = Record.find(params[:id])
#record.destroy!
flash[:message] = "Record was destroyed"
rescue ActiveRecord::RecordNotFound
flash[:message] = "Record was already destroyed"
rescue ActiveRecord::RecordNotDestroyed
flash[:message] = "Record was not destroyed"
end
In practice, there generally isn't a reason to do this unless the destroy can actually fail in both requests, for example, if there is a callback that prevents the record from being destroyed. And in that case, you probably need more extensive error handling to communicate the reason for the failure back to the client.
For most applications I've seen, just routing the user back to a safe place in the event of a failure is enough.
or
def destroy
#record = Record.find_by(id:params[:id])
if #record
#record.destroy!
flash[:message] = "Record is destroyed"
else
flash[:message] = "Record not found!"
end
end
also you can have this in your application_controller.rb, to handle all such request, it will apply to all resource.
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found
flash[:danger] = "Record not Found!"
redirect_to root_path
end
but again, now you should use find() to raise ActiveRecord::RecordNotFound if record not found

Stop deletion of admin where name like 'string_name'

I'm trying to stop A particular Admin from being removed from the database with ruby on rails
I have tried a few things but heres the code as it stands
Edit 2 changed User.name to #user.name
Model
after_destroy :can_not_destroy_super_admin
private
def can_not_destroy_super_admin
if #user.name == "super admin"
raise "Can't delete this admin"
end
end
I think its a problem with User.name, but I know its seeing this code because I've had errors raising issues with different code I've tried in here.
I'm aware that this is a relatively crude method for stopping deletion of an admin but it's a simple way of getting what I need done.
Any suggestions or help is very much appreciated.
Edit.1
Here is the destroy method
Controller
def destroy
#user = User.find(params[:id])
begin
#user.destroy
flash[:notice] = "User #{#user.name} deleted"
rescue Exception => e
flash[:notice] = e.message
end
respond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end
end
I'm guessing your destroy action looks something like this?
def destroy
#user = user.find params[:id]
#user.destroy
end
If this is the case, the user you want to check against in your callback is #user.name, not User.name. You want to ensure that the actual user instance you called destroy on is the same one you're checking the name of.
Edit: As determined in the comments, the callback is actually on the model, I misinterpreted as being in the controller. In the model, to reference the objects name, only name is needed, not User.name or #user.name.

Rails: attributes not being saved even though I called #user.save

I'm running this function, and I KNOW that it gets called because the redirect_to is working. But for some reason, #user isn't! If it helps, #user is devise based.
def make_feed_preference
#user = current_user
##user.feed_preference = params[:preference]
#user.feed_preference = "time"
#user.name = "Shoo Nabarrr"
#user.karma = 666
#user.save
redirect_to '/posts'
end
I fixed it myself. I had to create a new class attached to users in order to get it to work. Lol.
Do you have any validations on this user? They are probably blocking this save. The redirect_to will be called regardless of whether or not the save passes or fails.
I would recommend doing it like this instead:
if #user.save
redirect_to '/posts'
else
render :feed_preference
end
Where :feed_preference is the form where users enter their feed preferences.
There are cases where I want to be sure to update a flag or other field on a record even if the record has validation problems. (However, I would never do that with unvalidated user input.) You can do that thusly:
def make_feed_preference
case params[:preference]
when 'time', 'trending_value', 'followers'
current_user.update_attribute 'feed_preference', params[:preference]
flash[:notice] = 'Your feed preference has been updated.'
else
flash[:notice] = 'Unknown feed preference.'
end
redirect_to '/posts'
end

Resources