6

For Reasons™, I want to have one controller handle html requests and another handle json api requests.

I've got this in my routes:

scope module: :api, as: :api do
  constraints format: :json do
    resources :users
  end
end
constraints format: :html do
  resources :users
end

This works great when the url sports a suffix: /users.json goes through my Api::UsersController à la api_users_path; /users.html goes through my UsersController à la users_path.

However this does not behave as expected when there is no suffix in the url. Implementing the constraints as lambda show things go wrong:

#=> visiting /users

scope module: :api, as: :api do
  constraints ->(request){ puts "In api: #{request.format}"; request.format == :json } do
    resources :users
  end
end
constraints ->(request){ puts "In html: #{request.format}"; request.format == :html } do
  resources :users
end

#=> In api: text/html
#=> (request.format == :json) => false

and yet it ends up in the api controller.

No fiddling with custom constraint classes or lambdas or anything prevents rails from selecting the first route defined if none of the constraints match.

I can't find a way to write a constraint that catches when the url lacks a suffix, and I don't feel as if I should have to–request.format == :html reports true when I'm navigating to /users. Why isn't the second constraint catching that?

Also, while I could "fix" this by changing the order of these two, I'd much rather know why my constraints aren't working right.

Does anyone know how to enforce these constraints differently to effectively switch on whatever the format is, not just the url suffix, or have an explicit constraint that accommodates no format suffix?

Chris Keele
  • 3,364
  • 3
  • 30
  • 52
  • the second contraint cant catch that the route doesnt match – apneadiving Oct 11 '13 at 19:44
  • why dont you simply set the request format yourself for all actions in your api? – apneadiving Oct 11 '13 at 19:46
  • actually, since you namespace your controller I dont understand why you need route constraints – apneadiving Oct 11 '13 at 19:58
  • I'm trying to route on format. The namespacing has no affect on that aside from preventing namespace collisions of link helpers (api_users_path vs users_path) since these routes, aside from the constraints, are identical. Is there a way I can clarify this intent in my question? – Chris Keele Oct 11 '13 at 20:16
  • oh, just understood, you put a module but no namespace! ok so I cant help you I always but my apis in either `api.` or in `/api` – apneadiving Oct 11 '13 at 20:18
  • I do too normally. External requirements dictate this structure. ¯¯\\_(ツ)\_/¯ – Chris Keele Oct 11 '13 at 20:21
  • does `scope module: :api, as: :api, constraints: { format: :json }` works? or perhaps `constraints format: :json { scope module: :api ...`? – j03w Oct 15 '13 at 08:46
  • Playing with the block ordering is a good idea. Doesn't seem to help though. – Chris Keele Oct 15 '13 at 14:37
  • This is really strange, I can't reproduce this at all. I made a brand new rails 4 app, copy-pasted your routes, created 2 controllers, and added some more logging, and it works like you're trying to get it to work. Here's a gist with the only changes I made after `rails new`, including what I see in the logs: https://gist.github.com/carols10cents/7034946 – carols10cents Oct 18 '13 at 01:13
  • Thanks @carolclarinet, I'll use that as a canary and spelunk into my own stack. Seems this might be a config issue instead of a Rails one; I too made reduced test case but had the same issue. – Chris Keele Oct 18 '13 at 13:33

3 Answers3

3

I don't think you need the second constraints block (the one for :html). I think this should do what you want:

scope module: :api, as: :api do
  constraints format: :json do
    resources :users
  end
end

resources :users
Jeremy Green
  • 8,547
  • 1
  • 29
  • 33
1

Extending Jeremy's answer.. What if you insert the users default html routes before tha api scope for routes?

like,

resources :users
scope module: :api, as: :api do
  constraints format: :json do
    resources :users
  end
end

Strangely, this works for me!

Aditya
  • 144
  • 7
  • Changing the order, with or without constraints, *does* work, which is what I'm doing now. I mentioned: `"While I could "fix" this by changing the order of these two, I'd much rather know why my constraints aren't working right."` because while overlaying the routes wasn't my call, I'd rather have the API routes for an API-centric application come first. – Chris Keele Oct 14 '13 at 15:32
  • Aww.. My bad! Walked through the question swiftly.. But considering Ruby to be a procedure driven language.. This should be the best solution. – Aditya Oct 15 '13 at 11:09
1

Setting the suffix-less default format to be json should take care of your needs.

scope module: :api, as: :api, defaults: { format: :json} do
  constraints format: :json do
    resources :users
  end
end

Only explicit .html suffix requests end up at this resource

resources :users
mczepiel
  • 711
  • 3
  • 12