I am trying to implement a generic vanity url system in Rails 3. Generic in the sense that the vanity url isn't tied to a specific model. It is similar to the Vanities gem where I have a VanityUrlController
that is hit from all the vanity urls. The difference is that I don't want to do an external redirect from foo.com/username
to foo.com/users/1
or foo.com/product-name
to foo.com/products/1
. I want the vanity url to to stick around and have the the VanityUrlContoller do an internal redirect that mimics the corresponding show action.
I know what controller and action I want to dispatch the internal redirect to, but I am having problems with actually dispatching it. This is where I am at the moment:
TargetController.new.process("show", request.env)
It seems to start processing the new "request," but there are key pieces missing... like the actual request object.
Any thoughts or pointers would be very appreciated.
Update:
I ran across the dispatch method in ActionController which seems to get me a little farther.
TargetController.new.dispatch("show", request)
I have two problems with this, 1) it is listed as a private api method so if there is another way to do this, I'd rather that, and 2) even though it is rendering the show template for the TargetController, it is complaining about "Missing template vanity_urls/show."
UPDATE
Here is the basics of the solution we came up with. We do some other things like forcing encodings and checking some other application specific stuff, but this should be all you need to get going.
This goes at the very bottom of your routes.rb
file so your vanity routes don't clobber your other named routes.
# Vanity routes.
match ':id', :as => 'vanity', :to => proc { |env|
id = env["action_dispatch.request.path_parameters"][:id]
vain_object = <method to find the object you want to display>
if vain_object.nil?
# render your 404 page
'application#404'
else
model = vain_object.class.model_name
# figure out the controller you want to go to
controller = [model.pluralize.camelize,"Controller"].join.constantize
# reset the :id parameter with the id of the object to be displayed
env["action_dispatch.request.path_parameters"][:id] = vain_object.id
# do your internal redirect
controller.action("show").call(env)
end
}
You'll also want to be careful while creating your vanity routes, so they don't collide with your other controllers. Some other useful things to know about are:
Rails.application.routes.routes.any? { |r| r.requirements[:controller] == vanity_url }
Which tells you if your vanity_url
has the same name as a current controller.
Rails.application.routes.recognize_path("/#{vanity_url}", :method => :get)
Which tells you if this maps to anything already.
Sure, there are a couple of hacks along the way, but it works like a charm.