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…

On Sat, Feb 20, 2010 at 3:02 PM, Daniel G.
[email protected]wrote:

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

On Sat, Feb 20, 2010 at 4:00 PM, Conrad T. [email protected]
wrote:

post ‘login’ => ‘session#create’, :as => :login
end
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

On Sat, Feb 20, 2010 at 4:00 PM, Conrad T. [email protected]
wrote:

post ‘login’ => ‘session#create’, :as => :login
end
implemented this as follows:

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

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

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…

On Sat, Feb 20, 2010 at 4:32 PM, Daniel G.
[email protected]wrote:

     end

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

-Conrad

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

On Sat, Feb 20, 2010 at 4:11 PM, Daniel G.
[email protected]wrote:

get ‘login’ => ‘session#new’
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?

On Sat, Feb 20, 2010 at 4:38 PM, Daniel G.
[email protected]wrote:

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

On Sat, Feb 20, 2010 at 4:49 PM, Conrad T. [email protected]
wrote:

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

On Sat, Feb 20, 2010 at 5:19 PM, Daniel G.
[email protected]wrote:

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

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

On Sat, Feb 20, 2010 at 6:56 PM, Conrad T. [email protected]
wrote:

instance(s) of
require ‘active_model’
@attributes = attributes
puts session.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

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.

On Feb 21, 12:14 am, Daniel G. [email protected]

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.

On Sat, Feb 20, 2010 at 9:14 PM, Daniel G.
[email protected]wrote:

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

I’m trying to prove to you that ActiveModel works without
ActiveRecord::Base. It
works so much so I’m building ActiveRecord style interface to work with
Maglev.
Next, in your initial e-mail to the mailing list, you mentioned that
there
was a possible
bug in Rails 3 routing. What does this have to do with the possible bug
in
routing?

-Conrad