Ruby on rails: previous url helper for entire application - ruby-on-rails

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

Related

Devise redirect after sign_in and perfom POST action

I'm building an events platform with Devise gem & Rails 6.1.
I'm having trouble with a small RSVP functionality.
When a non-login user click "Attend" button, it redirect the user to sign-in and once this is done the user is redirect back to the previous page. HOWEVER user has to click again "Attend" button.
I want the user redirect back and ALSO complete the create action ( POST action )
I managed to do the redirect work fine, but the user would have to click again on the "Attend" button which I do not want. I've searched the entire internet but none of the solutions worked.
The closet solution I've found is from this blog https://blog.justinthiele.com/retaining-form-data-through-a-login-process-a/ which I tried to apply on my app but without success.
I would think that something is missing in the after_sign_in_path_for method.
Any help would be appreciated.
UPDATE!
After debugging, I changed a bit the way I'm overriding Devise.
I feel I'm very close to the solution but can't figure it out what I'm missing.
attendee_controller.rb
def create
if !current_user
# Store the data in the session so we can retrieve it after login
session[:attendee] = params
# Redirect the user to register/login
redirect_to user_session_path
else
#attendee = current_user.attendees.build({event_id: params[:id]})
respond_to do |format|
AttendeeMailer.with(event: #attendee).notify_event_creator.deliver_now
if #attendee.save
format.html { redirect_to #attendee.event, notice: "You have successfully registered for the event!" }
format.json { render :show, status: :created, location: #attendee }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #attendee.errors, status: :unprocessable_entity }
end
end
end
end
app\controllers\users\sessions_controller.rb (user_session_path)
protected
def after_sign_in_path_for(resource_or_scope)
if session[:attendee].present?
new_attributes = session[:attendee]
previous_url = session[:previous_url] #defined in the show method from the events controller.
#attendee = current_user.attendees.create({event_id: new_attributes[:id]})
#clear session
session[:attendee] = nil
session[:previous_url] = nil
previous_url
else
#if there is not temp attendee in the session proceed as normal
super
end
end
events_controller.rb
def show
session[:previous_url] = request.fullpath
end
routes.rb
devise_for :users, controllers: { sessions: "users/sessions" }
resources :events do
post :attendee, to: 'attendees#create', on: :member
post :unattend, to: 'attendees#destroy', on: :member
end
In Views, views\events\show.html.erb
<%= button_to 'Attend Now', attendee_event_path(#event), class:"btn btn-success btn-lg" %>
Found the solution !
There is actually a gem called repost, that helps you redirect to a POST action. I implemented it in my solution.
Here is the whole technique I applied. To redirect after sign-in and then complete the POST action when the login was successful:
First, I create a session session[:previous_url] for later on to be used to redirect back here.
def show
session[:previous_url] = request.fullpath #url for user to come back after signing in
...
attendee_controller.rb : we create session[:attendee]
def create
if !current_user
session[:attendee] = true
redirect_to user_session_path
else
...
app\controllers\users\sessions_controller.rb (user_session_path)
protected
def after_sign_in_path_for(resource_or_scope)
if session[:attendee]
previous_url = session[:previous_url]
session[:previous_url] = nil #clear session
previous_url #going back to event page
else
super
end
end
More info into after_sign_in_path_for method here in the Devise How-To Wiki.
previous_url will bring us back to show view from the events.
Here is where the gem will come save the day :
app\controllers\events_controller.rb
before_action :attendee?, only: :show
private
def attendee?
if session[:attendee]
session[:attendee] = nil
repost(attendee_event_path, options: {authenticity_token: :auto})
end
end
This is the gem template :
repost('url' , params: {}, options: {})
Here is the gem link: https://github.com/vergilet/repost

Rails: double render error in actions

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

Double render error when trying to auto sign in and redirect user into a specific path after registration in Devise

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.

return redirect_to in private controller method

Preface: I'm using devise for authentication.
I'm trying to catch unauthorized users from being able to see, edit, or update another user's information. My biggest concern is a user modifying the form in the DOM to another user's ID, filling out the form, and clicking update. I've read specifically on SO that something like below should work, but it doesn't. A post on SO recommended moving the validate_current_user method into the public realm, but that didn't work either.
Is there something obvious I'm doing wrong? Or is there a better approach to what I'm trying to do, either using devise or something else?
My UsersController looks like this:
class UsersController < ApplicationController
before_filter :authenticate_admin!, :only => [:new, :create, :destroy]
before_filter :redirect_guests
def index
redirect_to current_user unless current_user.try(:admin?)
if params[:approved] == "false"
#users = User.find_all_by_approved(false)
else
#users = User.all
end
end
def show
#user = User.find(params[:id])
validate_current_user
#user
end
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
validate_current_user
#user
end
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to #user, :notice => 'User was successfully created.' }
else
format.html { render :action => "new" }
end
end
end
def update
#user = User.find(params[:id])
validate_current_user
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, :notice => 'User was successfully updated.' }
else
format.html { render :action => "edit" }
end
end
end
private
def redirect_guests
redirect_to new_user_session_path if current_user.nil?
end
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
return redirect_to(current_user)
end
end
end
The authenticate_admin! method looks like this:
def authenticate_admin!
return redirect_to new_user_session_path if current_user.nil?
unless current_user.try(:admin?)
flash[:error] = "Unauthorized access!"
redirect_to root_path
end
end
EDIT -- What do you mean "it doesn't work?"
To help clarify, I get this error when I try to "hack" another user's account:
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".
If I put the method code inline in the individual controller actions, they do work. But, I don't want to do that because it isn't DRY.
I should also specify I've tried:
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
redirect_to(current_user) and return
end
end
If you think about it, return in the private method just exits the method and passes control back to the controller - it doesn't quit the action. If you want to quit the action you have to return again
For example, you could have something like this:
class PostsController < ApplicationController
def show
return if redirect_guest_posts(params[:guest], params[:id])
...
end
private
def redirect_guest_post(author_is_guest, post_id)
redirect_to special_guest_post_path(post_id) if author_is_guest
end
end
If params[:guest] is present and not false, the private method returns something truthy and the #show action quits. If the condition fails then it returns nil, and the action continues.
You are trying and you want to authorize users before every action. I would suggest you to use standard gems like CanCan or declarative_authorization.
Going ahead with this approach you might end up reinventing the wheel.
In case you decide on using cancan, all you have to do is add permissions in the ability.rb file(generated by rails cancan:install)
can [:read,:write,:destroy], :role => "admin"
And in the controller just add load_and_authorize_resource (cancan filter). It will check if the user has permissions for the current action. If the user doesnt have persmissions, then it will throw a 403 forbidden expection, which can be caught in the ApplicationController and handled appropriately.
Try,
before_filter :redirect_guests, :except => [:new, :create, :destroy]
should work.
This is because you are using redirect twice, in authenticate_admin! and redirect_guests for new, create and destroy actions.
"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."
That's the reason of the error. In show method, if you are neither the owner of this account nor the admin, you are facing two actions: redirect_to and render
My suggestion is to put all of the redirect logic into before_filter

rails devise sign_in doesn't work on redirect

I have this method:
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
if params[:mypmnode]
session[:return_to] = projects_pmnode_path(params[:mypmnode])
sign_in(#user)
end
format.html { redirect_to(session[:return_to], :notice => 'User was successfully updated.') }
format.xml { head :ok }
else
#create_company = true if params[:user][:company_id].blank? and params[:user][:company_attributes].length > 0
#create_department = true if params[:user][:department_id].blank? and params[:user][:department_attributes].length > 0
format.html { render :action => "edit" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
The idea is that if the user is updated, He is automatically signed-in and redirected to a page where authentication is required.
In this page, I have: before_filter :authenticate_user!
This doesn't work on redirect.
If I then go to another page making use of this sign_in function, then the user logs-in correctly.
Any idea why redirect doesn't work? Thx!
UPDATE:
to make it clearer, I insert the second page code (controller):
class PmnodesController < Projects::BaseController
before_filter authenticate_user!
def index
#pmnodes = Pmnode.all
respond_to do |format|
format.html
end
end
If the password is updated on #user, devise will invalidate the session. After the update_attributes, you could try calling sign_out first.
sign_out(#user)
sign_in(#user)
Are you sure that your progam goes inside this blog
if params[:mypmnode]
session[:return_to] = projects_pmnode_path(params[:mypmnode])
sign_in(#user)
end
if not this should sign in your use automatically.
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
if params[:mypmnode]
session[:return_to] = projects_pmnode_path(params[:mypmnode])
end
sign_in(#user)
format.html { redirect_to(session[:return_to], :notice => 'User was successfully updated.') }
format.xml { head :ok }
else
#create_company = true if params[:user][:company_id].blank? and params[:user][:company_attributes].length > 0
#create_department = true if params[:user][:department_id].blank? and params[:user][:department_attributes].length > 0
format.html { render :action => "edit" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
I had a similar problem:
I had a controller method that created and signed in a user
def new
#user = User.create!
sign_in #user
redirect_to some_nondefault_path
end
where some_nondefault_path required authentication. The new action did not require authentication. The user was getting created and signed in, but the user session wasn't persisting and the user was getting 401-unauthorized and redirected to the signin page instead of some_nondefault_path.
I ended up solving it by adding
skip_before_filter :verify_authenticity_token, :only => :new
to the first controller. It seemed to be trying to verify the CSRF token before creating the user session, which was failing and blocking the creation of a normal user session (even though it wasn't trying to authenticate_user!).
Hope this helps!

Resources