Search code examples
ruby-on-railsbutton-to

button_to works only intermittently, and often results in a "no route" error


Added on edit, 2013-02-11:

I should make it clearer that the problem I'm having is not that I can't get the ERB code I write to produce correct HTML code. Rather, it is that my Rails installation sometimes interprets the HTML code correctly, so that clicking the link has the right effect, and sometimes incorrectly, producing a "No route" error. More details in my answer below.

Original question, 2013-02-08:

I'm making a simple login form using Rails 3.2.11. I've tried several ways of coding the Submit button of the form, but each has failed due to a missing route error. I got one method to work, and checked in my code. However, now the very same code fails.

This is the code of app/views/session/new.html.erb (I'm using simple_form, but not its button wrapper):

<h2>Log in</h2>
<%= simple_form_for :session do |f| %>
  <%= f.input :username %>
  <%= f.input :password %>
  <%= button_to "Submit", session_index_path %>
<% end %>

The relevant part of the HTML code that this produces is:

<h2>Log in</h2>
<form accept-charset="UTF-8" action="/session/new" class="simple_form session" method="post" novalidate="novalidate">
  <div style="margin:0;padding:0;display:inline">...</div>
  ...the input fields...
  <form action="/session" class="button_to" method="post">
    <div>
      <input type="submit" value="Submit" />
      <input name="authenticity_token" type="hidden" value="...token value here..." />
    </div>
  </form>
</form>

This is my config/routes.rb:

MyApp::Application.routes.draw do
  resources :at_user
  resources :session, :only => [:new, :create, :destroy]
  match 'login'  => 'session#new',     as: :login
  match 'logout' => 'session#destroy', as: :logout
  root to: 'main#index'
end

This is what the command rake routes outputs:

at_user_index GET    /at_user(.:format)          at_user#index
              POST   /at_user(.:format)          at_user#create
  new_at_user GET    /at_user/new(.:format)      at_user#new
 edit_at_user GET    /at_user/:id/edit(.:format) at_user#edit
      at_user GET    /at_user/:id(.:format)      at_user#show
              PUT    /at_user/:id(.:format)      at_user#update
              DELETE /at_user/:id(.:format)      at_user#destroy
session_index POST   /session(.:format)          session#create
  new_session GET    /session/new(.:format)      session#new
      session DELETE /session/:id(.:format)      session#destroy
        login        /login(.:format)            session#new
       logout        /logout(.:format)           session#destroy
         root        /                           main#index

The target path of button_to is session_index_path, which should cause the create method of SessionController to be called -- and for a while, it did. Now, after I've restarted Rails, pressing the button instead produces an error page, with the text

No route matches [POST] "/session/new"

For some reason, Rails has started to think that the target of button_to is session#new instead of session#create. It's as if it thinks that the HTTP method it's supposed to call is GET instead of POST -- however, the HTML code shows that the method is post.

By the way, another thing I earlier tried was to give button_to the action and method parameters, as documented here:

<%= button_to "Submit", options: {action: 'create', method: :post} %>

then this is what's generated:

<form action="/session/new?options%5Baction%5D=create&amp;options%5Bmethod%5D=post" class="button_to" method="post">

which doesn't look like what I want, either. button_to's default HTTP method is POST, which appears in the result, but the :options hash is just tacked onto the end of the URL, and the word create appears nowhere else.

The first answer to this question says that, unlike what the documentation says, one should not put the parameters of button_to in a hash, but give them directly. So, I tried this:

<%= button_to "Submit", action: 'create' %>

However, the action create still does not show up in the generated HTML:

<form action="/session" class="button_to" method="post">

So, those were things I tried before attempting to using the named path method, which worked for a minute, but for some mysterious reason, doesn't anymore.

Any ideas what I'm doing wrong? (It's probably something obvious I've overlooked.)


Solution

  • The problem was most probably that after I created the application, I decided to change the names of a couple of the classes and the associated files. This resulted in class and file names that didn't follow the Rails convention on pluralizing nouns in certain contexts. For instance, I had a file session_controller.rb that contained a class named SessionController; the correct names are sessions_controller.rb and SessionsController.

    I created a dummy Rails application, and then commanded rails generate scaffold AtUser and rails generate scaffold Session, and then used the resulting filenames and identifiers as a guide for correcting the actual application. Now, I don't get those "No route" errors anymore. The command rake routes outputs:

        at_users GET    /at_users(.:format)          at_users#index
                 POST   /at_users(.:format)          at_users#create
     new_at_user GET    /at_users/new(.:format)      at_users#new
    edit_at_user GET    /at_users/:id/edit(.:format) at_users#edit
         at_user GET    /at_users/:id(.:format)      at_users#show
                 PUT    /at_users/:id(.:format)      at_users#update
                 DELETE /at_users/:id(.:format)      at_users#destroy
        sessions POST   /sessions(.:format)          sessions#create
     new_session GET    /sessions/new(.:format)      sessions#new
         session DELETE /sessions/:id(.:format)      sessions#destroy
           login        /login(.:format)             sessions#new
          logout        /logout(.:format)            sessions#destroy
            root        /                            main#index
    

    Before this, I saw a "No route" error also when I included a file in another file without having required the former at the top of the latter. After performing the renaming fix, I tried to reproduce this error, and it did reoccur: Rails mistakenly reports a missing route, when the actual problem is a missing require statement. This suggests that Rails's error reporting mechanism is buggy.

    Thanks to user bullfrog for pointing me in the right direction.