Rails - Redirecting after Form Errors - ruby-on-rails

I'm very new to rails, so please forgive my limited knowledge.
I have a controller called users. It has two methods: new and create.
When users#new is called, a form is shown to sign up for an account on my site. I have set up a route for this which makes the URL /signup, like so:
match "signup" => "users#new", :as => "signup"
When the user navigates to /signup, I create a new user instance variable and show them the form, like so:
UsersController
def new
#user = User.new
end
New View
<%= form_for #user do |f| %>
<!-- Form code here... -->
<!-- Then at the end: -->
<%= f.submit :value => 'Sign Up' %>
<% end %>
When the user submits this form, it sends the data to users#create.
My code for users#create in UsersController looks like so:
def create
#user = User.new(params[:user])
if #user.save
redirect_to root_url, :notice => 'Signed Up!'
else
render "new"
end
end
The if/else statement is to check if rails was able to create my new user or not. If it was, it redirects to the index no problem.
If it wasn't able to create the user, it renders my new view, and it displays the errors fine.
But, the URL it then gives to us is /users, because when it submits the form it submits to /users. How can I get it so if the signup fails, it will redirect to /signup, and still show the errors that occured?
UPDATE: routes.rb
Flightdb::Application.routes.draw do
get "users/new"
get "home/about"
get "home/index"
root :to => 'home#index'
match 'about' => 'home#about'
match "signup" => "users#new", :as => "signup"
resources :users
end

Well the answer you don't want to hear is that this usually isn't done. The semantics of the URLs aren't ideal either way. /new implies a fresh new form... but a form with errors is sort of a "partially created" user. The user will never need to use the URL in either case, so no functionality is lost.
Also, consider putting registration and authentication actions on an 'account' (singular) resource. the 'users' controller/resource should probably only be for a backend admin interface. if there are public proiles per user, put them on a 'profiles' resource. put the user's dashboard on a 'dashboard' controller (not a resource).

I've come across the very same issue today and based on Alex's and Marian's comments, I ended up with these changes:
1) in form view:
<%= form_for #user, url: signup_path do |f| %>
2) in routes.rb:
get "signup" => "users#new", :as => "signup"
post "signup" => "users#create"
resources :users
root :to => "home#index"
I'm a newbie in RoR so I'd welcome comments if there are any side-effect or issues. Or if there is some better way.

the route is indeed corrected this way, but the context of the corresponding controller action "users#new", such as variables that have been initialized by the action, is lost.
So we end up in a kind of unstable situation where we're neither in the "new" context nor outside of it...
How to control that context is the question ? Maybe through ActiveModel::Validator
I'm not sure where and how to alter this behaviour...

Related

Rendering 'new' leads to a different url

When handling validation errors in a User form on my rails project, I have the instruction render 'new' if the user is not valid.
However, when this occurs, the url in the search bar changes.
Originally, it's https://localhost:3000/signup but when the user submit the forms and the render 'new' occurs, the URL becomes https://localhost:3000/users
Why is that?
Here's my routes.rb
Rails.application.routes.draw do
# Resources
resources :users, only: [ :new, :create ]
# Application root
root 'static_pages#home'
# Static pages
get '/help', to: 'static_pages#help'
get '/contact', to: 'static_pages#contact'
# Users
get '/signup', to: 'users#new'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Thanks,
In the Rails REST conventions you use different URI's to display the form to create a resource and for the forms action attribute which you POST to.
Lets say you have:
resources :pets
This gives us:
GET /pets/new => pets#new
POST /pets => pets#create
We then have a typical form:
# posts to /pets
<%= form_for(#pet) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
And a run of the mill controller:
class PetsController < ApplicationController
def new
#pet = Pet.new
end
def create
#pet = Pet.new(pet_params)
if #pet.save
redirect_to #pet
else
render :new
end
end
# ...
end
So when the user visits /pets/new and fills in the form the browser url should read /pets if the form is invalid. This is the intended behavior - posting to a different URL avoids a myriad of cache and history related issues.
From a restful standpoint its also correct - you're no longer viewing the new action - you're viewing the results of attempting to create a resource. The former is idempotent - the latter is not.
You need to recognize that the line render :new is short for render template: 'pets/new'. It does not redirect and it does not call the new action in the controller. It simply renders the template and returns it as the response to the current request.
The reason this is happening is because of the way Rails handles routes. Most likely, the form on your signup page has something like:
<?= form_for #user do |f| ?>
If you look at the generated HTML you'll notice that the form is POSTing to the url '/users'. So when the signup form is submitted, the app is going to redirect you to /users.
This is the normal behavior in Rails. (see Rails Guides)
If you want the URL to look like /signup you can add a named route for it:
# Users
get '/signup', to: 'lead_magnets#new'
post '/signup', to: 'lead_magnets#create'
Then in your signup form you'll need to explicitly reference the new signup path:
<?= form_for #user, url: signup_path do |f| ?>
The generated HTML should look like
<form class="new_user" id="new_user" action="/signup" method="post">
You need to add
post '/signup', to: 'users#new'
Did you try to include recognize path?
Rails.application.routes.recognize_path
The reason for this is because when you submit your form, you're submitting it to some action from your new view. The action that the form submits to has a path, and you can see what the path is by typing:
rake routes
in your console and searching for the create action. When your validation fails in the create action, you call render 'new'.
When you render a template, the url of the page being rendered will be the url / path of the controller action that rendered the template.
In your case, whatever action you have that calls render 'new' will be the one determing the url once your template is rendered.

Routing error after using :path option

I have a model called 'users' that I changed to 'people' in the url.
Routes.rb
resources :users, :path => "people"
Everything works fine except when creating a new user I get a routing error and it redirects to '/users', instead of creating the user and going to '/people/:id'.
No route matches [POST] "/users"
If I take
:path => "people"
out of the routes it works fine.
The form looks like:
<%= simple_form_for(#user) do |f| %>
and here is the controlller:
def create
#user = User.create(user_params)
if #user.save
redirect_to #user
else
redirect_to root_path
end
end
What is happening is in your route file, you are changing what the users route is routing to with your path: 'people' call.
However, since rails sees that the model you're creating is called User, it assumes that it should post that to /users.
While, I'm not completely familiar with simple_form, this works with normal rails:
<%= form_for #user, url: users_path do |f| %>
Notice the url: users_path option, what this does is explicitly link the form to the user path, aka /testers.
--Cheers

Rails routing, redirects to wrong url when creating a new session

I' trying to create a simple login form where users should be able to sign in. I want it to redirect to the users show action, but instead it redirects to .../sessions, I want it to redirect to `.../users/1. I have a session controller that looks like this (I have been following Rails Tutorial Book)
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
sign_in user
redirect_to user
else
render 'new'
end
end
I have tried to change redirect_to userto redirect_to users_path(user) but it doesn't work.
My routes looks like this:
root :to => 'pages#home'
resources :users
resources :sessions, only: [:new, :create, :destroy]
match '/signup', to: 'users#new'
match '/signin', to: 'sessions#new'
match '/signout', to: 'sessions#destroy', via: :delete
And sessions/sessions.new.html.erb looks like this:
<%= form_tag sessions_path do %>
<%= text_field_tag :email, params[:email], :placeholder => "E-post" %>
<%= password_field_tag :password, nil, :placeholder => "Lösenord" %>
<%= submit_tag "Logga in" %>
<% end %>
My rails skills is a bit rusty, so it's probably something basic.
Update
I managed to "solve" it by creating a index action in my session controller which redirect to the correct path when getting called. I don't' know (or think) this is the most optimal way to do it. The original problem was that when a user signed in, it for some reason got redirected to /sessions, and not the root path which I want. When or if the user updated the browser the application crashes (since I didn't have a index action in my session controller). I don't know why it isn't redirecting to the right path, I did also try redirect_to user_path(user), but with the same result. Any tips on how to solve it the "right" way, would be great! I also had the same problem when users signed up, it redirected to /users instead of /.
Check what rake routes returns to you, and you should find there the route for a user page. You should be using something like
redirect_to user_path(user)
It sounds like your action is falling to the else condition and consequently doing the render :action=>"new" and not the redirect you're looking for. In the case of the render action, the url would show something like you've described ".../sessions" since the form is likely (and correctly) doing a POST to that url but has failed and is re-rendering without redirecting.
In other words, I think your user.authenticate(...) is probably failing or for some reason returning nil or false.
UPDATE:
Since you're using jquerymobile, the page that you redirect to is being AJAXed in. The documentation (see the Redirects and linking to directories section) says that you can use a data-url attribute on the div that has the data-role as page to control the url. So, for your example, wrap the content in app/pages/home in a 'page roled' div tag with the data-url set for whatever you want...for example:
<div data-role="page" data-url="/home">
<% if signed_in? %>
<%= render "signed_in"%>
<% else %>
<%= render "home_page" %>
<% end %>
</div>

Rails routing error: app can't perform POST

I'm working through Ryan Bates' Railscast #124: Beta Invitations. I've got all the code in place, but I haven't been able to actually get things working. When I try to send an invite email, I get this message.
Routing Error
No route matches [POST] "/invitations"
If I pluralize the resource's name in Routes.rb, I get a different routing error.
Routing Error
uninitialized constant InvitationsController
What am I doing wrong?
Here's my Routes.rb file.
resources :users, :invitation
resources :sessions, :only => [:new, :create, :destroy]
match '/hunts', :to => 'hunts#index'
match '/signup/', :to => 'users#new'
match '/signin', :to => 'sessions#new'
match '/signout', :to => 'sessions#destroy'
match '/contact', :to => 'pages#contact'
match '/about', :to => 'pages#about'
match '/help', :to => 'pages#help'
root :to => "pages#home"
match ':controller(/:action(/:id(.:format)))'
end
And my Invitation Controller.
class InvitationController < ApplicationController
def new
#invitation = Invitation.new
end
def create
#invitation = Invitation.new(params[:invitation])
#invitation.sender = current_user
if #invitation.save
if logged_in?
Mailer.deliver_invitation(#invitation, signup_url(#invitation.token))
flash[:notice] = "Thank you, invitation sent."
redirect_to root_path
else
flash[:notice] = "Thank you, we will notify when we are ready."
redirect_to root_path
end
else
render :action => 'new'
end
end
end
Update: Here's the info requested.
Views/invitation/html.erb
<%= form_for #invitation do |f| %>
<p>
<%= f.label :recipient_email, "Friend's email address" %><br />
<%= f.text_field :recipient_email %>
</p>
<p><%= f.submit "Invite!" %></p>
<% end %>
rake routes is a very useful tool which you can use to see all the routes defined for your application.
You have added resources :invitation which defines the following routes
invitation_index GET /invitation(.:format) invitation#index
POST /invitation(.:format) invitation#create
new_invitation GET /invitation/new(.:format) invitation#new
edit_invitation GET /invitation/:id/edit(.:format) invitation#edit
invitation GET /invitation/:id(.:format) invitation#show
PUT /invitation/:id(.:format) invitation#update
DELETE /invitation/:id(.:format) invitation#destroy
Note that you are calling the InvitationController's actions.
So nothing is wrong with your route -> controller mapping.
You are just posting to a non-existent route. When you pluralize the route's name, you end up having a non-existent controller (InvitationsController).
Just change the URL you're posting to and you're good to go.
Try to use the plural when you call resources in your config/routes.rb:
resources :users, :invitations
This happens because you pass an instance of the Invitation model (#invitation) to this helper, it pluralize the class name to know where to submit.
Moreover, since #invitation is not yet saved in the DB (#invitation.new_record? returns true) then form_for set the form's method to "POST".
This information means the POST request to 'invitations' is processed by "invitations#create" (The create method of the InvitationsController class). It's convention over configuration, if you want to access invitations in a RESTful way and use resources in your config/routes.rb things must be named in a certain way to work out of the box (or you could simply override the "action" attribute of your form using some of the form helpers options).
BTW if you want to make things in a different manner you should read the Rails Guide to Routing and see if some option can help you to define your invitations routing rules, and have a look at the REST chapter of the Getting Started Rails Guide.
UPDATE: I missed the sentence "If I pluralize the resource's name in Routes.rb, I get a different routing error."
BTW, the problem is your controller class name is "InvitationController" while the form generated by the form_for helper submit to "/invitations".

Force current_user path

Currently users can access their "profile" through many paths.
localhost:3000/users/current_user
localhost:3000/users/current
localhost:3000/users/id#
How can I make it that they can only get to their "profile" through localhost:3000/users/current_user
One suggestion on the 'what' of your question: instead of the ideal url being localhost:3000/users/current_user I suggest localhost:3000/user or something even more descriptive such as localhost:3000/profile or localhost:3000/account.
Could you include the entries in your routes.rb? Even if Authlogic, etc. add routes to your app, they should do it in routes.rb. If you have the entry:
map.resource :users
then that's where the /users/123 route is coming from. I agree with Matchu that even if you don't use /users/123 you should keep it and route other requests to it.
An Additional Idea
If you don't want to get into the (kinda complicated, and not pretty) business of preserving model validation errors across redirects, here's another way. I'm assuming from here that you already have map.resource :users, so that you have the 7 default actions on your UsersController (index, new, create, show, edit, update, destroy).
In your routes.rb:
map.profile 'profile', :controller => 'users', :action => 'show'
map.edit_profile 'profile/edit', :controller => 'users', :action => 'edit', :conditions => { :method => :get }
map.update_profile 'profile/edit', :controller => 'users', :action => 'update', :conditions => { :method => :put }
You will need to update your form_for tag slightly:
<% form_for #user, :url => update_profile_path do |f| %> ...
Now, assuming you start on /profile, and click an edit link that takes you to /profile/edit (should show the form), if you fill out the form such that it fails validation then you should end up back on /profile/edit with the correct errors in the f.error_messages output.
Your controller code for edit should stay the same, and your code for update should be:
def update
#user = current_user || User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:notice] = "Successfully updated user."
redirect_to #user
else
render :action => 'edit'
end
end
The render (rather than a redirect) preserves the state of the #user model (including errors) and just renders the edit template again. Since you directed it at the update_profile_path the url viewed by the user is still /profile/edit.
Umm, first, remove the /users/current route that you must have in your routes.rb somewhere. (Although I prefer /users/current to /users/current_users, since the latter is rather redundant.)
As for /users/123, in your controller, you can check if the current user's ID matches 123 or whatever, and, if so, redirect.
But I really prefer the opposite effect. Pushing /users/current to /users/123 makes more sense in my brain, since it keeps the routes consistent for all users while still allowing you to cache links to /users/current.

Resources