Pylons Routes, NestedConnector
ในไฟล์ routes.py ของโปรเจค Pylons ของผมมันยาวมาก ถ้าหากจะลองนับแล้วก็คงได้ประมาณ 80 routes ที่ทั้งหมดมีการบังคับ POST, GET และ conditions อื่นๆ อยู่อีก ในเรื่องการดูแลมันเลยวุ่นวายพอสมควร
วันนี้พยายาม convert โปรเจคไปเป็น Pylons 0.9.7 ก็เลยตัดสินใจว่ามานั่งเคลียร์เจ้า routes นี่ซักหน่อยดีกว่า ให้สั้นลงและสามารถดูแลได้ง่ายขึ้น จากเดิมที่หน้าตาประมาณนี้
m.connect('/', controller='posts', action='list', conditions=dict(method='GET'))
m.connect('/page/{page}', controller='posts', action='list', requirements=dict(page='\d+'), conditions=dict(method='GET'))
m.connect('/tag/{tag}', controller='posts', action='list_tags', conditions=dict(method='GET'))
m.connect('/tag/{tag}/page/{page}', controller='posts', action='list_tags', requirements=dict(page='\d+'), conditions=dict(method='GET'))
ให้เหลือแค่นี้
with m.controller('posts') as c:
with c.action('list') as a:
a.GET('/')
a.GET('/page/{page}', requirements=dict(page='\d+'))
with c.action('list_tags') as a:
a.GET('/tag/{tag}')
a.GET('/tag/{tag}/page/{page}', requirements=dict(page='\d+'))
ตั้งชื่อไว้ว่า NestedConnector ทำโดยการเรียกใช้งาน contextlib และ with statement ที่เป็นฟีเจอร์ตั้งแต่ Python 2.5 เพื่อทำ DSL กลายๆ มาใช้สำหรับกรณีนี้
เจ้าโค้ดที่ใช้หน้าตาแบบนี้
from contextlib import contextmanager
class NestedConnector(object):
"""
An extension to Routes_ that allows you to connect a new route to
the Mapper in a slightly different way.
Example::
mapper = Mapper()
m = NestedConnector()
with m.controller('posts') as c:
with c.action('list') as a:
a.GET('/')
a.GET('/page/{page}', requirements=dict(page='\d+'))
with c.action('list_tags') as a:
a.GET('/tag/{tag}')
a.GET('/tag/{tag}/page/{page}', requirements=dict(page='\d+'))
which is basically equals to::
m = Mapper()
m.connect('/', controller='posts', action='list', conditions=dict(method='GET'))
m.connect('/page/{page}', controller='posts', action='list', requirements=dict(page='\d+'), conditions=dict(method='GET'))
m.connect('/tag/{tag}', controller='posts', action='list_tags', conditions=dict(method='GET'))
m.connect('/tag/{tag}/page/{page}', controller='posts', action='list_tags', requirements=dict(page='\d+'), conditions=dict(method='GET'))
.. _Routes: http://routes.groovie.org/
"""
def __init__(self, mapper, fields={}):
self.mapper = mapper
self.fields = fields
@contextmanager
def controller(self, controller):
"""Define the `controller` for use with connector methods."""
self.fields['controller'] = controller
c = NestedConnector(self.mapper, self.fields)
yield c
del self.fields['controller']
@contextmanager
def action(self, action):
"""Define the `action` for use with connector methods."""
self.fields['action'] = action
c = NestedConnector(self.mapper, self.fields)
yield c
del self.fields['action']
def GET(self, *args, **kargs):
"""Connect to the Mapper with `method='POST'` condition"""
kargs.update(self.fields)
if not 'conditions' in kargs: kargs['conditions'] = {}
kargs['conditions'].update(dict(method='GET'))
self.mapper.connect(*args, **kargs)
def POST(self, *args, **kargs):
"""Connect to the Mapper with `method='POST'` condition"""
kargs.update(self.fields)
if not 'conditions' in kargs: kargs['conditions'] = {}
kargs['conditions'].update(dict(method='POST'))
self.mapper.connect(*args, **kargs)
นอกเหนือจากวิธีใช้ข้างบนแล้ว ยังใช้แบบนี้ได้ด้วย แต่ไม่รู้เหมือนกันว่าจะทำไปทำไม
with nested(m.controller('posts'), m.action('list')) as (c, a):
a.GET('/')
a.GET('/page/{page}', requirements=dict(page='\d+'))
อยากให้มันทำงานมากกว่านี้หน่อย แต่รู้สึกยังจะเกินความสามารถ ดังนั้นเอาแค่นี้ไปก่อน