jbailey: (Default)
jbailey ([personal profile] jbailey) wrote2007-02-01 06:45 pm

Relationship properties

My brain is drawing a blank on this:

Let's say I have a class Group and a class Person, where there's an n:m relationship between these. It's simple to say that Group could have an attribute "members" which is a collection of Persons, or Person could have an attribute "groups" which is a collection of Groups.

The problem then is that there's no relationship properties there. I can't easily describe for how long the person has been a member of the group, when that membership expires, etc. It seems to me that these type of properties ought to be describable in an elegant way. Perhaps as a characteristic of the array? I want to do this ideally in a way that for items that have no meaningful characteristics I can just use a standard Array class of some sort, but that when properties come up, I want to be able to add them silently so that existing code can continue to just work.
ext_157608: (Default)

[identity profile] sfllaw.livejournal.com 2007-02-01 11:50 pm (UTC)(link)
You want an intermediate representation called Membership. A Membership has a Person and a Group, along with various properties like start and stop dates.

You can use a degenerate form if you want Groups to contain Memberships, or Persons to have Memberships.
ext_157608: (Default)

[identity profile] sfllaw.livejournal.com 2007-02-01 11:51 pm (UTC)(link)
Now that I think about it, you build an Array of Memberships, and optionally have references to these Memberships in each Group and Person. This allows for fast lookups, no matter how you traverse things.

m:n relationships

(Anonymous) 2007-02-02 12:36 am (UTC)(link)
sfllaw is right, you would normally add a third class "Membership" (or similar) which references a Group and a Person and contains additional attributes of the membership.
If you have a data representation which allows for arrays in a class, you could add arrays to Group and/or Person which contains references to all the Memberships of that Group/Person. However, if you use such an array, you have to make sure that you always update the array when you add or remove a membership from the list of Memberships. In a relational database context, I would however strongly advise against addition of such arrays, even if the database would permit them. It can get quite complicated and nasty to debug when the array(s) get(s) out of sync relative to the Memberships. I grew quite fond of using mostly relational database like structures in programs I wrote: Don't store the same data twice, even if that might make the program slower on some occasions (for SQL databases, this is rare), it makes it a lot easier to maintain data integrity.

Regards,
Sven

[identity profile] malpingu.livejournal.com 2007-02-02 01:30 am (UTC)(link)
I agree with using a Membership association class, as suggested above. However, I would avoid dependence upon any particular implementation structure, such as Array, and instead use getter/setter methods for better abstraction.

[identity profile] jkakar.livejournal.com 2007-02-02 03:05 am (UTC)(link)
The Membership class makes sense; however, it shouldn't be exposed in the public API. Instead your user and group classes should have methods that use it unbeknownst to the user, as in:

User.is_expired($group)
User.expires_on($group)
User.member_since($group)
Group.is_expired($user)
Group.expires_on($user)
Group.member_since($user)

This has several nice properties:

- The Membership stays out of the public API.

- Using explicitly named methods makes for good code-readability:

if (! $user->is_expired($group)) { ... }

- It's easy to unit test.

It has at least the following deficiency:

- You can get into a situation where passing users and groups around together to access membership data becomes tedious. If/when that happens you can refactor to contextful objects with a simpler API:

User.__construct($group)
User.is_expired()
User.expires_on()
User.member_since()
etc.