Routing error after using :path option - ruby-on-rails

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

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.

Rails 4 - No route matches PATCH on custom route

I need a page for changing Profile of current user, it's weird if the url is /user/:id, so I map it to /settings
get "/settings", to: "users#edit", as: "settings_user"
But when I submit the form I got this error:
Routing Error
No route matches [PATCH] "/settings"
The weird part is if I press back and re-submit the form, it will submit just fine.
If I go to another page then back to the form, it will get error on first try but works fine on second try onward.
My controller:
class UsersController < ApplicationController
...
def edit
#user = current_user #this is the cache of currently logged in user
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to settings_user_path, notice: "Profile has been saved"
end
end
private
def user_params
params.require(:user).permit(:id, :name, :email, :bio)
end
end
My view:
<%= form_for #user do |f| %>
...
<% end %>
Note:
Other page that are using the default route like my Product page works fine, so it's not the Rails config problem.
Devise
I guess you're using devise (by how you're using current_user) - so you may wish to look at Devise' custom routing paths. Although this won't provide a routing structure for your user object, it may come in handy some time:
#config/routes.rb
devise_for :users, path: "auth", path_names: { sign_in: 'login', sign_out: 'logout', password: 'secret', confirmation: 'verification', unlock: 'unblock', registration: 'register', sign_up: 'cmon_let_me_in' }
--
Routes
If you want to manage your user object, you'll be best using the resources route definition:
#config/routes.rb
resources :users, only: [], path: "" do
collection do
get :settings, action: :edit
match :settings, action :update, via: [:patch, :put]
end
end
The problem you have is your form is thinking it should send your update request to /settings too:
No route matches [PATCH] "/settings"
The way around this is to either provide a route for patch (as demonstrated above), OR define the url parameter of the form:
<%= form_for #user, url: your_update_path do |f| %>
--
Hope this helps?
This issue only occur on Chrome 37 Beta. I reverted back to Chrome 36 Release and everything works fine.
I guess I won't use the beta version for daily use ever again.

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

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