简体   繁体   中英

Understanding constraints in Rails

I have been putting tests together to ensure that some CRUD actions are not routable in my application based upon this resource declaration

resources :posts, only: [:index, :show] do
  resources :categories
end

My Rspec Controller test

it 'does not route to #new' do
  expect(get: '/posts/new').to_not be_routable
end

When I run this test I get the following output

expected {:get=>"/posts/new"} not to be routable, but it routes to {:controller=>"posts", :action=>"show", :id=>"new"}

So after some research I came across this post on SO, having read it an implementing the solution using constraints.

resources :posts, only: [:index, :show], except: :new, constraints: { id: /\d+/ } do
  resources :categories
end

Now my tests pass, but I don't understand why and can't leave it without understanding it. So I have two questions

  1. Why was the test failing in the beginning?
  2. How did adding the constraints argument fix it?

According the the post you linked, and the answer you implemented,

The difference between

'/posts/new' # which is the route to the new action of post controller

and

'/posts/:id' #which is the route to the show action of the same controller

is almost nothing, apart from the fact that one is /new and the other is /:id

Now, this difference is almost nothing, given that having a route like /posts/:id means that the only thing really needed here is the /posts/ part. any other thing in front of this will be seen as an id for /posts/:id .

To this effect, /posts/5 , /posts/10 , /posts/you , /posts/me , and even /posts/new all matches /posts/:id . with :id equaling => 5, 10, you, me, new respectively.

The above is the reason why your test failed because going to /posts/new is routable for the show action, which essentially is /posts/:id

On the other hand, when you added the constraint ( id: /\\d+/ ), you are telling the route that id should be strictly digits. This will prevent any character that is not a digit from being interpreted as an id , so, from the example above, ( /posts/5 , /posts/10 , /posts/you , /posts/me , and even /posts/new ), only posts/5 and /posts/10 will match the show action, while the rest will not.

Hope this is well explanatory ...

1) Your test was failing initially because of the :show route for :posts . It would look something like this:

GET    /posts/:id(.:format)      posts/#show

This will match any GET request to /posts/xxxx and will assign whatever you put in the second part of the path ( xxxx in this case) to the :id element in your params hash.

If you inspect the params[:id] value in your show action after browsing to /posts/new you'll see that params[:id] == 'new'

So why did your test fail?

In your test, you are checking that /posts/new doesn't route anywhere. But without any constraints on what is an acceptable value for :id , it will route to your show action.

2) The constraint you added specifies that the :id parameter must match the regular expression /\\d+/ in order to be accepted. This is one or more digits (0-9).

So, GET /posts/123 is accepted, but GET /posts/new is not. Incidentally, this constraint will prohibit you from using friendly ids to make your URLs nicer. eg GET /posts/my-new-post-about-something will not be acceptable due to the constraint specifying that any argument be an integer.

Alright, this is what I think happened.

If you put back your original code:

resources :posts, only: [:index, :show] do
  resources :categories
end

and run rake routes , you will see that get posts/new can be matched in term to one of the /posts/:id route with :id being matched to the string "new".

Adding the contraint that id must be numeric does not allow for this match to happen anymore.

Note that if you were using mongoid instead of activerecord for instance, your contraint would not work since ids are string.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM