Helma Logo
main list history
previous version  overview  next version

Version 2 by hannes on 18. February 2009, 00:07

This page shows how to implement many-to-many using the Helma 1 *Object-Relational Mapping* layer.

Let's assume the following scenario: each user should be able to subscribe to (ie. adopt) one or more custom HopObjects (e.g. news channels, weblogs and the like).

Ie. it must be possible to attach one or more custom HopObjects to each user object and, vice versa, to attach one ore more user objects to any custom HopObject.

<pre><b>HopObject1</b>          <b>HopObject2</b>
&nbsp; +-userObject1        +-userObject1
&nbsp;  +-userObject2        +-userObject3
&nbsp;  +-userObject3        +-userObject5
&nbsp;  +-...                +-...
&nbsp;  +-userObject[n]      +-userObject[n]

<b>userObject1</b>          <b>userObject2</b>
&nbsp;  +-HopObject1        +-HopObject1
&nbsp;  +-HopObject2        +-...
&nbsp;  +-...                +-HopObject[m]
&nbsp;  +-HopObject[m]
</pre>

We cannot simply map this special hierarchy between two HopObjects the way we are used to do with setting the appropriate type.properties for them. We are restricted to either map the user objects to one HopObject or the HopObjects to one user object (ie. building a 1:1one-to-one, resp. 1:n one-to-many relationship). That is due to the limitation of the relational table structure:<pre>+---------+-----------+structure:

<pre>+---------+-----------+
| user_id | hopobj_id |
+---------+-----------+
|  1      |  1        |
+---------+-----------+
|  2      |  1        |
+---------+-----------+
|  3      |  1        |
+---------+-----------+
|  1      |  2        |
+---------+-----------+
</pre>

Here the trouble starts: it is not possible to assign the same <tt>user_id</tt> to another <tt>hopobj_id</tt> inside the same table because either <tt>user_id</tt> or <tt>hopobj_id</tt> has to be a unique key.

However, we are on the right track and this table leads directly to the solution of the problem if we use it as "manager" for the other two tables (which are in consequences the user objects and HopObjects inside Helma). To do that, we simply add another column containing the missing unique key to the table above:<pre>+----+---------+-----------+above:

<pre>+----+---------+-----------+
| id | user_id | hopobj_id |
+----+---------+-----------+
| 1  |  1      |  1        |
+----+---------+-----------+
| 2  |  2      |  1        |
+----+---------+-----------+
| 3  |  3      |  1        |
+----+---------+-----------+
| 4  |  1      |  2        |
+----+---------+-----------+
| 5  |  3      |  2        |
+----+---------+-----------+
| 6  |  5      |  2        |
+----+---------+-----------+
</pre>

In summary, we now have to deal with three tables: one containing the user objects, the second containing the custom HopObjects (which certainly have to be defined more precisely) and the third one above, mapping user objects and HopObjects to each other.

The steps to turn the relational tables into object-oriented representations accesible by Helma are quite easy. However, yet the general type-mapping syntax appears rather tricky (but better type-mapping is just around the corner).

Let's assume at this point that the HopObjects we deal with are constructed from a prototype i will refer to as <tt>Channel</tt> from now on. Ie. there is a directory called "Channel" in the application's directory as well as a database table called "db_channel" (certainly both could use the same name, but I make a difference here for transparency reasons).

The user objects are derived from the standard prototype <tt>user</tt> and reside in a database table called "db_user".

The "managing" object I will call <tt>Subscription</tt> from now on. Also here, we then assume that there is a prototype called "Subscription" and a db table called "db_subscription".

We start off with the <tt>user</tt>'s type.properties:

<blockquote><tt>_db = <i>dbconnex</i>
_table = db_user
_children = collection(Subscription)
_children.local = id
_children.foreign = <i>user_id</i>
_id = <i>id</i>
_name = <i>name</i>
name = <i>name</i>
password = <i>password</i>
email = <i>email</i></tt></blockquote>

Please note that you probably have to arrange the datasource and column names according to those in your tables (I have written them in italics).

As you might have noticed, this user prototype comes along with some more properties (<tt>name</tt>, <tt>password</tt> etc.), which is certainly no necessity, however very realistic.

For <tt>Channel</tt> we set-up a very basic type mapping:

<blockquote><tt>_db = <i>dbconnex</i>
_table = db_channel
_id = <i>id</i></tt></blockquote>
Finally, we turn to the type.properties file for <tt>Subscription</tt>:

<blockquote><tt>_db = <i>dbconnex</i>
_table = db_subscription
_id = <i>id</i>
channel = <i>channel_id</i> &gt; Channel.id
user = object(user)
user.local = <i>user_id</i>
user.foreign = id</tt></blockquote>

Et voilà, with these settings <i>n:m</i> relationships should not be a big deal anymore.

Just be aware that when you want to execute a user subscription (ie. to add a <tt>Channel</tt> object to a <tt>user</tt> object in this case), you have to use the <tt>Subscription</tt> object:

function subscribe(channel) {
  var subscr = new Subscription();
  subscr.user = user;
  subscr.channel = channel;
  user.add(subscr);
}

     removed
     added