Shane Hathaway wrote:
Steve Alexander wrote:
What about making relationships among pre-existing objects that were not designed with relationships in mind?
As it stands, Jobs have no relationships with Users, but now you want to relate Jobs to Users.
I'd say that this could be a fairly common use case. Here's my take on what Relationships should be, using ERM jargon (see: http://www.cs.jcu.edu.au/ftp/web/teaching/Subjects/cp1500/1998/Lecture_Notes...) A relation is a mapping from roles to entities. These entities don't need to know anything about the relation in order to be a part of it. The relation may also have descriptive data attached to it. A Relationship is an object that contains a set of relations of the same type, meaning that each relation is based on the same set of roles. It provides methods for searching and modifying this set. The Relationship also imposes constraints on and among the relations that it contains. A role does not impose class or other restrictions on the entities that may fill it, although the Relationship may. It is possible to derive a "view" of a Relationship, such that it contains the subset of relations in which one or more roles contain specified entities. Any relation added to the view must map these roles to these entities. In concrete terms:
r = Relationship(roles=[['group'], ['member']]) r.add(group='men', member='fred') <relation group='men' member='fred'> r.add('men', 'barney') # implicit use of role order? <relation group='men' member='barney'> fred_groups = r.view(member='fred') fred_groups.add(group='water buffalo') for rel in fred_groups.get(): print rel.group 'men' 'water buffalo' print fred_groups.get(group='men') <relation group='men' member='fred'> len(r), len(fred_groups) 3, 2 r.remove(member='fred') len(r), len(fred_groups) 1, 0
The various common sorts of cardinality (one-to-one, etc) constraints can be expressed by list nesting. The example above creates a many-to-many relationship. # one-to-one Relationship(['person', 'ssn']) # one-to-many Relationship(['boss', ['employee']]) # one-to-many-to-many Relationship(['book', ['edition'], ['editor']]) # one-to-one-to-many Relationship(['mother', 'father', ['child']]) # one-to-(one-to-many) Relationship(['superclass', ['class', ['instance']]]) Notice the subtle but important difference between the last two examples. A child must have exactly one (mother, father) pair, and an instance must have exactly one (superclass, class) pair. Also, a class must have exactly one superclass (we're talking single-inheritance, here), but a father may have children with more than one mother and vice-versa. Attaching descriptive data might look like this: r = Relationship(['invoice', 'payment']) link = r.add(invoice=inv1, payment=p1) link['amount'] = 50 link = r.add(invoice=inv2, payment=p1) link['amount'] = 75 This facility could also be used to "annotate" objects, with a single-role relation: member_data = Relationship(['member']) member_data.add(current_user)['email'] = 'guy@example.com' It would probably be valuable to allow arbitrary constraint objects that are notified of attempts to add and remove relations. Cheers, Evan @ 4-am