Hi,

I have a many-many relationship implemented as an Association Object.
When I add an association between a parent and a child, the list of
associations within the parent contains the child element twice. A
flush() does not help, but commit() resolves the issue. The
generated SQL only creates one INSERT, though (as expected).

The attached script can be used to reproduce the issue. The objects are
directly from the bi-directional "Association Object" example in the
0.9 docs (parent and child have an extra attribute "name", though). The
rest of the code just adds instances of these objects to the session.

On my system (Debian Jessie, SA 0.9.4), this script produces the
following output:

    0 some data <__main__.Child object at 0x7ffb63d81fd0>
    1 some data <__main__.Child object at 0x7ffb63d81fd0>
    --FLUSH--
    0 some data <__main__.Child object at 0x7ffb63d81fd0>
    1 some data <__main__.Child object at 0x7ffb63d81fd0>
    --COMMIT--
    0 some data <__main__.Child object at 0x7ffb63d81fd0>

Side question: what's the "proper" way to remove a many-many association
between two entities? As in: both entities continue to exist, it's just
the association that is removed (eg: entity "software", entity "os",
assoc "runs_on"). What I currently do is iterate over the associations
of the parent and remove the matched child, but that seems inefficient.

Christian

(re-submitted because apparently being subscribed to the ML is required
to post to i)

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.
#!/usr/bin/python

from sqlalchemy import Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Association(Base):
    __tablename__ = 'association'
    left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
    right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
    extra_data = Column(String(50))
    child = relationship("Child", backref="parent_assocs")

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)
    children = relationship("Association", backref="parent")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)

# PREPARE: Create tables, establish session, add 1 parent, child, commit
engine = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
Base.metadata.create_all(engine)
session = Session()
session.add(Parent(name='parent_name'))
session.add(Child(name='child_name'))
session.commit()

# Associate the parent with the child
# Query for parent, child because that's how the code will eventually be used
# (from within a method). I just flattened it for this example.
a = Association(extra_data="some data")
par = session.query(Parent).filter_by(name='parent_name').one()
chi = session.query(Child).filter_by(name='child_name').one()
a.child = chi
# SIDE QUESTION: Why is the following line required for association objects?
# With an association TABLE (following the doc's many-many example), omitting
# this works; with an assoc OBJECT the next line (append) raises IntegrityError
a.parent = par
par.children.append(a)

# QUESTION: Why are the Associations in p.children duplicated?
# Observe the changed behavior after flush / commit below!
p = session.query(Parent).filter_by(name='parent_name').one()
for idx, assoc in enumerate(p.children):
    print idx, assoc.extra_data, assoc.child

print '--FLUSH--'
session.flush()

# Flush has no effect...
for idx, assoc in enumerate(p.children):
    print idx, assoc.extra_data, assoc.child

# Commit resolves the issue!
print '--COMMIT--'
session.commit()

for idx, assoc in enumerate(p.children):
    print idx, assoc.extra_data, assoc.child

Reply via email to