I'm new in Rails world, but this Double render error a little bit interesting. I have never met this problem in PHP or in ASP.NET.
So I have two actions in one controller which is extended via Devise's Register controller.
I want to achieve that If the user logged in the two new_with_school and create_with_school actions will be redirected by the redirect_signed_in_user function.
class RegistrationsController < Devise::RegistrationsController
def new_with_school
redirect_signed_in_user
build_resource({})
resource.build_school
respond_with self.resource
end
def create_with_school
redirect_signed_in_user
build_resource(sign_up_params_with_school)
resource.school = School.new(sign_up_params_with_school[:school_attributes])
resource.school.user = resource
resource.role = 1
resource.save
respond_to do |format|
if resource.save
format.html { redirect_to after_sign_up_path_for(resource) }
format.json { render :show, status: :created, location: resource }
else
clean_up_passwords resource
set_minimum_password_length
format.html { render :new_with_school }
format.json { render json: resource.errors, status: :unprocessable_entity }
end
end
end
protected
def redirect_signed_in_user
redirect_to '/' if user_signed_in?
end
end
I gave this error message:
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".
So it seems in Rails I can use only one redirect function in action.
But in this case, how can I achieve that if the user logged in, those 2 actions cannot reachable by the logged in user. The most beautiful solution will be, it the webapp could somehow redirect users to the root path (without throw an exception), so I want the most user friendly solution.
How can I do this? (I have read the devise code, but I cannot figure out how the root redirection works in)
Use this:
def redirect_signed_in_user
redirect_to '/' && return if user_signed_in?
end
Related
the following is my code for one of my controllers I have written for my Rails show reviewing application. Note that I that I did not use Devise for user auth.
The problem I am facing right now is that I want the user (pco) to only be able to update the show if he/she is the one which originally uploaded it. Here, authorized_as_pco_to_show can determine that but it needs the #show to be passed into it as a parameter. Therefore, I cannot use before_action.
The way I have it right now is to put this authorized_as_pco_to_show method at the start of every action which only allows for the correct pco to access it. I was wondering if there would be a better way of doing this. Any help would be much appreciated!
def update
authorized_as_pco_to_show #show
respond_to do |format|
if #show.update(show_params)
format.html { redirect_to #show, notice: "Show was successfully updated." }
format.json { render :show, status: :ok, location: #show }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #show.errors, status: :unprocessable_entity }
end
end
end
You can pass parameters to before actions if required. Instead of this:
before_action :authorized_as_pco_to_show
You can use:
before_action do
authorized_as_pco_to_show #show
end
However, as mentioned in the comments, you'll need to get that show from somewhere. Assuming you have another before_action along the lines of load_show which loads it into an instance variable, you can then just use that within your other before_action. Something like this:
before_action :load_show, :authorized_as_pco_to_show
# your actions here
private
def load_show
#show = Show.find(params[:id])
end
def authorized_as_pco_to_show
#show.authorized? # replace with whatever your checks are
end
I'm building an ecommerce app, and have a pretty standard user model and order model. For a user to place an order they must first be logged in. I'm using a Bootstrap modal for the standard login\signup forms.
I'm trying to find a way for when a signed out visitor clicks the Order Now button (links to /orders/new) to have the login\signup modal appear, and then after they log in they'll be redirected to /orders/new path.
Here's my current Orders#new method:
def new
#order = Order.new
if current_user
#order.user = current_user
#order.docs.build
else
redirect_to new_order_path
end
end
Here's my current Users#new and Users#create methods. I used this guide to setup the modals.
def new
#user = User.new
respond_modal_with #user
end
def create
#user = User.new(user_params)
respond_modal_with #user, location: root_path
respond_to do |format|
if #user.save
format.html {
login(params[:user][:email], params[:user][:password])
UserMailer.send_signup_email(#user).deliver_later
redirect_to root_path, notice: "You have successfully signed up"
}
format.json { render :show, status: :created, location: #user }
else
format.html { render :new, alert: "Registration failed" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
Sorry if I'm missing important info here. It's my first time posting and I'm a newbie. Thanks in advance for your help!
Edit:
Comments were recommending I use Devise for authorization, and I should have mentioned that I'm already using Sorcery for my authentication\authorization. So that part of the problem isn't an issue. The trouble I'm having is just how to trigger the modal from the Orders controller. Thanks!
Hi if you want to add functionality such as login/signup and perform all those operations, I would recommend you to use 'devise' gem. It is very easy and handy to use. You can find its documentation here. It has several methods like user_signed_in? which will help your problem signed out user to show login page and then continue your modal operation.
Thanks
To do this in a DRY way using Devise you can use the StoreLocation module. see the code: https://github.com/plataformatec/devise/blob/7d3d6fb3f04caea95e343956a88654f753b45af4/lib/devise/controllers/store_location.rb
To use that, you can follow the instructions from their WIKI: https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in
Pretty much, you will have to:
1.Create a filter in your application_controller to redirect to sign-in page and save the current path:
def ensure_logged_in_or_redirect
unless logged_in?
store_location_for(:user, request.path)
redirect_to new_user_session_path
end
end
2.Create the after_sign_in_path_for method also in the application_controller, as shown on the Wiki.
3.Call that ensure_logged_in_or_redirect filter before the actions you want to protect, like the 'New Order' one.
Hope that helps.
Is there an easy way to write a helper method to always update the previously visited url in the session. I have tried the method below but the url saved is always the current one. I would like to be able to use this helper in all my controllers for redirect.
#application_controller.rb
class ApplicationController < ActionController::Base
before_filter :my_previous_url
def my_previous_url
session[:previous_url] = request.referrer
end
helper_method :my_previous_url
end
I have used it in my update method in the User controller as seen below but it always redirects to the same opened url (kind of looks like refresh was hit).
def update
if current_user.admin == true and #user.update(user_params)
redirect_to my_previous_url, notice: "Password for User #{#user.username} has Successfully been Changed."
return
elsif current_user.admin == false and #user.update(user_params)
session[:user_id] = nil
redirect_to login_path, notice: "Password for User #{#user.username} has Successfully been Changed. Please Log-In Using the New Password."
return
end
respond_to do |format|
if #user.update(user_params)
changed = true
format.html { redirect_to logout_path }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
request.referer isn't what you want here as it will be set on page redirects, thus losing the page you came from originally. I think that you have an implied requirement that it should return the last visited url which was different to the current one, is that the case? Also, i think that you would only want to set it for GET requests, otherwise you risk sending people back to the wrong url, since they will be sent back with a GET request. I'm assuming here that the purpose of this previous_url is to give people a "back" link.
Also, don't get the method to set the previous_url mixed up with the method to read it back out again.
I would do it like this:
#application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_previous_url
helper_method :previous_url
def set_previous_url
if request.method == :get && session[:previous_url] != session[:current_url]
session[:previous_url] == session[:current_url]
session[:current_url] = request.url
end
end
def previous_url
session[:previous_url]
end
end
The following is a standard posts#create action (app/controllers/posts_controller.rb).
At the controller level, I want to prevent an anonymous user (a user who is not signed in) from being able to save a post. As a secondary objective, I don't even want to execute the Post.new line if the user is not signed in. I want to know what is the best practice for accomplishing this.
Also, as a side note, I am unsure of how to write the json portion of the response. If I am redirecting with an alert message in the HTML context, what would be a good thing to respond with in JSON world?
def create
#posting = Post.new(posting_params)
respond_to do |format|
if #posting.save
format.html { redirect_to #posting, notice: 'Post was successfully created.' }
format.json { render action: 'show', status: :created, location: #posting }
else
format.html { render action: 'new' }
format.json { render json: #posting.errors, status: :unprocessable_entity }
end
end
end
For the time being I have the following line in my code, above the Post.new line:
redirect_to home_path, warning: 'You must be logged in to post.' and return unless user_signed_in?
I suppose another option is something like the following, placed above the if #posting.save line. But really, I am looking to see what other folks would do.
unless user_signed_in?
format.html { redirect_to home_path, alert: 'You must be logged in to post.' and return }
format.json { render json: .....not sure what to put here..... }
end
Your advice is greatly appreciated.
A before_filter is good for this sort of thing:
before_filter :confirm_user_signed_in, only: [:new, :create]
def confirm_user_signed_in
unless user_signed_in?
respond_to do |format|
format.html { redirect_to home_path, alert: 'You must be logged in to post.' and return }
format.json { render json: .....not sure what to put here..... }
end
end
end
As far as what to render in the JSON scenario, you can render nothing at all, but with a 403 (Forbidden) status. You can optionally include some data explaining why the 403 occurred, but there's no standard for how that data will be displayed. Some frameworks (Backbone, I think) will look for a hash containing an errors key, which can be set to the reason.
Something like:
format.json { render json: { errors: ["Login required."] }, status: 403 }
The better practice is to use before filter and mention list of actions like this:
before_filter :require_login, :only => [:new, :create]
Try using the cancan gem. You can not only prevent the unwanted user from posting, also you can do various other permissions, which do not bloat the controller. These permissions are defined by you in a separate file called ability.rb.
cancan Railscast
cancan Github
Hi I am getting a double redirection here
What I've been trying to accomplish is to auto sign in the user and redirect the user into a specific path, not in the root path.
In my registrations_controller.rb
def new
#user = User.new
end
def create
#user = User.new params[:user]
#valid = #user.save
if #valid
sign_up
end
respond_to do |format|
format.js { render layout: false }
end
end
def sign_up
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_up_path_for(resource)
end
In my application_controller.rb
def after_sign_in_path_for(resource)
if resource.admin? or resource.super_admin?
admin_index_path
else
if mobile_device?
page_path("backgroundinfo")
else
page_path("howto")
end
end
end
def after_sign_up_path_for(resource)
page_path("howto")
end
Any workarounds will be appreciated.
PS: Removing the respond_with resource, :location => after_sign_up_path_for(resource) is still redirecting the user to the root_path not in the page_path maybe because the controller action is in registration?
A double render error happens when you try to render two templates from the same request.
Your sign_up method attempts to respond with a respond_with block, and after that your create method attempts to make another response from the respond_to block. Here is the issue:
if #valid
sign_up
end
This calls your sign_up method which attempts to render a response. After that happens, the following block tries to render another template:
respond_to do |format|
format.js { render layout: false }
end
Your execution path attempts two render calls, which gets you a double render error.
Your code should look like something along these lines:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
# sign in user here and render response for success
format.js { #handle js }
format.html { #handle html }
else
# user was not saved
format.js { #handle error }
format.html { #handle error }
end
end
end
I had the same issue, and bypassed it by overriding Devise::SessionsController, like so:
class SessionsController < Devise::SessionsController
def new
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
#yield resource if block_given?
#respond_with(resource, serialize_options(resource))
# The rest of your code
# your redirect
redirect_to '/'
end
I'm unsure as to whether this has any negative side affects, but it appears to work fine, and also stops it from appending user sign-in data to the URL.