Barg the Blog

You're looking at an article archive of Barg the Blog, all contents here are no longer updated.

Please update your bookmark to Grid.in.th.


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+'))

อยากให้มันทำงานมากกว่านี้หน่อย แต่รู้สึกยังจะเกินความสามารถ ดังนั้นเอาแค่นี้ไปก่อน

Back