Rails 3 possible bug in Routing

Hi, I just ran into this ActionController::RoutingError and just wanted to check if someone can confirm this as a bug in the Rails 3 beta gem.

config/routes.rb contains:

  get 'login' => 'session#new'   post 'login' => 'session#create', :as => :login

GET /login works fine:

Started GET "/login" for 127.0.0.1 at 2010-02-20 17:45:49   SQL (0.3ms) SET SQL_AUTO_IS_NULL=0   Processing by SessionController#new as HTML Rendered session/new.html.haml within layouts/application.html.haml (77.9ms) Completed in 85ms (Views: 84.1ms | ActiveRecord: 0.2ms) with 200

However POST /login gives the following error:

Started POST "/login" for 127.0.0.1 at 2010-02-20 17:45:58   SQL (0.3ms) SET SQL_AUTO_IS_NULL=0

ActionController::RoutingError (No route matches "/login"):

rake routes returns the expected urls:

       login POST /login {:controller=>"session", :action=>"create"}              GET /login {:controller=>"session", :action=>"new"}

Thanks, Daniel

So I did some more digging it's not related to routing directly. The error only occurs when using form_for with a session model defined like this:

class Session   include ActiveModel::Validations

  attr_accessor :login, :password, :id

end

when changing from form_for to form_tag the routing error goes away. Not really sure yet why this...

Hi, I just ran into this ActionController::RoutingError and just

wanted to check if someone can confirm this as a bug in the Rails 3

beta gem.

config/routes.rb contains:

get ‘login’ => ‘session#new’

post ‘login’ => ‘session#create’, :as => :login

Daniel, can you post the complete route? The ‘get’ and ‘post’ HTTP verbs should exist within a member or collection block of a resource block. For example,

resources :posts do

collection do

get :search

end

end

or

resources :posts do

get :search, :on => :collection

end

Note: both of the examples are equivalent.

Next, your routes look ambiguous meaning that you could have easily implemented this as follows:

match ‘login’ => “user_sessions#lnew”, :as => :login

match ‘login’ => “user_sessions#destroy”, :as => :logout

Lastly, your URLs will look like the following:

http://localhost:3000/logout

http://localhost:3000/login

Good luck,

-Conrad

Hi, I just ran into this ActionController::RoutingError and just

wanted to check if someone can confirm this as a bug in the Rails 3

beta gem.

config/routes.rb contains:

get ‘login’ => ‘session#new’

post ‘login’ => ‘session#create’, :as => :login

Daniel, can you post the complete route? The ‘get’ and ‘post’ HTTP verbs should exist within a member or collection block of a resource block. For example,

resources :posts do

collection do

get :search

end

end

or

resources :posts do

get :search, :on => :collection

end

Note: both of the examples are equivalent.

Next, your routes look ambiguous meaning that you could have easily implemented this as follows:

match ‘login’ => “user_sessions#lnew”, :as => :login

Correction: match ‘login’ => “user_sessions#new”, :as => :login

Hi, I just ran into this ActionController::RoutingError and just

wanted to check if someone can confirm this as a bug in the Rails 3

beta gem.

config/routes.rb contains:

get ‘login’ => ‘session#new’

post ‘login’ => ‘session#create’, :as => :login

Daniel, can you post the complete route? The ‘get’ and ‘post’ HTTP verbs should exist within a member or collection block of a resource block. For example,

resources :posts do

collection do

get :search

end

end

or

resources :posts do

get :search, :on => :collection

end

Note: both of the examples are equivalent.

Next, your routes look ambiguous meaning that you could have easily implemented this as follows:

match ‘login’ => “user_sessions#lnew”, :as => :login

match ‘login’ => “user_sessions#destroy”, :as => :logout

Today isn’t my day. The logout line should be

match ‘logout’ => “user_sessions#destroy”, :as => :logout

not quite the routes you are providing are not equivalent to what I wanted to archive and they are the only routes in the routing file for this test. What I want is:

GET /login should be resolved to session#new POST /login should be resolved to session#create

possible ways of doing so are according to the action_dispatch/ routing.rb file

get 'login' => 'session#new' post 'login' => 'session#create', :as => :login

or when using match

match 'login' => 'session#new', :via => :get match 'login' => 'session#create', :via => :post

the above two examples are equivalent since get and post just add the :via => :method to the options and call match

class Session < ActiveRecord::Base   # include ActiveModel::Validations

  attr_accessor :login, :password #, :id

end

ah the last bit of the previous message should have not been in there, but should have been in this message.

Changing the Session class to:

class Session < ActiveRecord::Base end

and adding a table to the database (which is not the goal here just a workaround for figuring out what's going on here) makes the everything work correctly with:

form_for(Session.new, :url => login_path)

This clearly shouldn't be related but this is what I have so far...

Ok what is really happening here is that for_for(Session.new, :url => login_path) includes a hidden input field setting _method to put which correctly complains about a routing error since no route is defined for PUT /login Remaining question to me is why does form_for set the method to PUT

Session.new.new_record? => NoMethodError Session.new.id => nil

So to solve this, the reason why this ends up using :method => :put is the following code in "apply_form_for_options!":

        html_options =           if object.respond_to?(:new_record?) && object.new_record?             { :class => dom_class(object, :new), :id => dom_id(object), :method => :post }           else             { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }           end

which means for every object not responding to new_record? it will automatically set the method to PUT since the options are reverse merged later with the provided options this can be avoided by setting explicit :html => { :method => :post } in form_for - not sure though if this is entended behavior...

If someone has some inside view comments would be appreciated...

So to solve this, the reason why this ends up using :method => :put is

the following code in “apply_form_for_options!”:

    html_options =

      if object.respond_to?(:new_record?) && object.new_record?

        { :class  => dom_class(object, :new),  :id =>

dom_id(object), :method => :post }

      else

        { :class  => dom_class(object, :edit), :id =>

dom_id(object, :edit), :method => :put }

      end

Yes, this is basic Rails. PUT HTTP verb translates to an update action.

-Conrad

Yes, this is correct and expected, the question to me is rather if it is expected behavior to assume an update operation if the object doesn't respond to :new_record?

not quite the routes you are providing are not equivalent to what I

wanted to archive and they are the only routes in the routing file for

this test. What I want is:

GET /login should be resolved to session#new

POST /login should be resolved to session#create

possible ways of doing so are according to the action_dispatch/

routing.rb file

get ‘login’ => ‘session#new’

post ‘login’ => ‘session#create’, :as => :login

or when using match

match ‘login’ => ‘session#new’, :via => :get

match ‘login’ => ‘session#create’, :via => :post

the above two examples are equivalent since get and post just add

the :via => :method to the options and call match

class Session < ActiveRecord::Base

include ActiveModel::Validations

attr_accessor :login, :password #, :id

The above attr_accessor overwrites the login and password getters and setters

provided by ActiveRecord.

-Conrad

Yes, this is correct and expected, the question to me is rather if it

is expected behavior to assume an update operation if the object

doesn’t respond to :new_record?

Yes, this is expected because AR instance is either new (i.e. hasn’t been saved) or

not new (i.e. has been saved). One can easily test this in the Rails console.

-Conrad

Yes, this is correct and expected, the question to me is rather if it

is expected behavior to assume an update operation if the object

doesn’t respond to :new_record?

Yes, this is expected because AR instance is either new (i.e. hasn’t been saved) or

not new (i.e. has been saved). One can easily test this in the Rails console.

-Conrad

irb(main):026:0> post = Post.new

=> #<Post id: nil, title: nil, body: nil, created_at: nil, updated_at: nil>

irb(main):027:0> post.new_record?

=> true

irb(main):028:0> post.save

=> true

irb(main):029:0> post.new_record?

=> false

-Conrad

Ok but I'm not using an ActiveRecord instance here. I just temporarily made Session inherit from ActiveRecord::Base for testing purpose. And the attr_accessors didn't override anything since the table I created only contained an id attribute. The idea here was to just create a normal class (not inheriting from ActiveRecord) and to only use the validations module. The session is not going to be stored in the database.

The original implementation of Session was:

class Session   include ActiveModel::Validations   attr_accessor :login, :password, :id end

Ok but I’m not using an ActiveRecord instance here. I just temporarily

made Session inherit from ActiveRecord::Base for testing purpose. And

the attr_accessors didn’t override anything since the table I created

only contained an id attribute.

Session class inherits from ActiveRecord::Base. Thus, if you create an instance(s) of

Session, then each instance is a type of ActiveRecord::Base.

The idea here was to just create a normal class (not inheriting from

ActiveRecord) and to only use the validations module. The session is

not going to be stored in the database.

Then you can simply do the following:

Ok but I’m not using an ActiveRecord instance here. I just temporarily

made Session inherit from ActiveRecord::Base for testing purpose. And

the attr_accessors didn’t override anything since the table I created

only contained an id attribute.

Session class inherits from ActiveRecord::Base. Thus, if you create an instance(s) of

Session, then each instance is a type of ActiveRecord::Base.

The idea here was to just create a normal class (not inheriting from

ActiveRecord) and to only use the validations module. The session is

not going to be stored in the database.

Then you can simply do the following:

**
require 'active_model'
class Session
include ActiveModel::Validations
validates_presence_of :login
validates_presence_of :password
attr_accessor :login, :password
def initialize( attributes = {})
@attributes = attributes
end
end
puts "valid session"
puts
session = Session.new( :login => "foo", :password => "bar" )
puts session.valid? # => false
puts session.password = "foobar"
puts session.valid? # => true
puts session.errors
puts
puts "invalid session"
puts
session2 = Session.new( :login => "", :password => "bar" )
puts session2.valid? # => false
puts session2.password = "foobar"
puts session2.valid? # => true
puts session2**

Good luck,

-Conrad

Here’s a better version:

require ‘active_model’

class Session

include ActiveModel::Validations

validates_presence_of :login

validates_presence_of :password

attr_accessor :login, :password

def initialize( attributes = {})

@attributes = attributes

end

end

puts “valid session”

puts

session = Session.new

puts session.login = ‘foo’

puts session.password = ‘bar’

puts session.valid? # => true

puts session.errors

puts

puts “invalid session”

puts

session2 = Session.new

puts session2.password = “bar”

puts session2.valid? # => true

puts session2.errors

I wish that this helps.

Good luck,

-Conrad

What are you trying to prove here? I'm not using ActiveRecord and my Session class IS NOT inheriting from ActiveRecord::Base either

Session.anchestors => [Session, ActiveModel::Validations, ActiveSupport::Callbacks, Object, PP::ObjectMixin, JSON::Ext::Generator::GeneratorMethods::Object, ActiveSupport::Dependencies::Loadable, Arel::Sql::ObjectExtensions, Arel::ObjectExtensions, Kernel, BasicObject]

I know how to add validations to it etc. (not included in this post to keep the examples lean) the point I'm trying to make here is the dependency of form_for on the :new_record? method.

Maybe I should make clear that I changed the Session implementation back to its original state after finding what caused the PUT method being attached. Looks like you were still assuming my Session inherits from ActiveRecord::Base. It was just for testing purpose to see if it works correctly with an ActiveRecord model.

Maybe I should make clear that I changed the Session implementation

back to its original state after finding what caused the PUT method

being attached. Looks like you were still assuming my Session inherits

from ActiveRecord::Base. It was just for testing purpose to see if it

works correctly with an ActiveRecord model.

If you attended the Exploring Rails 3 Online conference, it was clearly discussed

that this feature (i.e. ActiveModel) is ready for 3rd party ORM builders.

-Conrad