refactoring rails redirects with multiple ifs - ruby-on-rails

In the app I am working on, the user can get to a new form from several different places. I need to redirect them back to where they came from. I solved it by passing a parameter in the link on the view, for example
= link_to "create a new post ยป", new_user_post_path(#current_user, :return_to => 'dashboard')
then in the new action in the controller like so
if params[:return_to] == 'dashboard'
session[:return_to]= 'dashboard'
end
now I am looking at the controller. what I need to do I can achieve by being really verbose, but I'm looking for a more elegant solution. Here is what I have now
if #user_post.save
flash[:notice] = "New Post Created"
if session[:return_to] == 'dashboard'
session.delete(:return_to)
redirect_to dashboard_path and return
else
redirect_to user_hub_path(#user)
end
else
flash[:error] = "Could not save post"
redirect_to new_user_post_path(#user)
end
This does what I need but I was hoping to tidy it up a little. I started looking at 1 line enumerators to see if I could do something like this...
if #user_post.save
flash[:notice] = "New post Created"
session[:return_to] == 'dashboard' ? redirect_to "#{session.delete(:return_to)}_path" : redirect_to user_hub_path(#user)
else
flash[:error] = "Could not save post"
redirect_to new_user_post_path(#user)
end
but it really doesn't like this part....redirect_to "#{session.delete(:return_to)}_path"
any advice? Guess I'm trying to get it to redirect and delete the session in one line and this is beyond my knowledge...

This looks like a good case for using Rails' built-in :back parameter, which, according to the Rails docs here, sends the user "Back to the page that issued the request. Useful for forms that are triggered from multiple places. Short-hand for redirect_to(request.env["HTTP_REFERER"])"
You can avoid passing a return_to param, and change your controller code to:
if #user_post.save
redirect_to :back, notice: "New Post Created" and return
else
redirect_to new_user_post_path(#user), flash: { error: "Could not save post" }
end

What about using send?
send takes a message name and optional arguments and forwards that to send's receiver (http://ruby-doc.org/core-2.2.0/Object.html#method-i-send)
path = send("#{session.delete(:return_to)}_path")
redirect_to path
In this case it sends the message to the current controller instance (same as just calling the ..._path method but gives the flexibility of the dynamic call)

Please instead of using a redirect_to variable of your controller, use the Referer Header.
HTTP referer (originally a misspelling of referrer) is an HTTP header field that identifies the address of the webpage (i.e. the URI or IRI) that linked to the resource being requested
In Rails is quite simple inside a controller
redirect_to request.referer
That is more elegant and efficient, because the session cookie storage can be expensive (cache storage) and could affect your HTTP cache proxies performance.

Related

how to prevent a DELETE HTTP request from succeeding in this situation?

Rails beginner here..
I have a users resource where I implemented a callback that's supposed to prevent an admin user from deleting herself.
before_filter :admin_no_delete, only: :destroy
def admin_no_delete
admin_id = current_user.id if current_user.admin?
redirect_to root_path if params[:id] == admin_id
end
If this looks familiar to some, it's from Michael Hartl's rails tutorial, exercise #10 here but I tried to do it differently, not as he suggested.
My (lame) test for this fails
describe "deleting herself should not be permitted" do
before do
delete user_path(admin)
end
it { should_not redirect_to(users_path) }
end
But exposing a delete link for the admin user just to test and clicking on that link, it seems like the callback actually succeeds in executing (redirecting to root_path).
I was able to invoke the destroy action using jQuery to delete the record being protected by the callback (using Web Inspector's javascript console):
$.ajax({url: 'http://localhost:3000/users/104', type: 'DELETE', success: function(result){alert(result)} })
Looking for ideas on how to prevent a DELETE HTTP request from succeeding in this situation.. also any ideas on how to properly test for this kind of situation?
Thanks.
Simple: params[:id] is a string, while admin_id is a Fixnum. You can just change it as follows and it should work:
redirect_to root_path if params[:id].to_i == admin_id
The logic you're using seems a little odd to me, though. Why use a before filter if it's just for one action, and why change the redirect? I think the logic should be directly in the destroy action and look something like this:
def destroy
unless current_user.admin? && current_user.id == params[:id].to_i
User.find(params[:id]).destroy
flash[:success] = "User destroyed."
end
redirect_to users_path
end
You're comparing admin_id, an integer with params[:id]. Values in params are always strings (or arrays/hashes containing more strings) so the comparison will always fail.

flash notice is lost on redirect, how to find out what's removing it?

There are many posts on SO about this ( respond_with redirect with notice flash message not working Why is :notice not showing after redirect in Rails 3, among others) , I've read at least 4 and still can't solve this issue.
I've got a portion of my site that lets people do some things before they create an account. I prefer this from a UX perspective. So they're allowed to do X and Y then they get redirected to the "Create account" page (uses Devise).
The redirect looks like:
if userIsNew
... stow information in a cookie to be retrieved later ...
redirect_to "/flash", flash[:notice]
=> "Ok, we'll get right on that after you sign up (we need your email)."
and return # this has to be here, since I'm terminating the action early
end
So "/flash" is a plain page that I made to test this. It doesn't do anything, has no markup of its own, just has the basic html from the application.html, which has this line in the body:
<% if flash[:notice] %>
<p><%= notice %></p>
<% else %>
No notice!
<% end %>
It says 'No notice' every time.
I have tried:
adding in a flash.keep to my before_filter in the static controller
using :notice => instead of flash[:notice] =>
putting the notice in a cookie and pulling that text out of the cookie and into a flash in the before_filter of my application controller
redirect_to :back with the flash[:notice] =>
It's either
flash[:notice] = 'blablabla'
redirect_to foo_url
or
redirect_to foo_url, notice: 'blablabla'
I'm overriding ApplicationController#redirect_to to call flash.keep so that any messages are persisted on redirect without having to explicitly call flash.keep in my controller actions. Works well so far. Haven't had a scenario yet where unwanted messages are persisted.
class ApplicationController < ActionController::Base
def redirect_to(*args)
flash.keep
super
end
end
Let me know if there are any scenarios where this isn't a good solution.
I have been fighting with the same problem for some time and none of the posts seemed to help.
It turns out that - like usually it happens - the the problem was in my code. I did have a "redirect_to" that I forgot about, which was clearing the flash.
Namely, "root_path" for me was served by the StaticPagesController's home method. "home" was doing some checks and then redirecting you to the user_path.
In my code I had in numerous places
redirect_to root_path, :flash => {error: #error}
These redirects were never displaying the flash because my hidden "home" controller serving the "root_path" was making another redirect that cleared the flash.
Therefore my problem was solved when i added the "flash.keep" in my "home" controller method
def home
if current_user
#user = current_user
flash.keep
redirect_to #user unless #user.no_role?
end
end
Faced the same problem, flash just disappeared after any redirect, nothing helped, then, I found that it was switched off...
Check your /config/application.rb for this:
config.middleware.delete ActionDispatch::Flash

Rails flash :notice doesn't work

I have this code :
def create
login(params[:email], params[:password])
if current_user
flash[:notice] = "Welcome back #{current_user.email}"
return redirect_to_first_page
else
flash[:notice] = "Email or password is wrong. Try again !"
redirect_to root_url
end
end
when the login is successful the flash is set and the redirect to the first page is made.
This part is working. The second part is not setting the flash notice message. Then when the page is displayed no message from flash is show. What is different i've try to have
return redirect_to root_url
but nothing still not showing anything.
In my controller i have a helper like flash_notice all it does is return flash[:notice].
This is because the flash is always empty in a view but accessible in controller.
In the view i have just one line :
<%= flash_notice %>
I'm using rails 3.1
Chris Drappier is correct, the flash hash is current for one request only. You can invoke a "keep" method with
flash.keep[:notice]="This message will persist"
Personally, I like to keep the flash in question in params when needed. The gory details are here:
http://guides.rubyonrails.org/action_controller_overview.html#the-flash

How to route RESTfully with multiple entry points?

I have a model called Project, which is a collection of information stored by a Company. This company can create projects two ways.
The first is the standard RESTful way - the company goes to the Project index, then clicks 'New Project', and upon creation is returned to the index with a flash message.
The second is a 'quick create' that can be accessed when a company is looking at a client's profile. From here, the company can enter some basic information and send this off to create a project with that client (the client is specified automatically here).
The second of these two scenarios has a project being accessed from clients/show. Sending this data to projects/create would ordinarily route the company to projects/index, but I don't want that. In this case, the create action is meaningfully different in that certain fields are treated differently, and the redirect is also different. What would you suggest I do?
Build an alternative 'create_from_client' action in projects.
Build a 'create_project' action in clients.
Send a parameter to projects/create and set client_id and redirect to client/show if that parameter exists.
Something else I'm not aware of.
Thanks!
You can leverage the referrer directly from the Request object and fork based on that, similar to how redirect_to :back works.
From the Rails API docs for the redirect_to options hash:
:back - Back to the page that issued the request.
Useful for forms that are triggered from multiple places.
Short-hand for redirect_to(request.env["HTTP_REFERER"])
So you can simply do something like this:
def create
#project = Project.new( params[:project] )
#project.save
respond_with #project, location: get_location!
end
private
def get_location!
case request.env["HTTP_REFERER"]
# Your routing logic here.
end
This is nice and easy to unit test, too, if you're into that. :)
context "if the user came from the regular form" do
before { controller.request.env["HTTP_REFERER"] = "regular_form_url" }
it "redirects to the index path" do
post :create
response.should redirect_to :index
end
end
context "if the user came from the quick-create form" do
before { controller.request.env["HTTP_REFERER"] = "quick_create_url" }
it "redirects to some other path" do
post :create
response.should redirect_to some_other_path
end
end
I would just add another action to the controller, 'quick_create' or whatever. You can dry out the form with partials and parameters to the partial to tell how to render things...This just seems like the easiest way.
I've got this semi-rational (or is that semi-irrational) hang up against leveraging the referrer...
I ussualy add hidden referer field with current URL then redirect to it
For example
def create
#project = Project.new params[:project]
#project.save
respond_with #project, :location => params[:referer] || [:index, :projects]
end

How to return failed validation parameters to a rails model form that doesn't know what page it's on?

I have a small contact request form that appears on nearly every page in a site.
The form submits to the RequestsController create method. If all goes well, the request is saved in the Requests table and an e-mail is sent. If validation fails, I would like to redirect back to the referrer page.
Here is my create action...
def create
#request = Request.new(params[:request])
#location = Location.find(params[:location_id])
if #request.save
PodsMailer.contact_request(#request, #location).deliver
PodsMailer.contact_confirmation(#request, #location).deliver
flash[:notice] = "Thanks! Your message was sent. We will contact you shortly."
redirect_to :back
else
flash[:notice] = "Errors -- please check the form"
flash[:errors] = #request.errors
redirect_to ??????
end
end
My question is when validation fails, how do I redirect back to the referrer AND send the values entered into the form by the user? I can't use a named route because I don't know where the request is coming from.
"redirect_to :back" works fine by itself. But I can't figure out how to send parameters. The flash hash makes it through just fine. So there's got to be something simple I'm missing.
I have tried passing a params hash (:params => params[:request]) to every combination I can imagine of redirect_to with :back, url_for, request.referer... but with no luck.
I imagine there is some way to manually build the url and tack on query parameters, though I'm not exactly sure how to do that. But I was wondering if there is a Rails trick for passing parameters in this situation. Also, I'd like to implement a server-side solution before adding an AJAX solution.
Any help would be much appreciated. My first question here. Hopefully I've done this correctly.
Thanks--
Best here is to use AJAX and validation on client (that don't mean to deny server vilidation).
But for you I suggest this hacky solution:
def create
#request = Request.new(params[:request])
#location = Location.find(params[:location_id])
if #request.save
PodsMailer.contact_request(#request, #location).deliver
PodsMailer.contact_confirmation(#request, #location).deliver
flash[:notice] = "Thanks! Your message was sent. We will contact you shortly."
redirect_to :back
else
flash[:notice] = "Errors -- please check the form"
flash[:errors] = #request.errors
referer = request.referer.gsub(/\?.*/, "")
redirect_to referer + "?" + params[:request].map{|k,v| "request[#{k}]=#{v}"}.join("&")
end
end
But now you should in your request form do something like this:
<%= form_for Request.new(params[:request]) do |f| %>
...
or remove it into controller

Resources