Render and/or redirect Ruby - ruby-on-rails

i have login page with 2 sessions client and admin so when client logged in i do redirect to complete the form and when admin logged in i redirect him to dashboard, the problem is :
ActionController::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".)
def login
#institute = Configuration.find_by_config_key("LogoName")
available_login_authes = FedenaPlugin::AVAILABLE_MODULES.select{|m| m[:name].classify.constantize.respond_to?("login_hook")}
selected_login_hook = available_login_authes.first if available_login_authes.count>=1
if selected_login_hook
authenticated_user = selected_login_hook[:name].classify.constantize.send("login_hook",self)
else
if request.post? and params[:user]
#user = User.new(params[:user])
user = User.find_by_username #user.username
if user.present? and User.authenticate?(#user.username, #user.password)
authenticated_user = user
end
end
end
if authenticated_user.present?
successful_user_login(authenticated_user) and return
elsif authenticated_user.blank? and request.post?
flash[:notice] = "#{t('login_error_message')}"
end
end
private
def successful_user_login(user)
session[:user_id] = user.id
flash[:notice] = "#{t('welcome')}, #{user.first_name} #{user.last_name}!"
redirect_to session[:back_url] || {:controller => 'user', :action => 'dashboard'}
if user.client
redirect_to session[:back_url] || {:controller => 'client', :action => 'complete_registration'}
end
end
end

You said right redirect_to is not a return method, the codes above is executed too. In you successful_user_login if user is client, you are calling redirect_to twice, so you can refactor this method to:
if user.client
redirect_to session[:back_url] || {:controller => 'client', :action => 'complete_registration'}
else
redirect_to session[:back_url] || {:controller => 'user', :action => 'dashboard'}
end

ActionController::DoubleRenderError is a error thrown by Ruby on Rails when a controller action tries to render or redirect a response twice. In your code, the action attempts to redirect the user to both 'user/dashboard' and 'client/complete_registration' depending on the user's status as a client. This will result in a DoubleRenderError. Notice that rendering or redirecting do not returns from the method.
Here are some untested code examples with different approaches:
You can calculate the controller and the action before calling redirect_to:
def successful_user_login(user)
session[:user_id] = user.id
flash[:notice] = "#{t('welcome')}, #{user.first_name} #{user.last_name}!"
controller, action = if user.client
["client", "complete_registration"]
else
["user", "dashboard"]
end
redirect_to session[:back_url] || {controller: controller, action: action}
end
You can also do an if and return after first redirect_to:
def successful_user_login(user)
session[:user_id] = user.id
flash[:notice] = "#{t('welcome')}, #{user.first_name} #{user.last_name}!"
if user.client
redirect_to session[:back_url] || {:controller => 'client', :action => 'complete_registration'}
return
end
redirect_to session[:back_url] || {:controller => 'user', :action => 'dashboard'}
end
Or do an if else:
def successful_user_login(user)
session[:user_id] = user.id
flash[:notice] = "#{t('welcome')}, #{user.first_name} #{user.last_name}!"
if user.client
redirect_to session[:back_url] || {:controller => 'client', :action => 'complete_registration'}
else
redirect_to session[:back_url] || {:controller => 'user', :action => 'dashboard'}
end
end

Related

AbstractController::DoubleRenderError in TicketsController#update

i get a AbstractController::DoubleRenderError in TicketsController#update.
If i select a particular user, the update is not happening.
def update
#selected_group = Group.find_by_id(params[:Department]) unless params[:Department].nil?
#selected_company = Company.find_by_id(params[:Company]) unless params[:Company].nil?
#ticketnote_content = params[:Ticketnote]
if ((#selected_group != nil) && (#selected_company != nil))
map_group_to_department
map_user_to_staff
update_ticket
if (#response['response'] == "Failed")
flash[:error] = response['err_desc']
redirect_to "/ticket/#{params[:id]}/edit"
return
elsif (#response['response'] == "Success")
#ticketnote_content
if #ticketnote_content != ""
add_note_to_ticket
end
map_assets_findings_tickets
flash[:notice] = "Succesfully updated the ticket"
TicketHistory.create_ticket_history(#assigned_user,#selected_asset,#ticket_params,current_user,#updated_ticket_response,"Updated")
end
else
flash[:error] = "Company or department can't be blank."
redirect_to "/ticket/#{params[:id]}/edit"
return
end
redirect_to :controller => 'tickets' , :action => 'show', :id => params[:id],:test_id => #test,:ticket_id=> params[:ticket_id]
end
As you have already used redirect_to within your if else statement and after execution of if else you are redirecting again, which lead this error(you can use only once within each action). In order to resolve this, I would suggest following solutions(the question was not clear, so I may not be right):
Solution 1: If your last redirect_to is not required then delete it, i.e.
redirect_to :controller => 'tickets' , :action => 'show', :id => params[:id],:test_id => #test,:ticket_id=> params[:ticket_id]
Solution 2: Update with and return in every redirect_to and moving you last redirect_to in your successful response condition(I'm not sure where you want your Ticket#show) , i.e.
def update
#selected_group = Group.find_by_id(params[:Department]) unless params[:Department].nil?
#selected_company = Company.find_by_id(params[:Company]) unless params[:Company].nil?
#ticketnote_content = params[:Ticketnote]
if #selected_group && #selected_company
map_group_to_department
map_user_to_staff
update_ticket
if (#response['response'] == "Failed")
flash[:error] = response['err_desc']
redirect_to "/ticket/#{params[:id]}/edit"
elsif (#response['response'] == "Success")
add_note_to_ticket if #ticketnote_content != ""
map_assets_findings_tickets
flash[:notice] = "Succesfully updated the ticket"
TicketHistory.create_ticket_history(#assigned_user,#selected_asset,#ticket_params,current_user,#updated_ticket_response,"Updated")
redirect_to :controller => 'tickets' , :action => 'show', :id => params[:id],:test_id => #test,:ticket_id=> params[:ticket_id]
end
else
flash[:error] = "Company or department can't be blank."
redirect_to "/ticket/#{params[:id]}/edit" and return
end
end
P.S.: You can use redirect_to and flash[:message] in one line:
redirect_to your_path(params), :notice => "your message"

How can I dry this method?

def restore_download_delete_file
begin
case params[:submit]
when "restore"
restore_status = restore_file(params[:file_names])
raise if restore_status != 0
flash[:notice] = "File Successfully Restored."
redirect_to :action => "database_settings"
when "download"
download_status = download_file(params[:file_names])
raise if download_status != 0
when "delete"
delete_status = delete_file(params[:file_names])
raise if delete_status != 0
flash[:notice] = "File Successfully Deleted."
redirect_to :action => "database_settings"
end
rescue Exception => e
flash[:error] = "Error with #{params[:submit]}! Please retry."
redirect_to :action => "database_settings"
end
end
How could I improve this method?
You can clean it up by dividing it into four: one for restore, one for delete, one for download, and one for calling the appropiate one and handling the exceptions.
def restore_download_delete_file
begin
self.send "#{params[:submit]}"
rescue Exception => e
flash[:error] = "Error with #{params[:submit]}! Please retry."
redirect_to :action => "database_settings"
end
end
def restore
restore_status = restore_file(params[:file_names])
raise if restore_status != 0
flash[:notice] = "File Successfully Restored."
redirect_to :action => "database_settings"
end
def download
download_status = download_file(params[:file_names])
raise if download_status != 0
end
def delete
delete_status = delete_file(params[:file_names])
raise if delete_status != 0
flash[:notice] = "File Successfully Deleted."
redirect_to :action => "database_settings"
end
Also, a couple considerations:
Raise proper exceptions, not nil.
Don't rescue all exceptions. Rescue the ones you are raising instead.
Try like this
begin
status = send("#{params[:submit]}_file", params[:file_names])
raise unless status == 0
if params[:submit] == 'restore' || params[:submit] == 'delete'
flash[:notice] = "File Successfully #{params[:submit].capitalize}d"
end
rescue Exception => e
flash[:error] = "Error with #{params[:submit]}! Please retry."
ensure
redirect_to :action => "database_settings" unless params[:submit] == 'download'
end
def restore_download_delete_file
submit = params[:submit]
if not restore_file(params[:file_names]).zero?
flash[:error] = "Error with #{submit}! Please retry."
elsif submit != "download"
flash[:notice] = "File Successfully #{submit.capitalize}d."
else
return
end
redirect_to :action => "database_settings"
end
You can always turn your mega-method into a little interpreter:
private
FILE_CMDS = {
'delete' => {
:action => lambda { |names| delete_file(names) },
:notice => 'File Successfully Deleted.',
:redirect => 'database_settings',
},
'download' => {
:action => lambda { |names| download_file(names) },
},
'restore' => {
:action => lamba { |names| restore_file(names) },
:notice => 'File Successfully Restored.',
:redirect => 'database_settings',
},
}
public
def restore_download_delete_file
begin
cmd = FILE_CMDS[params[:submit]]
status = cmd[:action].call(params[:file_names])
raise if(status != 0)
flash[:notice] = cmd[:notice] if(cmd[:notice])
redirect_to :action => cmd[:redirect] if(cmd[:redirect])
rescue Exception => e
flash[:error] = "Error with #{params[:submit]}! Please retry."
redirect_to :action => "database_settings"
end
end
But I think egarcia's approach makes the most sense; there's really
no need to mash all this stuff into one method. The commonalities
really are quite minimal so four methods in one controller makes
more sense: one action, one method. DRY is just a guideline, it isn't
dogma to be followed at all costs.

Rails 3: How to "redirect_to" in Ajax call?

The following attempt_login method is called using Ajax after a login form is submitted.
class AccessController < ApplicationController
[...]
def attempt_login
authorized_user = User.authenticate(params[:username], params[:password])
if authorized_user
session[:user_id] = authorized_user.id
session[:username] = authorized_user.username
flash[:notice] = "Hello #{authorized_user.name}."
redirect_to(:controller => 'jobs', :action => 'index')
else
[...]
end
end
end
The problem is that redirect_to doesn't work.
How would you solve this ?
Finally, I just replaced
redirect_to(:controller => 'jobs', :action => 'index')
with this:
render :js => "window.location = '/jobs/index'"
and it works fine!
There is very easy way to keep the flash for the next request. In your controller do something like
flash[:notice] = 'Your work was awesome! A unicorn is born!'
flash.keep(:notice)
render js: "window.location = '#{root_path}'"
The flash.keep will make sure the flash is kept for the next request.
So when the root_path is rendered, it will show the given flash message. Rails is awesome :)
I think this is slightly nicer:
render js: "window.location.pathname='#{jobs_path}'"
In one of my apps, i use JSON to carry on the redirect and flash message data. It would look something like this:
class AccessController < ApplicationController
...
def attempt_login
...
if authorized_user
if request.xhr?
render :json => {
:location => url_for(:controller => 'jobs', :action => 'index'),
:flash => {:notice => "Hello #{authorized_user.name}."}
}
else
redirect_to(:controller => 'jobs', :action => 'index')
end
else
# Render login screen with 422 error code
render :login, :status => :unprocessable_entity
end
end
end
And simple jQuery example would be:
$.ajax({
...
type: 'json',
success: functon(data) {
data = $.parseJSON(data);
if (data.location) {
window.location.href = data.location;
}
if (data.flash && data.flash.notice) {
// Maybe display flash message, etc.
}
},
error: function() {
// If login fails, sending 422 error code sends you here.
}
})
Combining the best of all answers:
...
if request.xhr?
flash[:notice] = "Hello #{authorized_user.name}."
flash.keep(:notice) # Keep flash notice around for the redirect.
render :js => "window.location = #{jobs_path.to_json}"
else
...
def redirect_to(options = {}, response_status = {})
super(options, response_status)
if request.xhr?
# empty to prevent render duplication exception
self.status = nil
self.response_body = nil
path = location
self.location = nil
render :js => "window.location = #{path.to_json}"
end
end
I didn't want to modify my controller actions so I came up with this hack:
class ApplicationController < ActionController::Base
def redirect_to options = {}, response_status = {}
super
if request.xhr?
self.status = 200
self.response_body = "<html><body><script>window.location.replace('#{location}')</script></body></html>"
end
end
end

Learning how to spec..seem to be having troubles

Here's my code I'm spec'ing:
def vote_up
get_vote
#vote.value += 1 unless #vote.value == 1
#vote.save
respond_to do |format|
format.js { render :action => "vote", :layout => false }
end
end
Seems pretty straightforward. This is what I'm trying to spec it with :
it "should vote up" do
#mock_cat = Factory.create(:category)
Category.stub(:mock_cat)
#mock_post = Factory.create(:post)
Post.stub(:current_post).and_return(#mock_post)
#vote = Factory(:vote)
get :vote_up, :id => #vote
#vote.reload.value.should == 1
end
It's returning this :
undefined method `to_i' for #<Vote:0x1052a4af8>
I can't really figure out why though. If I stubbed my mock_vote as (:vote), wouldn't it run through the controller method and get +1 attributed to it?
Update
Here's the private method from my posts_controller.rb
private
def get_vote
current_post = Post.all.detect{|r| r.id == params[:id].to_i}
#post = current_post
#vote = current_post.votes.find_by_user_id(current_user.id)
unless #vote
#vote = Vote.create(:user_id => current_user.id, :value => 0)
current_post.votes << #vote
end
end
Answer:
it "should vote up" do
#mock_cat = Factory.create(:category)
Category.stub(:mock_cat)
#post = Factory(:post)
get :vote_up, :id => #post.id
#post.reload.vote_score.should == 1
end
I recommend to move logic from controller to model. Models are much easier to spec. Instead of:
def vote_up
get_vote
#vote.value += 1 unless #vote.value == 1
#vote.save
respond_to do |format|
format.js { render :action => "vote", :layout => false }
end
end
Your controller should looks like this:
def vote_up
#vote = Vote.vote_up(params[:id])
respond_to do |format|
format.js { render :action => "vote", :layout => false }
end
end
or, if you really need get_vote (may be you should move it to before_filter?)
def vote_up
#vote.vote_up
respond_to do |format|
format.js { render :action => "vote", :layout => false }
end
end
If necessary. add exceptions (may be with rescue_from)
Then, you will need specs only in model, and some integration (rspec,steac, cucumber)
It's hard to follow what exactly your stubs are doing because you didn't post the code for get_vote. But I think you're over-using stubs when you could just be taking advantage of the factories you're already creating.
it "should vote up" do
# Does your Vote belong to a post or a category or anything? I don't know.
# Modify as needed -- Factory(:vote, :post => Factory(:post))
#vote = Factory(:vote)
get :vote_up, :id => #vote
#vote.reload.value.should == 1
end
Note the reload. Your controller's modifying a record we've already pulled from the database, so we need to reload it to check its new value.

work with rescue in Rails

I am working with the following piece;
def index
#user = User.find(params[:id])
rescue
flash[:notice] = "ERROR"
redirect_to(:action => 'index')
else
flash[:notice] = "OK"
redirect_to(:action => 'index')
end
Now I either case whether I have a correct ID or not, I am always getting "OK" in my view, what am I doing wrong?
I need that when I have no ID in the DB to show "ERROR". I have also tried to use rescue ActiveRecord::RecordNotFound but same happens.
All help is appreciated.
All code after the end of the rescue block is interpreted only if there are no returns in the rescue block. So you can call return at the end of your rescue block.
def index
begin
#user = User.find(params[:id])
rescue
flash[:notice] = "ERROR"
redirect_to(:action => 'index')
return
end
flash[:notice] = "OK"
redirect_to(:action => 'index')
end
or
def index
#user = User.find(params[:id])
# after is interpret only if no exception before
flash[:notice] = "OK"
redirect_to(:action => 'index')
rescue
flash[:notice] = "ERROR"
redirect_to(:action => 'index')
end
But in your case the better is to use rescue_from or rescue_in_public
like
class UserController < ApplicationController
def rescue_in_public(exception)
flash[:notice] = "ERROR"
redirect_to(:action => 'index')
end
def index
#user = User.find(params[:id])
flash[:notice] = "OK"
redirect_to(:action => 'index')
end
end
But the using of rescue_in_public is not really good advice
Just an overall Rails Rescue answer:
I found this to be very cool:
#user = User.find(params[:id]) rescue ""
If there is no user with that id, then User.find will return nil. Returning nil is not an error case and will not trigger a rescue.

Resources