many-to-many relations dont delete/do anything properly if the is_backref flag is on

Issue #249 resolved
Joona Kulmala created an issue
foo_baa = Table('foo_baa', activemapper.metadata,
            Column('foo_id', Integer, ForeignKey('foo.id'), primary_key=True),
            Column('baa_id', Integer, ForeignKey('baa.id'), primary_key=True))

class Foo(ActiveMapper):
    class mapping:
        name = column(String)
        baas = many_to_many('Baa', foo_baa, backref='foos')

    def __str__(self):
        return "<Foo %r>" % self.name

class Baa(ActiveMapper):
    class mapping:
        name = column(String)

    def __str__(self):
        return "<Baa %r>" % self.name




>>> for x in ('First', 'Second'):
...     Foo(name='Foo%s' % x)
...     Baa(name='Baa%s' % x)
>>> objectstore.flush()

[00:18:37,643](2006-07-21) [engine](engine): BEGIN
[00:18:37,741](2006-07-21) [engine](engine): INSERT INTO baa (name) VALUES (%s)
[00:18:37,743](2006-07-21) [engine](engine): ['BaaFirst']('BaaFirst')
[00:18:37,790](2006-07-21) [engine](engine): INSERT INTO baa (name) VALUES (%s)
[00:18:37,807](2006-07-21) [engine](engine): ['BaaSecond']('BaaSecond')
[00:18:37,839](2006-07-21) [engine](engine): INSERT INTO foo (name) VALUES (%s)
[00:18:37,840](2006-07-21) [engine](engine): ['FooFirst']('FooFirst')
[00:18:37,872](2006-07-21) [engine](engine): INSERT INTO foo (name) VALUES (%s)
[00:18:37,874](2006-07-21) [engine](engine): ['FooSecond']('FooSecond')
[00:18:37,902](2006-07-21) [engine](engine): COMMIT

>>> # get Foo object with id 1 and add relation to <Baa 'BaaFirst'> and flush
>>> obj = Foo.get(1)
>>> print obj.baas
[obj.baas.append(Baa.get(1))
>>> obj.flush()

[2006-07-21 00:18:38,027](]
>>>) [engine](engine): BEGIN
[00:18:38,031](2006-07-21) [engine](engine): INSERT INTO foo_baa (foo_id, baa_id) VALUES
(%s, %s)
[00:18:38,034](2006-07-21) [engine](engine): [1L](1L,)
[00:18:38,061](2006-07-21) [engine](engine): COMMIT

>>> print obj.baas
[object at 0x55de50>](<__main__.Baa)


>>> # get Baa object with id 1 (<Baa 'BaaFirst'>) and add relation to <Foo 'FooSecond'> 
>>> # from Baa and flush
>>> obj = Baa.get(1)
>>> print obj.foos
[object at 0x5220b0>](<__main__.Foo)
>>> obj.foos.append(Foo.get(2))
>>> obj.flush()

[00:18:38,123](2006-07-21) [engine](engine): BEGIN
[00:18:38,127](2006-07-21) [engine](engine): COMMIT

In my opinion obj.flush() should also commit changes between it's ManyToMany-relations. Now it works only from "active" side of relation (Foo in this example). objectstore.flush() would obviously do the trick, but that is something that id wouldn't like to do at my Web Framework's controller. obj.save() or obj.flush() would be much more logical.

Comments (6)

  1. Mike Bayer repo owner
    • changed milestone to 0.3.0

    in case you feel like playing with this, line 248 of sqlalchemy/orm/dependency.py is exactly where this happens :

     if self.is_backref:
                # if we are the "backref" half of a two-way backref 
                # relationship, let the other mapper handle inserting the rows
                return
    

    the solution involves the two corresponding DependencyProcessors being aware of each other, and either coexisting within the UOWTransaction and somehow insuring that only one of them does the many-to-many update of the rows, or checking if the other one is not present and then placing itself in the UOWTransaction.

  2. Mike Bayer repo owner

    another failing test:

    from sqlalchemy import *
    
    metadata = BoundMetaData('sqlite://')
    studentTbl = Table('student', metadata, Column('name', String(20), primary_key=True))
    courseTbl = Table('course', metadata, Column('name', String(20), primary_key=True))
    enrolTbl = Table('enrol', metadata,
        Column('student_id', String(20), ForeignKey('student.name'),primary_key=True),
        Column('course_id', String(20), ForeignKey('course.name'), primary_key=True))
    
    metadata.create_all()
    
    class Student(object):
        def __init__(self, name=''):
            self.name = name
    class Course(object):
        def __init__(self, name=''):
            self.name = name
    
    Student.mapper = mapper(Student, studentTbl)
    Course.mapper = mapper(Course, courseTbl, properties = {
        'students': relation(Student.mapper, enrolTbl, lazy=True, backref='courses')
    })
    
    sess = create_session()
    s1 = Student('Student1')
    c1 = Course('Course1')
    c2 = Course('Course2')
    c3 = Course('Course3')
    s1.courses.append(c1)
    s1.courses.append(c2)
    c3.students.append(s1)
    sess.save(s1)
    sess.flush()
    sess.delete(s1)
    sess.flush()
    assert enrolTbl.count().execute() == 0
    
  3. Log in to comment