Reputation: 3999
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:
PUT
test fails on Rails 4, because no 404 is raisedPATCH
test succeedsPUT
request works on productionPATCH
request fails on productionThe 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:
PUT
to raise a 404PATCH
to succeedGems used: * Rails 4.2 * RSpec 3.4 * RSpec-rails 3.4 * simple_form 3.2
Upvotes: 2
Views: 779
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