Rendering 'new' leads to a different url - ruby-on-rails

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.

Related

When the form is submitted with an error, the rendered url changes in Rails 4

I have created a user signup page. When the user submits the form incorrectly, when displaying a validation error, it does not render on the same URL.
The signup form is located at this url:
http://localhost:3000/signup
I have added the following routes for the signup page:
resources :users
match '/signup', to: 'users#new', via: 'get'
When I submit the form, the model validation shows up but the url redirects to:
http://localhost:3000/users
I want the user to remain at the same url after submitting the form.
http://localhost:3000/signup
This is my controller code:
def new
#user = User.new
end
def create
#user = User.new(user_params) # Not the final implementation!
if #user.save
# Handle a successful save.
else
render 'new'
end
end
This is the beginning tag of my form:
<%= form_for #user, url: {action: "create"} do |f| %>
I'm working with Rails 5, but I suspect it's not too different conceptually.
In routes.rb:
get 'signup', to: 'users#new'
post 'signup', to: 'users#create'
resources :users
and in your form helper:
<%= form_for #user, url: signup_path do |f| %>
Essentially, the reason why it's not working as described in your original post is because when the form posts (rails 4.2 form helpers), it's tied to url: {action: "create"}, which by default, according to the routes automatically generated for resources :users, is post '/users', to: 'users#create (rails 4.2 routing guide). And by searching top down in routes.rb, it'll see this first and you end up with http://localhost:3000/users.
So, the changes I propose, should bind the form to post to the signup path instead which, in routes.rb, is seen as the second route entry.
I believe that #Jaskaran's question is in regards to question 2 found at the bottom of Michael Hartle's Ruby on Rails Tutorial, section 7.3.3, so I will be answering in reference to that question:
How does the URL on the unsubmitted signup form (Figure 7.12) compare to the URL for a submitted signup form (Figure 7.18)? Why don’t they match?
To answer this question, we need to understand how routing works in a Rails application. Let's walk through what happens when you visit each page and make a submission.
Your main page (home.html.erb) contains a link to your signup page (new.html.erb). That link looks like this:
<%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
The signup_path is the part we are most interested in. signup_path is a route helper and essentially tells Rails that when someone clicks on that link, Rails should behave as if they just went to your /signup page. This is why the first URL you see (before submitting the form) shows http://localhost:3000/signup.
Now let's take a look at what happens when your form is submitted. According to the new.html.erb file, the form is contained within this Ruby code:
<%= form_for(#user) do |f| %>
. . .
<% end %>
You may remember from 7.2.2 that that Ruby code is actually rendered in HTML as:
<form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post">
. . .
</form>
What that is saying is that when the form is submitted, it will make a POST request to /users - and that's the URL that you are seeing in your browser.
We can better understand why this is if we first stop to think - where does /users route to? You can quickly check your routes by typing bundle exec rake routes in your console. You should see it listed there:
Prefix Verb URI Pattern Controller#Action
users GET /users(.:format) users#index
POST /users(.:format) users#create
So any POST requests to /users will be routed to the create method of our users_controller.rb. If you're wondering why that is, it is because we added this line to our routes.rb file:
resources :users
More details on that line can be found here.

Controller/Action not found although physically present?

My attempt to make a new-action which gives the user a form and a create-action to process it fails with the error message `.
View: app/views/studios/new.html.erb:
<%= form_for #studio, url: {action: 'create'} do |f| %>
<%= f.text_field :name %>
<%= f.submit 'Create' %>
<% end %>
Controller: app/controllers/studio_controller.rb:
def new
#studio = Studio.new
respond_to do |format|
format.html
format.json { render json: #studio }
end
end
def create
# TODO
end
Route: config/routes.rb
get 'studios/new' => 'studios#new', :as => 'new_studio'
On attempting to visit http://localhost:3000/studios/new, I am presented with the error
No route matches {:action=>"create", :controller=>"studios"}
As you can see, the create-action is present in the studios-controller. Why is it failing?
It's failing because you don't have route defined for the create action. You have a get action defined for the new action only.
Update your routes file to add a post route to the create action as:
post 'studios/create' => 'studios#create'
Or, you could choose to use resourceful routing and update your routes file as:
resources :studios, only: [ :new, :create ]
This will define the new and create route for your studio resource. To see the generated routes you can run rake routes.
The rails form helper, given the instance variable #studio, asks for the instance variable via the new action, and, upon clicking save, calls the create action to validate and save this new instance. It's failing because rails cannot find the action to submit the object to.
Try adding resources :studios for RESTful routes in config/routes.rb (index, new, create, show, edit, update, destroy) automagically routing to actions with the same name in the app/controllers/studio_controller.rb
Try visiting the docs for form helpers here: http://guides.rubyonrails.org/form_helpers.html#binding-a-form-to-an-object for a more in depth explaination.

Rails - custom action in controller - how to reference in view?

I'm using a custom action to get the id of a project into the session, so that only relevant info for that project is shown in other areas. I've made a custom action in the projects controller, and am having trouble getting a link to work in the view to call that action. I just get an error saying "Couldn't find project without ID". I'm new to rails - I know it's probably an easy question, but help would be much appreciated, thanks!
View Code:
<%= link_to 'Select Project', :action => :select_project %>
Controller Code:
def select_project
#project = Project.find(params[:id])
session[:project_id] = #project.id
end
Routes:
resources :projects do
collection do
get :select_project
end
end
Alternative routes code:
resources :projects do
put 'select_project', on: :member
end
This is untested but I believe it is what you are looking for:
Routes:
resources :projects do
member do
post :set_current
end
end
this should create the following:
Endpoint: /projects/:id/set_current POST
Helper: set_current_project_path
Controller
def set_current
project = Project.find(params[:id])
session[:project_id] = project.id
redirect_to projects_path, :notice => "Current project set to #{project.name}"
end
Views
# index / erb tags excluded for simplicity
#projects.each do |project|
link_to 'Select Project', set_current_project_path(project), :method => :post
end
# show
<%= link_to 'Select Project', set_current_project_path(#project), :method => :post %>
See:
http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
Note also the use of 'post' instead of 'get', since we are changing the state of an object (session)
it is preferred to use a post not a get, otherwise users might pull up an old get request in the address bar
of their browser and set their session to a project unknowingly.
like varatis said - use rake routes or CONTROLLER=projects rake routes to help with determining what your route/path helpers look like and what http verbs they are expecting
And is there a reason why it's project not #project in the controller
The #project creates an instance variable; in a rails controller instance variables are made available to the views. This set_current action will never render a view, so no reason to make an instance variable out of it.
How come you have to set it to member and not collection in the routes
any action where you want to reference params[:id] should be a member route, an alternative would be to leave it as a collection route and pass params[:project_id] and pass that in all of your link_to calls, but in this case member makes more sense.
I believe resources :projects is a short cut for this break down
member do
get :show
get :edit
put :update
delete :destroy
end
collection do
get :index
get :new
post :create
end
hopefully that clarifies your questions some?
I think the route generated would be select_project_projects_path.
Link:
<%= link_to 'Select Project', select_project_projects_path %>
For future reference, run rake routes to see the automatic route helpers generated by Rails.

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 - Redirecting after Form Errors

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...

Resources