respond_with and namespaced controllers

in routes.rb

MyAwesomeSite::Application.routes.draw do
namespace :api do
namespace :v1 do
resources :topics
end
end
end

in app/controllers/api/v1/topics_controller.rb

class API::V1::TopicsController < API::V1::APIController

def create

respond_with Topic.create(params[:topic])

end

end

This unfortunately won’t work, as it is going to complain about the missing topic_url helper.

Instead I’ll have to do this:

class API::V1::TopicsController < API::V1::APIController

def create

topic = Topic.create(params[:topic])

respond_with topic, location: api_v1_topic_url(topic)

end

end

But this still won’t work, because when there are validation errors in topic, api_v1_topic_url will raise an exception. So you’ll need…

class API::V1::TopicsController < API::V1::APIController

def create

topic = Topic.build(params[:topic])

if topic.save

  respond_with topic, location: api_v1_topic_url(topic)

else

  respond_with topic

end

end

end

…which almost defeats the purpose of using respond_with in the first place.

Did I miss any obvious solution? If not, I’d like to see improve this. I have a few ideas:

  1. In the URL helpers, return nil instead of raising an exception

  2. Accept a proc for the location option in respond_with. Since respond_with won’t even look at the location option when there are errors in the object passed in, the proc will only be called when the object was saved successfully and thus avoids the exception in the url helper:

class API::V1::TopicsController < API::V1::APIController

def create

topic = Topic.create(params[:topic])

respond_with topic, location: ->{ api_v1_topic_url(topic) }

end

end

  1. Respect the controller’s namespace when constructing the location object, so it will try to use api_v1_topic_url, api_topic_url and topic_url in that order.

My favourite is #3, but I’d have to do some research to find out how to do this or if it’s possible at all. Any comments / feedback / pointers are welcomed!

Godfrey

Hi,

This seems to work well for me:
def create
respond_with :api, :v1, Topic.create(params[:topic])
end

Is this the behavior you want?

The documentation for respond_with is pretty detailed and mentions this solution (under nested resources):
http://apidock.com/rails/ActionController/Responder

Your 3rd suggestion is something that could be done by writing a custom responder if this seems like boilerplate to you.
Looking at your code, your model is not namespaced, so technically it is behaving correctly. If it were namespaced under
Api::V1 same as the controller it would just work without any hints (because the path is generated based on model_name).

def create
respond_with :api, :v1, Topic.create(params[:topic])
end

That works great! Didn’t make the connection between singleton resources and namespaces the first time I saw it. I’m quite happy with this solution!

Looking at your code, your model is not namespaced, so technically it is behaving correctly. If it were namespaced under Api::V1 same as the controller it would just work without any hints (because the path is generated based on model_name).

I understand that the path is currently generated based on model_name, but I wasn’t sure if this is the most sensible default, since this code resides in the controller, it could make use of the information to make a better guess at the naming (it seems quite unlikely to cause problem, because if your models are namespaced, chances are your controllers are also namespaced, but the reverse is not typically true outside of engines). I guess I’d try implementing the custom responder in my app and see how that goes, and if that worked out well for me I might open a PR and see what others think.

Thanks again for your help, really appreciate it!

Godfrey