Moving the :format attribute in routing from the end to beginning of route
In my application, I am trying to get my API to mimic GitHub's in how it has the (.:format) in the beginning of the route rather than appending it optionally at the end.
Here is my code that is "working" but can be ignored:
map.namespace :api do |api|
api.namespace :v1 do |v1|
v1.resource :company, :path_prefix => "api/v1/:format"
end
end
I can go to /api开发者_JAVA百科/v1/xml/company.json
and it Rails will provide json
as the params[:format]
rather than xml
.
When I run rake routes
I am getting
/api/v1/:format/company(.:format)
Is there a way to get it to return:
/api/v1/:format/company
Thanks in advance!
This is going to require some serious monkeypatching. Also routing is one of the most complex areas of the Rails codebase, both connecting incoming HTTP requests to your code and generating URLS.
Forgive me if I'm being presumptuous, but as far as I can ascertain, your reason for going against Rails convention is to mimic another company. In other words you're willing to disregard the collective wisdom of Rails contributors in favour of following a decision made by a handful of developers.
I think you should ask yourself is your reason for wanting this compelling enough to be commensurate with the effort required?
The harder it is do something other than the Rails way, the more rigorously one should question their decision. The presence of significant hurdles is usually indicative that there is a better way of doing something.
EmFi is right. I didn't answer the question merely expressed my opinion.
Put the following code into an initializer file within the config initializers directory inside your Rails app. What you name the file is does not matter to the framework as all files in this directory are in the load path. I suggest that you call it actioncontroller_resource_monkeypatch.rb
in order to make the intent clear.
ActionController::Resources.module_eval do
def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
if resource.has_action?(action)
action_options = action_options_for(action, resource, method, resource_options)
formatted_route_path = route_path.match(/\/:format\//) ? route_path : "#{route_path}.:format"
if route_name && @set.named_routes[route_name.to_sym].nil?
map.named_route(route_name, formatted_route_path, action_options)
else
map.connect(formatted_route_path, action_options)
end
end
end
end
My answer uses the same method as EmFi's i.e. by monkeypatching ActionController::Resources#map_resource_routes
. I decided to throw my hat into the ring because it did not offer a full implementation, that was left as an exercise for yourself. I also feel that a ternary assignment of formatted_route_path
is much cleaner and more concise than an if-else/unless-else block. One additional line of code instead of five! That's the least I can do for a 200 bounty!
Now run rake routes
new_api_v1_company GET /api/v1/:format/company/new {:action=>"new", :controller=>"api/v1/companies"}
edit_api_v1_company GET /api/v1/:format/company/edit {:action=>"edit", :controller=>"api/v1/companies"}
api_v1_company GET /api/v1/:format/company {:action=>"show", :controller=>"api/v1/companies"}
PUT /api/v1/:format/company {:action=>"update", :controller=>"api/v1/companies"}
DELETE /api/v1/:format/company {:action=>"destroy", :controller=>"api/v1/companies"}
POST /api/v1/:format/company {:action=>"create", :controller=>"api/v1/companies"}
TADA!
I believe the (.format) is optional (that's what the parenthesis mean), so /api/v1/:format/company(.:format) == /api/v1/:format/company
If you want to change it any more than that, you'll need to hack/monkey patch rails.
I'm just taking a shot here (I'll check and update tomorrow when I can play with the code), but couldn't you avoid using :format
and do something like this?
routes:
map.connect ':controller/:return_type/:action/:id'
controller:
@results = MyObject.all
case :return_type
when 'xml' render :text => @results.to_xml
when 'json' render :text => @results.to_json
end
If the case is too messy/ugly, you could easily create a helper method that mimics the behavior you're looking for.
Steve Graham says everything that needs to be said, but a question with 200 rep bounty deserves a proper answer. No matter how ill advised. Besides this does seem like it could be useful.
The monkey patch is surprisingly simple. You just need to override ActionController::Resource#map_resource_routes as follows.
def map_resource_routes(map, resource, action, route_path,
route_name = nil, method = nil, resource_options = {} )
if resource.has_action?(action)
action_options = action_options_for(action, resource, method, resource_options)
unless route_path.match(/\/:format\//) # new line of code
formatted_route_path = "#{route_path}.:format"
else # new line of code
formatted_route_path = route_path # new line of code
end # new line of code
if route_name && @set.named_routes[route_name.to_sym].nil?
map.named_route(route_name, formatted_route_path, action_options)
else
map.connect(formatted_route_path, action_options)
end
end
end
Incorporating this patch into your code involves either patching your rails gem or making a plugin, both of which are pretty simple and left as an exercise for the reader.
The code works by skipping the line that adds the option format to all routes if it the path already contains a parameter named format.
Edit: Linked to the Steve Graham answer that this solution refers to. There was only one at the original time of posting.
I used this code:
ActionController::Routing::Routes.draw do |map|
map.namespace(:v1, :path_prefix => "api/v1/:format") do |v1|
v1.resources :repositories
end
end
URLs become api/v1/[json/xml/whatever]/<restful url goes here>
I like the idea of namespacing the versions too (like in your question):
class V1::RepositoriesController < V1::ApplicationController
end
Whilst this method allows you to put the format in the URL twice: It is not up to you to ensure that users do not put the format in the URL twice, it is up to your users to ensure that they do not put the format in the URL twice.
Don't spend time solving PEBKACs.
精彩评论