Commits

Mike Bayer committed bee9d4c

- synonyms can now be created against props that don't exist yet,
which are later added via add_property(). This commonly includes
backrefs. (i.e. you can make synonyms for backrefs without
worrying about the order of operations) [ticket:919]

  • Participants
  • Parent commits b7bc179

Comments (0)

Files changed (4)

 
 - orm
     - Fixed cascades on a += assignment to a list-based relation.
-
+    
+    - synonyms can now be created against props that don't exist yet,
+      which are later added via add_property().  This commonly includes
+      backrefs. (i.e. you can make synonyms for backrefs without
+      worrying about the order of operations) [ticket:919]
+      
 - ext
     - '+', '*', '+=' and '*=' support for association proxied lists.
 

lib/sqlalchemy/orm/attributes.py

         
     def __init__(self, key, user_prop, comparator=None):
         self.user_prop = user_prop
-        self.comparator = comparator
+        self._comparator = comparator
         self.key = key
         self.impl = ProxiedAttribute.ProxyImpl(key)
-
+    
+    def comparator(self):
+        if callable(self._comparator):
+            self._comparator = self._comparator()
+        return self._comparator
+    comparator = property(comparator)
+    
     def __get__(self, instance, owner):
         if instance is None:
             self.user_prop.__get__(instance, owner)                

lib/sqlalchemy/orm/properties.py

 
     def do_init(self):
         class_ = self.parent.class_
-        aliased_property = self.parent._get_property(self.key, resolve_synonyms=True)
+        def comparator():
+            return self.parent._get_property(self.key, resolve_synonyms=True).comparator
         self.logger.info("register managed attribute %s on class %s" % (self.key, class_.__name__))
         if self.instrument is None:
             class SynonymProp(object):
                     return getattr(obj, self.name)
             self.instrument = SynonymProp()
             
-        sessionlib.register_attribute(class_, self.key, uselist=False, proxy_property=self.instrument, useobject=False, comparator=aliased_property.comparator)
+        sessionlib.register_attribute(class_, self.key, uselist=False, proxy_property=self.instrument, useobject=False, comparator=comparator)
 
     def merge(self, session, source, dest, _recursive):
         pass

test/orm/mapper.py

         assert u.user_name == 'jack'
         u.user_name = 'jacko'
         assert m._columntoproperty[users.c.user_name] is m.get_property('_user_name')
+    
+    def test_synonym_replaces_backref(self):
+        assert_calls = []
+        class Address(object):
+            def _get_user(self):
+                assert_calls.append("get")
+                return self._user
+            def _set_user(self, user):
+                assert_calls.append("set")
+                self._user = user
+            user = property(_get_user, _set_user)
+        
+        # synonym is created against nonexistent prop
+        mapper(Address, addresses, properties={
+            'user':synonym('_user')
+        })
+        compile_mappers()
+        
+        # later, backref sets up the prop
+        mapper(User, users, properties={
+            'addresses':relation(Address, backref='_user')
+        })
+
+        sess = create_session()
+        u1 = sess.query(User).get(7)
+        u2 = sess.query(User).get(8)
+        # comparaison ops need to work
+        a1 = sess.query(Address).filter(Address.user==u1).one()
+        assert a1.address_id == 1
+        a1.user = u2
+        assert a1.user is u2
+        self.assertEquals(assert_calls, ["set", "get"])
+    
+    def test_self_ref_syn(self):
+        t = Table('nodes', MetaData(),
+            Column('id', Integer, primary_key=True),
+            Column('parent_id', Integer, ForeignKey('nodes.id')))
+            
+        class Node(object):
+            pass
+            
+        mapper(Node, t, properties={
+            '_children':relation(Node, backref=backref('_parent', remote_side=t.c.id)),
+            'children':synonym('_children'),
+            'parent':synonym('_parent')
+        })
+        
+        n1 = Node()
+        n2 = Node()
+        n1.children.append(n2)
+        assert n2.parent is n2._parent is n1
+        assert n1.children[0] is n1._children[0] is n2
+        self.assertEquals(str(Node.parent == n2), ":param_1 = nodes.parent_id")
         
     def test_illegal_non_primary(self):
         mapper(User, users)