What I needed was a simple URL Aliasing, much like that in Drupal. What I need is quite simple: assign some nice, user- and seo-friendly URLs (like /company/about) to not so nice ones (like /node/13) - and, well, have an ability to access sub-urls in a nice way, too (like /company/about/edit in the example above). An additional behaviour is to redirect the user to a 'nice' url (/company/about) when he accesses the original one (/node/13).
In TG2, there are two mechanisms that determine how the request url is transformed to a call of a certain method of a certain controller: Routes and object dispatch. Routes is great, but there are several issues with RestControllers, for example; built-in TG2 object dispatch is a nice thing but not that easy to understand and modify as Routes.
After stepping into some problems with Routes I decided to dive into object dispatch mechanism. That leaded me to the RootController, the main controller in TG2 application, that is derived from BaseController, that is derived from (well, You'd better look into the tg2 sources! :)
What we will need is to override the _get_routing_info method of this controller.
My first, very Q&D solution, looks like this:
def _get_routing_info(self, request_url=None):
if request_url is None:
request_url = pylons.request.path
target_url = request_url
request_url = request_url.rstrip('/')
# Check for an alias for provided URL and if exists, redirect to alias
# This ensures that '/company/about' is used instead of '/node/1'
url_alias = DBSession.query(model.UrlAlias).filter(model.UrlAlias.url==request_url).first()
if url_alias is not None:
redirect(url('/'+url_alias.alias))
# If the alias for the requested url was not found, this url could be an alias itself.
# Query the database for the original url.
url_rewrite = {}
for v in DBSession.query(model.UrlAlias).all():
url_rewrite[v.alias] = v.url
while request_url is not None:
if url_rewrite.has_key(request_url):
target_url = target_url.replace(request_url, url_rewrite[request_url], 1)
break
elif '/' in request_url:
request_url = request_url[:request_url.rfind('/')] or None
else:
break
# Finally, call the appropriate method from the superclass:
return super(RootController, self)._get_routing_info(target_url)
The UrlAlias is a very simple table, in Elixir syntax it is described like that:
class UrlAlias(Entity):
url = Field(Unicode(128), required=True, nullable=False, index=True)
alias = Field(Unicode(128), required=True, nullable=False, index=True)
Please note that it's a very first, Q&D approach, it is buggy and has lots of todos.