Rails Devise redirect after login - ruby-on-rails

I am using Devise with Rails for my user login. It all works great, however, i am unable to get a specific redirection working.
I am new to rails so the method in which i am doing things may not be correct.
Essentially, with an example, i am allowing users to like a post. However, they need to be signed in to do so. I have created an action in my controller called 'like' and the controller has the devise filter
before_filter :authenticate_user!, :except => [:index, :show]
entered thereby the sign in page is being shown. Once the user signs in i want to redirect them back to the post which they have liked with the 'like' action having been called.
my controller looks like
def like
#post = Post.find(params[:id])
#like = Like.new;
#like.user_id = current_user.id;
#like.post_id = params[:id];
respond_to do |format|
if #like.save
format.html { redirect_to #post, notice: 'You have like this post' }
format.json { head :no_content }
else
format.html { redirect_to #post, notice: 'Sorry you like was not saved }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
Naturally i cannot hard code the path using after_sign_in_path_for
def after_sign_in_path_for(resource)
#path to somewhere
end
But i have read that i can use a session variable and perhaps call that in the above code. In which part of Rails could i write the session variable, as it would be too late in the controller action (as devise takes over before it hits the like action) and i cannot see how to set it in the view.
Also my link looks like
<%= link_to "Like", {:controller => :posts, :action => :like, :id => #post.id}, {:method => :post } %> |
PS the redirection when using creating a new 'post' works ok i.e. the user signs in and are redirected to the new post view.
Any help and tips would be greatly appreciated.
Thanks

You are experiencing this because like action is specifically designed for POST. Therefore, you should make sure that user is signed in before you POST to that URL, and doing it with session is tricky:
You'd have to unprotect like method by excluding it from before_filter
Then check manually if user_signed_in? (mind you this is a helper method),
Then (if user is not signed in), stash what you are liking in session and redirect to sign in page with return URL
Upon user visiting this return URL (it will be a GET and not a POST), you would have to look up the session info and do what original Like was supposed to do (but by then user will be signed in).
Seeing that all of this dance will end with a GET request, why not make Like action work on GET requests as well as pass parameters in the Query String in the first place? It will require 0 code changes and it will not expose you to a security threat since Like is protected by before_filter. You would just have to make sure that your Like links aren't followed by search engines by using rel="nofollow" on your <a> tags.
For a related discussion, see redirect_to using POST in rails. There, one of the suggestions is to build and submit a form on the client via JavaScript. That would have to happen on that return URL view once user has authenticated. This might be the best compromise if you object to having your like action exposed as GET (which violates REST principles)

this is easy to fix:
go in application controller:
# after login redirect
after_filter :store_location
def store_location
# previous url save when its not a admin or user url
session[:previous_url] = request.fullpath unless request.fullpath =~ /\/users/ || request.fullpath =~ /\/admin/ || request.fullpath =~ /\/admin\/login/ ||
request.fullpath =~ /\/login/
end

Related

Controller redirect to different paths depending on previous page

Question:
How I want to redirect to different path after create action, given if previous_page is ../reader/new goes to ../reader/blog/:id, whereas if previous_page is ../editor/new goes to ../editor/blog/:id.
Explaination:
I want to modify the controller actions so that it can redirect to different path depending on which page it comes from. For example, I have a reader, editor and blog model. Both reader and editor can create a blog.
Here is the original blogs_controller:
class BlogsController < ApplicationsController
def create
#blog = Blog.new(blog_params)
respond_to do |format|
if #blog.save
format.html { redirect_to #blog }
else
format.html { render :new }
end
end
end
private
def blog_params
params.require(:service).permit(:title, :content)
end
end
You have a few options:
use redirect_to :back in the controller if all you need is redirecting back to the previous page
add some logic processing the HTTP referer, i.e. decide where to redirect based on request.referer in the controller
pass the redirection info in a parameter to the create action, e.g. pass params[:redirect_to] = "reader" when coming from the "reader" page and decide where to redirect based on this parameter. You can even place the whole URI to redirect into the param and just redirect to it (but this approach is unsafe as params can be mangled by users).
Generally, I'd choose the third option (parameters) as you have the best control over the redirection process (the first two options rely on HTTP referer which can also be mangled by any visitor, thus are potentially unsafe).

Do I need to validate session variables in every controller action? (Rails)

Sort of a follow-on from this question.
In my application, I have a Task model. Multiple pages can link to a single Task's "edit" url. For example, /tasks/1/edit is linked to from /tasks/1 and user/1/ (User being another model).
In my controller, I want to be able to redirect back to any one of the referring pages after the "edit" is submitted via an "update" action. For example, if I go to /tasks/1/edit from user/1/, after the "update" action I want to redirect back to user/1/. Same deal if I go to /tasks/1/edit from /tasks/1.
In my GET "edit" action I am doing:
#task = Task.find(params[:id])
if request.referer and (request.referer == task_url(#task) or request.referer == user_url(#task.user))
session[:return_to] = request.referer
else
session.delete(:return_to)
end
In the corresponding PUT "update" action I do:
#task = Task.find(params[:id])
respond_to do |format|
if #task.update_attributes(params[:task])
format.html { redirect_to session.has_key?(:return_to) ? session[:return_to] : #task #return to task if no return_to specified
else
...
end
end
This works, but I am concerned that the client could spoof/fake their session[:return_to] in the "update", allowing them to redirect to whatever page they want.
Does that matter? Is this a valid concern? Do I need to validate session[:return_to] in the "update"?
In short, yes...
request.referrer stores the URL that brought a user to your site. If you did the following:
Create a static HTML page
Embed a link to the /task/1/edit
At the end of the update, it would redirect back to the static web page.
To solve this problem, what you should do is store a session variable that denotes the location (e.g. in your UsersController, do a before_filter and set session[:return_task_update_to] = :users)
And then, at the end of the update, you redirect back, and clear the :return_task_update_to variable in the session. You would obviously also handle the case if there is no session variable (in which case you would redirect to whatever made sense as a default).
This way, you don't need to validate the URL or do something crazy. Simply set up the state in the session, and redirect if it exists.

Redirect to after successful ajax form

I've got a form with remote => true.
And right now my controller looks like:
# POST /items
# POST /items.json
def create
#item = #store.items.build(params[:item])
respond_to do |format|
if #item.save
format.html { redirect_to edit_admin_item_path(#item), :flash => {:success => "#{#item.name} was successfully created."} }
format.js { render :js => "window.location.href = ('#{edit_admin_item_path(#item)}');"}
format.json { render json: #item, status: :created, location: #item }
else
format.html { render action: "new" }
format.js { render :partial => 'fail_create.js.erb', :locals => { :ajax_errors => #item.errors.full_messages } }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
Which works but feels very clumsy. It also doesn't allow me to use a flash notice, which is sad time indeed.
Ideally I feel like I should be able to simply use "format.js { redirect_to...} or check against the request headers and redirect_to. Sheesh!
I'm not sure what the best solution is. Any advice would be super awesome, thanks in advance!
-- PS -- I know this has been asked somewhat before but to no avail: How to redirect after a successful AJAX form submission. There seems to many questions similar floating around, but no real solutions.
I think it might be impossible. The response to a Ajax request is processed by XMLHttpRequest. If a 3xx response is returned, XMLHttpRequest will follow the redirect itself, if the URL is of same origin. No matter how you set the headers, the browser cannot be aware of that. So the only way could be changing window.location with some Javascript.
I use a combination of Rails responders to generate my response messages and some content in my <action>.js file.
The content of — say update.js would look something like this:
// Checks if the article slug has changed.
// If it has the entire page should be reloaded at that new location.
<%= reload_if_slug_changed #article, params[:id] %>
// Displays the flash notices
// See ApplicationHelper#js_flash_response
<%= js_flash_response %>
Where the different methods are defined in some helper (in my case my ApplicationHelper). The content of the different methods are as follows:
def js_flash_response
if flash.now[:notice].present?
js = "$('#notice').html('#{flash.now[:notice]}').change();"
elsif flash.now[:alert].present?
js = "$('#alert').html('#{flash.now[:alert]}').change();"
end
end
def reload_if_slug_changed object, expected_value
"window.location.href = '#{url_for [:edit, object]}';" if object.slug != expected_value
end
The content of the flash messages are generated automatically by Rails responders and displayed with the now scope that deletes the from the flash hash, ensuring that if the user reloads (after the flash has been displayed) they will not reappear.
I don't believe that you should ever make a form pointing to a restful create action a remote one, because you would always expect critical redirect, so in my case I only need to redirect if the url slug has changed.
I hope that this helps. It's not a solution, but simply the way that I handled some of the same problems.
Best regards.
Under your scenario, here's how I would inject javascript into the page from a controller action. After you've completed the logic section of your action insert something like this:
render :update do |page|
page << "javascript_here"
end
This should allow you to insert you window.location or create a javascript flash method and call it when your create method executes correctly.
If you're looking to DRY up your controller actions, I would recommend looking into this Railscast about make_resourceful. Make_resourceful automagically performs each core activity for each action. It also allows you to tap into the hooks that they've created such as before :create, after :create, response_for :create, and after :create_fails. By using this gem, you can run code based on the success or failure of your methods and have finer grained control over them.
In addition to this, you should be able to initialize a create.js.erb and create_fails.js.erb in your view file, include a format.js without anything passed to it in your controller, and Rails will automagically run that file that contains javascript depending on if the controller action executed successfully.

Devise - Authenticate user (after validations) on a create action

Using Devise, I know how to protect controller actions from non-signed-in users through:
before_filter :authenticate_user!
In order to illustrate what I am trying to achieve, please see an example:
I have the following controller: (a project belongs to a user)
projects_controller.rb
def create
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
What I am looking for is a way that users can interact more with the website before having to sign up/sign in. Something like:
after_validation :authenticate_user!
if the user is not signed in, and redirect him after success (sign up/sign in) to the "project" show page.
Things I thought:
1.) Change the controller in order to accept a project object without user_id, ask for authentication if the user is not signed in, then update attributes with the user_id
I try to do it like this first and it results to a very ugly code. (Moreover authenticate_user! doesn't redirect to the #project which lead to more customization)
2.) Create a wizard with nested_attributes (project form and nested new registration form and session form)
3.) Something better? (a custom method?)
It seems authologic manages this more easily. I'm not sure it is a reason to switch so I would like to have your idea/answer on this. Thanks!
EDIT
references: Peter Ehrlich answer comment
CONTROLLER WITH VALIDATIONS LOGIC
projects_controller.rb
def create
unless current_user
#project = Project.new(params[:project]) # create a project variable used only for testing validation (this variable will change in resume project method just before being saved)
if #project.valid? # test if validations pass
session['new_project'] = params[:project]
redirect_to '/users/sign_up'
else
render :action => 'new'
end
else
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
end
def resume_project
#project = current_user.projects.new(session.delete('new_project')) # changes the #project variable
#project.save
redirect_to #project
end
routes
get "/resume_project", :controller => 'projects', :action => 'resume_project'
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
def after_sign_in_path_for(resource)
return '/resume_project' if session['new_project'].present?
super
end
Something like this should work:
def create
unless current_user
session['new_project'] = params[:project]
redirect_to '/register'
return
end
# and on to normal stuff
# in your devise controller
def after_sign_in_path
return '/resume_project' if session['new_project'].present?
super
end
# back in projects_controller now
def resume_project
#project.create(session.delete('new_project'))
# you know the drill from here
# I'd also put in a check to make an error if the session is not set- in case they reload or some such
Keep in mind that session is a cookie in the browser, and thus has a size limit (4kb). If you're posting images or other media, you'll have to store them temporarily server-side.
Another option would be to create a userless project, and use a similar technique to allow them to claim it as their own. This would be nice if you wanted unclaimed projects displayed to all to be available as a flow.
I haven't tested it out, but it should be possible to store the action the user was going to, I.e. create, with the params hash that was submitted and redirect to it upon successful login. It would then handle the error cases as normal.
Have you tried that?

How to modify the create & update method in RoR?

I know that the after the create or update methods is done, they have the method like this
respond_to do |format|
format.js
end
Since I change my form from remote form to non remote form, so I won't use the format.js anymore, I just want to refresh the page, after the user create/update a product, so I have this code:
respond_to do |format|
page.reload
end
But it don't work, so I try not to use respond_to do, I only have the page.reload. But it also show me the site like this:
http://localhost:3000/products/50
I just want to reload the page after I create/update, why I can't do it in this way?
The reload method reloads the browser's current location using JavaScript. I would suggest that you probably want to do a server-side redirect after creating or updating your resource. Something like one of these alternatives:
redirect_to product_path(#product) # Redirect to the 'show' page for the product
redirect_to products_path # Redirect to the products' index page
How can I assign the product_path? in routes.rb?
in routes.db you can either:
map.product 'products/:id', :controller => 'products', :action => 'view'
map.resources :product
map.resource :product
1) will give you product_path(123) and product_url
2) will give you product_path, new_product_path, edit_product_path and so on
HTH

Resources