Noel Frostpaw
Noel Frostpaw

Reputation: 3999

PATCH & PUT routes in Rails upgrade

Okay,

I'm in the middle of upgrading our Rails 3 application from Rails 3.2 onto Rails 4.2. After reading through the upgrade notes and the changes, we are running into a weird issue with some PUT and PATCH routes.

In our Rails 3 application, we have the following route defined:

resources :entities, except: [:show] do
  member do
    put :change_monitoring_status
  end
 end

When running rake routes, the change_monitoring_status route shows up as a proper PUT route. The form being generated for this using the Rails helpers generates the correct request in Rails 3.

In Rails 4 however, this form is now producing a PATCH route instead. Which is totally okay as this is now the standard behaviour in Rails. However, when we use the form on Rails 4 Production, this results in a 404 - Not Found.

We tried to write an RSpec test for this to mimic the same behaviour and close the bug ticket. So for our Rails 4 upgrade, we wrote the following test:

describe '#change_monitoring_status' do

  before(:each) do
    sign_in user
  end

  it 'fails when put is used' do
    expect {
      put :change_monitoring_status, id: entity.id, _method: "PUT", form_object: {  }
    }.to raise_error
  end

  it 'works when patch is used' do
    expect {
      patch :change_monitoring_status, id: supplier.id, _method: "PATCH", form_object: {  }
    }.to_not raise_error
  end
end

We also changed the route to be PATCH instead of PUT now.


This is the behaviour we are seeing:

The way we understand this, is not in line with the documentation. The form is generating a proper PATCH request, yet we cannot get the correct behaviour going in the RSpec tests.

What we expect:

Gems used: * Rails 4.2 * RSpec 3.4 * RSpec-rails 3.4 * simple_form 3.2

Upvotes: 2

Views: 779

Answers (1)

max
max

Reputation: 101821

Controllers spec fake the whole request cycle. The request never really hits the routing layer - RSpec just looks for a route in the routes file which matches the controller and the action.

While this is good for speed since you skip the whole Rack layer it can sometimes mask routing errors.

Also there is a slight difference in your specs; the first uses id: entity.id while the latter uses id: supplier.id.

In this case I would use a routing spec or a request spec instead since this should cause an error in the routing layer - before the controller is even matched.

describe "change_monitoring_status", type: :routing do

  it 'fails when put is used' do
    expect(puts: "/entity/37/change_monitoring_status").not_to be_routable
  end

  it 'works when patch is used' do
    expect(patch: "/entity/37/change_monitoring_status").to 
      route_to(
         controller: "entities", 
         action: "change_monitoring_status", 
         id: 37
      )
  end
end

Upvotes: 3

Related Questions