Rails 5 redirect with strong parameters - ruby-on-rails

Controller:
def foo
redirect_to home_path(params: auth_params)
end
private
def auth_params
params.require(:auth).permit(:name, :email, :photo_url, :provider, :provider_id)
end
Here are the parameters that the controller errors out on:
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"...",
"auth"=>
{"name"=>"...",
"email"=>"...",
"photo_url"=>"...",
"provider"=>"...",
"provider_id"=>"..."}
}
With this error: unable to convert unpermitted parameters to hash
I permit all the params (with the exception of the rails default params (i.e. utf8, authenticity_token, so why is this error popping up?
EDIT:
Neither of the following suggestions are working either:
redirect_to home_path(auth_params)
redirect_to home_path, params: auth_params
A little more info on how I get to the controller action might help:
I have (in the same controller) an action bar that has a form_tag in the view that goes to foo_path. I've tried submitting the form via jQuery or a submit button, it doesn't change the result. As soon as I hit the auth_params method I get the error.
This means it is erroring out on the auth_params method rather than the redirect itself

I have get this error too when I have upgrade to Rails 5.1.4. I fixed with this
redirect_to(home_path, params: params.require(:auth).permit(:name, :email, :photo_url, :provider, :provider_id))
I hope this help you.

After I restarted the development server AND cleared all the cookies placed by rails (most notably the app-name session cookie), the normal strong parameter syntax (i.e. params.require().permit()) works fine.

The home_path and other url_for related methods are expecting a Hash object instead of an ActionController::Parameters object (which is what the params method in controllers returns) so you need to convert it to a hash first, like so:
redirect_to home_path(auth_params.to_h)

You´re doing it very wrong. If you are creating a resource then you should reply to validation errors by rendering a view - not redirecting.
The response you´re sending the user is the result of performing a non-idempotent action.
This might seem like nitpicking but outing the users email & chosen password in the GET parameters is pretty reckless.
If you are embedding a form in your homepage its pretty simple to just create a auths/new.html.erb view which just shows the form and lets the user correct the misstakes:
class AuthsController
def create
#auth = Auth.new(auth_params)
if #auth.save
redirect_to some_path, success: "Your account was created"
else
render :new
end
end
private
def auth_params
params.require(:auth).permit(:name, :email, :photo_url, :provider, :provider_id)
end
end
If the form has to be seamlessly included in the home page then you need to render that view or use ajax to perform it in place.

Related

How to pass objects from one controller to another controller

On an unsuccessful save, I would like to redirect to the previous view but with the error message.
redirect_to user_path(#user_id), errors: #user.errors
but in the view, when I check for errors I get an undefined variable errors.
I am not using the same controller new and create, so I can't have #user.errors.any in new.html.erb. I have two different controllers, one in which form is there, and another controller which will take care of create, if the create is not happening I need to redirect to the previous controller.
You may need to use render instead of redirect_to.
Something like this:
# controller_1
def step_1
#user = User.new
#user.do_something
...
end
# controller_2
def step_2
if #user.save?
# redirect to another...
else
render 'controller_1/step_1`
end
end
Then on view step_1.html.erb, you can print out errors of #user with #user.errors.
You have to pass the parameters inside the redirect_to helper like below,
redirect_to user_path(id: #user_id, error: #user.errors.messages)
Please check the rake routes and pass the appropriate key for id, whether it's :id, or :user_id

How do you access params[:model][:field] in Rails 4?

From what I understand...
If you have a form_for #model, params[:model] is available when the form is submitted. Furthermore, if the form has 2 attributes attr1 and attr2, params[:model][:attr1] and params[:model][:attr2] are available when the form is submitted.
In Rails 4, you're supposed to write a method model_params that says params.require(:model).permit(:attr1, :attr2).
You'd then use model_params like so: Model.new(model_params).
However, what do you do if you only need one of the fields from the form? Like if you only needed params[:model][:attr1]?
Example:
def create
#user = User.new(user_params)
if #user.save
# need access to params[:user][:password] here
redirect_to root_url, :notice => "Signed up!"
else
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
The gem responsible for this behaviour is strong_parameters. The permit() method decides what to pass on to the model based on the attributes you pass to it.
In your case, you passed it :attr1 and :attr2:
params.require(:model).permit(:attr1, :attr2)
This means the model will have attr1 and attr2 set to whatever values were passed from the form.
If you only wanted to set attr1, just remove attr2 from the permit() call.
params.require(:model).permit(:attr1)
You model will not have attr1 set, but not attr2. It's that simple.
You can even permit everything (not recommended) by calling permit! with a bang and no arguments.
You can read more about this behaviour on the gem's Github project page.
Update based on the OP's edit
If you need access to params[:user][:password] in the controller... well, you just accessed it in your example. You accessed it by typing params[:user][:password].
Nothing prevents you from accessing the params hash directly. strong_parameter's job is to prevent you from mass assigning a hash to a model, that's all.

How to prevent rails from changing the path when the edit action is rendered from update action

As far as I'm aware this is the standard rails pattern for editing and updating a resource.
I have a GET route for the edit action on /users/:id/edit and both a PUT and PATCH route for the update action on /users/:id.
Here are my controller actions:
def edit
#user = User.find params[:id]
end
def update
#user = User.find params[:id]
if #user.update_attributes(user_params)
redirect_to #user, success: "Changes saved"
else
flash.now.alert = "Unable to change account details"
render :edit
end
end
And I'm using the default form_for in my form partial:
<%= form_for #user do |f| %>
which is creating the following form in the html:
<form accept-charset="UTF-8" action="/users/1" id="edit_user_1" method="post">
...
<input name="_method" type="hidden" value="patch">
When the call to update_attributes succeeds the user is being redirected correctly, however when the call fails (due to ActiveRecord validations) the render action displays the edit view correctly, but the path in the user's browser changes from /users/1/edit to /users/1.
From what I've read it appears this is the expected behaviour of rails, but that seems confusing to me as I thought the core idea behind REST was that a URL referred to a canonical view of a resource?
If I redirect_to to the edit action instead of render then the url is as it should be, however I lose the error messages on the form.
Is there a more sensible way to persist the URL and the error messages than dumping the errors into the session before using redirect_to? I'd like to avoid that if I can.
Update:
The reason I want the URL to persist (as well as the errors) is because I'm using current_page? to set active states in the navigation. Therefore the 'Edit' action in the menu loses it's highlighting if the form is submitted with errors.
The reason you were in /users/1 is the view is rendered by #update, not #edit.
By default resource the path of #update is same as #show, with different request method GET and PUT. So, when #update renders HTML response, it's same as #show.
I think the result is acceptable, no matter what path your are in the result is expected. As you already found, if using redirect, the #user instance will be changed to a branch new one thus losing errors and previous fillings.
There is workaround on the path such as using session to pass instance variable, but I don't think it worth the effort. The current result is good enough.
Add
Manipulating active states is easy. I don't like current_page? which needs too much code. Use controller_path and action_name instead.
if controller_path == 'users' && (action_name == 'edit' || 'update')
# add active class
end
You should be able to redirect to the edit URL and avoid losing the error message by replacing
flash.now.alert = "Unable to change account details"
with
flash.alert = "Unable to change account details"

Ruby on Rails controller design

When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.

where does the url routes get resolved when you call render in rails controller actions

Is there any method that i should look at in rails3.2 source code so as to know where the navigation or the url part of the render call get resolved?
The reason is, i have a small app in which url is of the form
www.example.com/bob/edit
the above route as it suggests renders the edit form.EDIT: i was able to get to this route by modifying response on the link_to helper.
def update
#when validation passes
redirect_to #user
#when validation fails
respond_to do |format|
format.html {render :action => "edit"}
end
end
Now the problem is when a validation error occurs on submission to update action of users_controller,
the url becomes
www.example.com/users/bob/edit
config/routes.rb
get "users/new", to: => "users#new"
resources :users
as you can see there's nothing interesting happening in routes,
in models/user.rb
def to_param
"#{name}"
end
in views/edit.html.erb
form_for(#user) do |f|
end
Observation: here when the form is rendered afresh, form 'action' points to "users/bob" but when the form is re-rendered 'cos of validation error, form action mysteriosly changes to "users/" which is weired and if i remove the to_param in user.rb model it works fine
Though its not such a big deal, i was thinking where, if i needed to override the url that is generated on render call, to change?????
Any suggestions and pointers to explore are wecome....
I'm not sure how you're getting the URLs you're getting, but a general answer to your question would be it doesn't. The URL you see after sending a request is the URL the request was sent to (or redirected to), not that of the page you came from, nor that of the template you render in the end. In your case, I'm guessing the problem is that you created a custom URL for the edit page, but not for update, and your form_for(#user) is sending the request to your update URL (probably PUT "/users/bob").
To fix this, the first thing is to create your custom update route. Maybe something like:
put ":id/update", to: => "users#update"
And then have your form_for use that URL:
form_for(#user, :url => "#{#user.to_param}/update")

Resources