object-system-design.xml

Delivered as text/xml

[ hide source ]

File Contents

<?xml version='1.0' ?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
               "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
<!ENTITY % myvars SYSTEM "../variables.ent">
%myvars;
]>
<sect1 id="object-system-design" xreflabel="OpenACS 4 Object Model Design">
<title>Object Model Design</title>

<authorblurb>
<para>By Pete Su, Michael Yoon, Richard Li, Rafael Schloming</para>
</authorblurb>

<sect2 id="object-system-design-essentials">
<title>Essentials</title>

<sect3 id="objects-design-data-model">
<title>Data Model</title>

<itemizedlist>
<listitem><para><ulink url="/doc/sql/display-sql?url=acs-metadata-create.sql&amp;package_key=acs-kernel">
acs-metadata-create.sql</ulink></para></listitem>

<listitem><para><ulink url="/doc/sql/display-sql?url=acs-objects-create.sql&amp;package_key=acs-kernel">
acs-objects-create.sql</ulink></para></listitem>

<listitem><para><ulink url="/doc/sql/display-sql?url=acs-relationships-create.sql&amp;package_key=acs-kernel">
acs-relationships-create.sql</ulink></para></listitem>
</itemizedlist>
</sect3>
<sect3 id="objects-design-tcl-files">
<title>Tcl Files</title>

<para><emphasis>Not yet linked.</emphasis></para>
</sect3>

<sect3 id="objects-design-requirements">
<title>Requirements</title>

<itemizedlist>
<listitem><para><link linkend="object-system-requirements">Object Model
Requirements</link></para></listitem>

<listitem><para><link linkend="groups-requirements">Groups
Requirements</link></para></listitem>

<listitem><para><link linkend="permissions-requirements">Permissions
Requirements</link></para></listitem>
</itemizedlist>
</sect3>

</sect2>

<sect2 id="object-system-design-introduction">
<title>Introduction</title>


<para>Before OpenACS 4, software developers writing OpenACS applications or modules
would develop each data model separately. However, many applications built on
OpenACS share certain characteristics or require certain common services.
Examples of such services include:</para>

<itemizedlist>
<listitem><para>User comments</para></listitem>

<listitem><para>Storage of user-defined or extensible sets of attributes</para></listitem>

<listitem><para>Access control</para></listitem>

<listitem><para>General auditing and bookkeeping (e.g. creation date, IP addresses, and
so forth)</para></listitem>

<listitem><para>Presentation tools (e.g. how to display a field in a form or on a
page)</para></listitem>
</itemizedlist>

<para>All of these services involve relating additional service-related
information to application data objects. Examples of application objects
include:</para>

<itemizedlist>
<listitem><para>forum messages</para></listitem>

<listitem><para>A user home page</para></listitem>

<listitem><para>A ticket in the ticket tracker</para></listitem>
</itemizedlist>

<para>In the past, developers had to use ad-hoc and inconsistent schemes to
interface to various &quot;general&quot; services. OpenACS 4 defines a central
data model that keeps track of the application objects that we wish to
manage, and serves as a primary store of <emphasis>metadata</emphasis>. By
<emphasis>metadata</emphasis>, we mean data stored on behalf of an application
<emphasis>outside</emphasis> of the application&#39;s data model in order to enable
certain central services. The OpenACS 4 Object Model (or object system) manages
several different kinds of data and metadata to allow us to provide general
services to applications:</para>

<itemizedlist>
<listitem><para><xref linkend="objects-design-object-ident"/> </para>

<para>Every application object is given a unique identifier in the system. This
identifier can be used to find all data related to a particular object.
</para></listitem>

<listitem><para><xref linkend="objects-design-obj-context"/> </para>

<para>Every object is created in a particular security context, so the system
can provide centralized access control.
</para></listitem>

<listitem><para><xref linkend="objects-design-object-types"/> </para>

<para>Objects are instances of developer-defined object types. Object types
allow developers to customize the data that is stored with each object.
</para></listitem>

<listitem><para><xref linkend="objects-design-relation-types"/> </para>

<para>Relation types provide a general mechanism for mapping instances of one
object type (e.g. users) to instances of another object type (e.g.
groups).
</para></listitem>
</itemizedlist>

<para>The next section will explore these facilities in the context of the
particular programming idioms that we wish to generalize.</para>

<para><emphasis role="strong">Related Links</emphasis></para>

<para>This design document should be read along with the design documents for <link linkend="groups-design">the new groups system</link>, <link linkend="subsites-design">subsites</link> and <link linkend="permissions-design">the permissions system</link></para>

</sect2>

<sect2 id="object-system-design-history">
<title>History</title>


<para>The motivation for most of the facilities in the OpenACS 4 Object Model can be
understood in the context of the 3.x code base and the kinds of programming
idioms that evolved there. These are listed and discussed below.</para>

<sect3 id="objects-design-object-ident" xreflabel="Object Identification">
<title>Object Identification</title>

<para>Object identification is a central mechanism in OpenACS 4. Every application
object in OpenACS 4 has a unique ID which is mapped to a row in a central table
called <computeroutput>acs_objects</computeroutput>. Developers that wish to use OpenACS 4 services
need only take a few simple steps to make sure that their application objects
appear in this table. The fact that every object has a known unique
identifier means that the core can deal with all objects in a generic way. In
other words, we use object identifiers to enable centralized services in a
global and uniform manner.</para>

<para><emphasis>Implicit Object Identifiers in OpenACS 3.x</emphasis></para>

<para>The motivation for implementing general object identifiers comes from
several observations of data models in OpenACS 3.x. Many modules use a
<computeroutput>(user_id, group_id, scope)</computeroutput> column-triple for the purpose of
recording ownership information on objects, for access control. User/groups
also uses <computeroutput>(user_id, group_id)</computeroutput> pairs in its
<computeroutput>user_group_map</computeroutput> table as a way to identify data associated with a
single membership relation.</para>

<para>Also, in OpenACS 3.x many utility modules exist that do nothing more than
attach some extra attributes to existing application data. For example,
general comments maintains a table that maps application &quot;page&quot;
data (static or dynamic pages on the website) to one or more user comments on
that page. It does so by constructing a unique identifier for each page,
usually a combination of the table in which the data is stored, and the value
of the primary key value for the particular page. This idiom is referred to
as the &quot;(on_which_table + on_what_id)&quot; method for identifying
application data. In particular, general comments stores its map from pages
to comments using a &quot;(on_which_table + on_what_id)&quot; key plus the ID
of the comment itself.</para>

<para>All of these composite key constructions are implicit object identifiers -
they build a unique ID out of other pieces of the data model. The problem is
that their definition and use is ad-hoc and inconsistent, making the
construction of generic application-independent services unnecessarily
difficult.</para>

<para><emphasis>Object Identifiers in OpenACS 4</emphasis></para>

<para>The OpenACS 4 Object Model defines a single mechanism that applications use to
attach unique identifiers to application data. This identifier is the primary
key of the <computeroutput>acs_objects</computeroutput> table. This table forms the core of what
we need to provide generic services like access control, general attribute
storage, general presentation and forms tools, and generalized administrative
interfaces. In addition, the object system provides an API that makes it easy
to create new objects when creating application data. All an application must
do to take advantage of general services in OpenACS 4 is to use the new API to
make sure every object the system is to manage is associated with a row in
<computeroutput>acs_objects</computeroutput>. More importantly, if they do this, new services
like general comments can be created without requiring existing applications
to &quot;hook into&quot; them via new metadata.</para>

<para><emphasis role="strong">Note:</emphasis> Object identifiers are a good example of metadata
in the new system. Each row in <computeroutput>acs_objects</computeroutput> stores information
<emphasis>about</emphasis> the application object, but not the application object itself.
This becomes more clear if you skip ahead and look at the SQL schema code
that defines this table.</para>
</sect3>

<sect3 id="objects-design-obj-context" xreflabel="Object Context and Access Control">
<title>Object Context and Access Control</title>

<para>Until the implementation of the general permissions system, every OpenACS
application had to manage access control to its data separately. Later on, a
notion of &quot;scoping&quot; was introduced into the core data model.</para>

<para>&quot;Scope&quot; is a term best explained by example. Consider some
hypothetical rows in the <computeroutput>address_book</computeroutput> table:</para>

 
<informaltable>
<tgroup cols="5">
<tbody>
<row>
<entry><emphasis role="strong">...</emphasis></entry>
<entry><emphasis role="strong"><computeroutput>scope</computeroutput></emphasis></entry>
<entry><emphasis role="strong"><computeroutput>user_id</computeroutput></emphasis></entry>
<entry><emphasis role="strong"><computeroutput>group_id</computeroutput></emphasis></entry>
<entry><emphasis role="strong">...</emphasis></entry>
</row>

<row>
<entry>...</entry>
<entry><computeroutput>user</computeroutput></entry>
<entry><computeroutput>123</computeroutput></entry>
<entry> </entry>
<entry>...</entry>
</row>

<row>
<entry>...</entry>
<entry><computeroutput>group</computeroutput></entry>
<entry> </entry>
<entry><computeroutput>456</computeroutput></entry>
<entry>...</entry>
</row>

<row>
<entry>...</entry>
<entry><computeroutput>public</computeroutput></entry>
<entry> </entry>
<entry> </entry>
<entry>...</entry>
</row>
</tbody></tgroup></informaltable>

<para>The first row represents an entry in User 123&#39;s personal address book,
the second row represents an entry in User Group 456&#39;s shared address
book, and the third row represents an entry in the site&#39;s public address
book.</para>

<para>In this way, the scoping columns identify the security context in which a
given object belongs, where each context is <emphasis>either</emphasis> a person
<emphasis>or</emphasis> a group of people <emphasis>or</emphasis> the general public (itself a group
of people).</para>

<para>In OpenACS 4, rather than breaking the world into a limited set of scopes,
every object lives in a single <emphasis>context</emphasis>. A context is just an
abstract name for the default security domain to which the object belongs.
Each context has a unique identifier, and all the contexts in a system form a
tree. Often this tree will reflect an observed hierarchy in a site, e.g. a
forum message would probably list a forum topic as its context, and a
forum topic might list a subsite as its context. Thus, contexts make it
easier to break the site up into security domains according to its natural
structure. An object&#39;s context is stored in the <computeroutput>context_id</computeroutput>
column of the <computeroutput>acs_objects</computeroutput> table.</para>

<para>We use an object&#39;s context to provide a default answer to questions
regarding access control. Whenever we ask a question of the form &quot;can
user X perform action Y on object Z&quot;, the OpenACS security model will defer
to an object&#39;s context if there is no information about user X&#39;s
permission to perform action Y on object Z.</para>

<para>The context system forms the basis for the rest of the OpenACS access control
system, which is described in two separate documents: one for the
<link linkend="permissions-design">permissions system</link> and another for the
<link linkend="groups-design">party groups</link> system. The context system
is also used to implement <link linkend="subsites-design">subsites</link>.</para>
</sect3>

<sect3 id="objects-design-obj-types">
<title>Object Types</title>

<para>As mentioned above, many OpenACS modules provide extensible data models, and
need to use application specific mechanisms to keep track of user defined
attributes and to map application data to these attributes. In the past,
modules either used user/groups or their own ad hoc data model to provide
this functionality.</para>

<para><emphasis>User/Groups in OpenACS 3.x</emphasis></para>

<para>The user/group system allowed developers to define <emphasis>group types</emphasis>
along with attributes to be stored with each instance of a group type. Each
group type could define a helper table that stored attributes on each
instance of the group type. This table was called the
&quot;<computeroutput>_info</computeroutput>&quot; table because the name was generated by
appending <computeroutput>_info</computeroutput> to the name of the group type.</para>

<para>The user/groups data model also provided the
<computeroutput>user_group_type_member_fields</computeroutput> and
<computeroutput>user_group_member_fields</computeroutput> tables to define attributes for members
of groups of a specific type and for members of a specific group,
respectively. The <computeroutput>user_group_member_field_map</computeroutput> table stored
values for both categories of attributes in its <computeroutput>field_value</computeroutput>
column. These tables allowed developers and users to define custom sets of
attributes to store on groups and group members without changing the data
model at the code level.</para>

<para>Many applications in OpenACS 3.x and earlier used the group type mechanism in
ways that were only tangentially related to groups of users, just to obtain
access to this group types mechanism. Thus the motivation for generalizing
the group types mechanism in OpenACS 4.</para>

<para><emphasis>Object Types and Subtypes</emphasis></para>

<para>In OpenACS 4 <emphasis>object types</emphasis> generalize the OpenACS 3.x notion of group
types. Each object type can define one or more attributes to be attached to
instances of the type. This allows developers to define new types without
being artificially tied to a particular module (i.e. user/groups).</para>

<para>In addition, the OpenACS 4 object model provides mechanism for defining
<emphasis>subtypes</emphasis> of existing types. A subtype of a parent type inherits all
the attributes defined in the parent type, and can define some of its own.
The motivation for subtypes comes from the need for OpenACS to be more
extensible. In OpenACS 3.x, many applications extended the core data models by
directly adding more columns, in order to provide convenient access to new
information. This resulted in core data tables that were too &quot;fat&quot;,
containing a hodge podge of unrelated information that should have been
normalized away. The canonical example of this is the explosion of the
<computeroutput>users</computeroutput> table in OpenACS 3.x. In addition to being sloppy technically,
these fat tables have a couple of other problems:</para>

<itemizedlist>
<listitem><para>They degrade performance.</para></listitem>

<listitem><para>Denormalization can make it hard to maintain consistency constraints on
the data.</para></listitem>
</itemizedlist>

<para>Object subtypes provide a way to factor the data model while still keeping
track of the fact that each member of a subtype (i.e. for each row in the
subtype&#39;s table), is also a member of the parent type (i.e. there is a
corresponding row in the parent type table). Therefore, applications use
this mechanism without worrying about this bookkeeping themselves, and we
avoid having applications pollute the core data model with their specific
information.</para>
</sect3>

<sect3 id="objects-design-attributes">
<title>Object Attributes, Skinny Tables</title>

<para>As we described above, the OpenACS 3.x user/groups system stored object
attributes in two ways. The first was to use columns in the helper table. The
second consisted of two tables, one describing attributes and one storing
values, to provide a flexible means for attaching attributes to metadata
objects. This style of attribute storage is used in several other parts of
OpenACS 3.x, and we will refer to it as &quot;skinny tables&quot;. For
example:</para>

<itemizedlist>
<listitem><para>In the Ecommerce data model, the <computeroutput>ec_custom_product_fields</computeroutput>
table defines attributes for catalog products, and the
<computeroutput>ec_custom_product_field_values</computeroutput> table stores values for those
attributes.</para></listitem>

<listitem><para>In the Photo DB data model, the <computeroutput>ph_custom_photo_fields</computeroutput> table
defines attributes for the photographs owned by a specific user, and tables
named according to the convention
&quot;<computeroutput>ph_user_&lt;user_id&gt;_custom_info</computeroutput>&quot; are used to
store values for those attributes.</para></listitem>
</itemizedlist>

<para>In addition, there are some instances where we are not using this model
but <emphasis>should</emphasis>, e.g. the <computeroutput>users_preferences</computeroutput> table, which
stores preferences for registered users in columns such as
<computeroutput>prefer_text_only_p</computeroutput> and <computeroutput>dont_spam_me_p</computeroutput>. The
&quot;standard&quot; way for an OpenACS 3.x-based application to add to the list
of user preferences is to add a column to the <computeroutput>users_preferences</computeroutput>
table (exactly the kind of data model change that has historically
complicated the process of upgrading to a more recent OpenACS version).</para>

<para>The ACS Object Model generalizes the scheme used in the old OpenACS 3.x user/groups
system. It defines a table called <computeroutput>acs_attributes</computeroutput> that record
what attributes belong to which object types, and how the attributes are
stored. As before, attributes can either be stored in helper tables, or in a
single central skinny table. The developer makes this choice on a case by
case basis. For the most part, attribute data is stored in helper tables so
that they can take full advantage of relational data modeling and because
they will generally be more efficient. Occasionally, a data model will use
skinny tables because doing so allows developers and users to dynamically
update the set of attributes stored on an object without updating the data
model at the code level. The bottom line: Helper tables are more functional
and more efficient, skinny tables are more flexible but limited.</para>
</sect3>

<sect3 id="objects-design-relation-types" xreflabel="Relation Types">
<title>Relation Types</title>

<para>Many OpenACS 3.x modules use <emphasis>mapping tables</emphasis> to model relationships
between application objects. Again, the 3.x user/groups system provides the
canonical example of this design style. In that system, there was a single
table called <computeroutput>user_group_map</computeroutput> that kept track of which users
belonged to what groups. In addition, as we discussed in the previous
section, the system used the <computeroutput>user_group_member_fields</computeroutput> and
<computeroutput>user_group_member_fields_map</computeroutput> tables to allow developers to
attach custom attributes to group members. In fact, these attributes were not
really attached to the users, but to the fact that a user was a member of a
particular group - a subtle but important distinction.</para>

<para>In OpenACS 4, <emphasis>relation types</emphasis> generalize this mechanism. Relation
types allow developers to define general mappings from objects of a given
type T, to other objects of a given type R. Each relation type is a subtype
of <computeroutput>acs_object</computeroutput>, extended with extra attributes that store
constraints on the relation, and the types of objects the relation actually
maps. In turn, each instance of a relation type is an object that represents
a single fact of the form &quot;the object t of type T is related to the
object r of type R.&quot; That is, each instance of a relation type is
essentially just a pair of objects.</para>

<para>Relation types generalize mapping tables. For example, the 3.x user/groups
data model can be largely duplicated using a single relation type describing
the &quot;group membership&quot; relation. Group types would then be subtypes
of this membership relation type. Group type attributes would be attached to
the relation type itself. Group member attributes would be attached to
instances of the membership relation. Finally, the mapping table would be
replaced by a central skinny table that the relation type system defines.</para>

<para>Relation types should be used when you want to be able to attach data to
the &quot;fact&quot; that object X and object Y are related to each other. On
the face of it, they seem like a redundant mechanism however, since one could
easily create a mapping table to do the same thing. The advantage of
registering this table as a relation type is that in principle the OpenACS 4
object system could use the meta data in the types table to do useful things
in a generic way on all relation types. But this mechanism doesn&#39;t really
exist yet.</para>

<para>Relation types are a somewhat abstract idea. To get a better feel for
them, you should just skip to the <link linkend="object-system-design-relsmodel">data model</link>.</para>
</sect3>

</sect2>

<sect2 id="object-system-design-summary" xreflabel="Summary and Design Considerations">
<title>Summary and Design Considerations</title>


<para>The OpenACS 4 Object Model is designed to generalize and unify the following
mechanisms that are repeatedly implemented in OpenACS-based systems to manage
generic and application specific metadata:</para>

<sect3 id="objects-design-why-not-objdb">
<title>Why not Object Databases?</title>

<para>The presence of a framework for subtyping and inheritance always brings up
the question of why we don&#39;t just use an object database. The main reason
is that all of the major object database vendors ship products that are
effectively tied to some set of object oriented programming languages. Their
idea is to provide tight language-level integration to lower the
&quot;impedance mismatch&quot; between the database and the language.
Therefore, database objects and types are generally directly modeled on
language level objects and types. Of course, this makes it nearly impossible
to interact with the database from a language that does not have this tight
coupling, and it limits the data models that we can write to ideas that are
expressible in the host language. In particular, we lose many of the best
features of the relational database model. This is a disaster from an ease of
use standpoint.
	  </para>
<para>The &quot;Object relational&quot; systems provide an interesting
alternative. Here, some notion of subtyping is embedded into an existing SQL
or SQL-like database engine. Examples of systems like this include the new
Informix, PostgreSQL 7, and Oracle has something like this too. The main
problem with these systems: each one implements their own nonportable
extensions to SQL to implement subtyping. Thus, making OpenACS data models
portable would become even more difficult. In addition, each of these object
systems have strange limitations that make using inheritance difficult in
practice. Finally, object databases are not as widely used as traditional
relational systems. They have not been tested as extensively and their
scalability to very large databases is not proven (though some will disagree
with this statement).</para>
</sect3>

<sect3 id="objects-design-oracle">
<title>Oracle</title>

<para>The conclusion: the best design is to add a limited notion of subtyping to
our existing relational data model. By doing this, we retain all the power of
the relational data model while gaining the object oriented features we need
most.</para>

<para>In the context of OpenACS 4, this means using the object model to make our
data models more flexible, so that new modules can easily gain access to
generic features. However, while the API itself doesn&#39;t enforce the idea
that applications only use the object model for metadata, it is also the case
that the data model is not designed to scale too large type hierarchies. In
the more limited domain of the metadata model, this is acceptable since the
type hierarchy is fairly small. But the object system data model is not
designed to support, for example, a huge type tree like the Java run time
libraries might define.</para>

<para>This last point cannot be over-stressed: <emphasis role="strong">the object model is not
meant to be used for large scale application data storage</emphasis>. It is
meant to represent and store metadata, not application data.</para>
</sect3>
</sect2>

<sect2 id="object-system-design-datamodel">
<title>Data Model</title>


<para>Like most data models, the OpenACS Core data model has two levels:</para>

<orderedlist>
<listitem><para>The <emphasis>knowledge level</emphasis> (i.e. the metadata model)</para></listitem>

<listitem><para>The <emphasis>operational level</emphasis> (i.e. the concrete data model)</para></listitem>
</orderedlist>

<para>
You can browse the data models themselves from here: 
</para>

<itemizedlist>
<listitem><para><ulink url="/doc/sql/display-sql?url=acs-metadata-create.sql&amp;package_key=acs-kernel">
acs-metadata-create.sql</ulink></para></listitem>

<listitem><para><ulink url="/doc/sql/display-sql?url=acs-objects-create.sql&amp;package_key=acs-kernel">
acs-objects-create.sql</ulink></para></listitem>

<listitem><para><ulink url="/doc/sql/display-sql?url=acs-relationships-create.sql&amp;package_key=acs-kernel">
acs-relationships-create.sql</ulink></para></listitem>
</itemizedlist>

<para>(Note that we have subdivided the operational level into the latter two
files.)</para>

<para>The operational level depends on the knowledge level, so we discuss the
knowledge level first. In the text below, we include abbreviated versions of
the SQL definitions of many tables. Generally, these match the actual
definitions in the existing data model but they are meant to reflect design
information, not implementation. Some less relevant columns may be left out,
and things like constraint names are not included.</para>

<sect3 id="objects-design-knowledge-level">
<title>Knowledge-Level Model</title>

<para>The knowledge level data model for OpenACS objects centers around three tables
that keep track of object types, attributes, and relation types. The first
table is <computeroutput>acs_object_types</computeroutput>, shown here in an abbreviated
form:</para>

 

<programlisting>

<computeroutput>create table acs_object_types (
        object_type          varchar(1000) not null primary key,
        supertype            references acs_object_types (object_type),
        abstract_p           char(1) default &#39;f&#39; not null
        pretty_name          varchar(1000) not null unique,
        pretty_plural        varchar(1000) not null unique,
        table_name           varchar(30) not null unique,
        id_column            varchar(30) not null,
        name_method          varchar(30),
        type_extension_table varchar(30)
);
</computeroutput>

</programlisting>


<para>This table contains one row for every object type in the system. The key
things to note about this table are:</para>

<itemizedlist>
<listitem><para>For every type, we store metadata for how to display this type in certain
contexts (<computeroutput>pretty_name</computeroutput> and <computeroutput>pretty_plural</computeroutput>).</para></listitem>

<listitem><para>If the type is a subtype, then its parent type is stored in the column
<computeroutput>supertype</computeroutput>.</para></listitem>

<listitem><para>We support a notion of &quot;abstract&quot; types that contain no
instances (as of 9/2000 this is not actually used). These types exist only to
be subtyped. An example might be a type representing &quot;shapes&quot; that
contains common characteristics of all shapes, but which is only used to
create subtypes that represent real, concrete shapes like circles, squares,
and so on.</para></listitem>

<listitem><para>Every type defines a table in which one can find one row for every
instance of this type (<computeroutput>table_name</computeroutput>, <computeroutput>id_column</computeroutput>).</para></listitem>

<listitem><para><computeroutput>type_extension_table</computeroutput> is for naming a table that stores extra
generic attributes.</para></listitem>
</itemizedlist>

<para>The second table we use to describe types is <computeroutput>acs_attributes</computeroutput>.
Each row in this table represents a single attribute on a specific object
type (e.g. the &quot;password&quot; attribute of the &quot;user&quot; type).
Again, here is an abbreviated version of what this table looks like. The
actual table used in the implementation is somewhat different and is
discussed in a separate document.</para>

 

<programlisting>

<computeroutput>create table acs_attributes (
        attribute_id    integer not null primary key
        object_type     not null references acs_object_types (object_type),
        attribute_name  varchar(100) not null,
        pretty_name     varchar(100) not null,
        pretty_plural   varchar(100),
        sort_order      integer not null,
        datatype        not null,
        default_value   varchar(4000),
        storage         varchar(13) default &#39;type_specific&#39;
                        check (storage in (&#39;type_specific&#39;,
                                           &#39;generic&#39;)),
        min_n_values    integer default 1 not null,
        max_n_values    integer default 1 not null,
        static_p        varchar(1)
);
</computeroutput>

</programlisting>


<para>The following points are important about this table:</para>

<itemizedlist>
<listitem><para>Every attribute has a unique identifier.</para></listitem>

<listitem><para>Every attribute is associated with an object type.</para></listitem>

<listitem><para>We store various things about each attribute for presentation
(<computeroutput>pretty_name</computeroutput>, <computeroutput>sort_order</computeroutput>).</para></listitem>

<listitem><para>The <computeroutput>data_type</computeroutput> column stores type information on this
attribute. This is not the SQL type of the attribute; it is just a human
readable name for the type of data we think the attribute holds (e.g.
&quot;String&quot;, or &quot;Money&quot;). This might be used later to
generate a user interface.</para></listitem>

<listitem><para>The <computeroutput>sort_order</computeroutput> column stores information about how to sort
the attribute values.</para></listitem>

<listitem><para>Attributes can either be stored explicitly in a table (&quot;type
specific storage&quot;) or in a skinny table (&quot;generic storage&quot;).
In most cases, an attribute maps directly to a column in the table identified
by the <computeroutput>table_name</computeroutput> of the corresponding object type, although, as
mentioned above, we sometimes store attribute values as key-value pairs in a
&quot;skinny&quot; table. However, when you ask the question &quot;What are
the attributes of this type of object?&quot;, you don&#39;t really care about
how the values for each attribute are stored (in a column or as key-value
pairs); you expect to receive the complete list of all attributes.</para></listitem>

<listitem><para>The <computeroutput>max_n_values</computeroutput> and <computeroutput>min_n_values</computeroutput> columns
encode information about the number of values an attribute may hold.
Attributes can be defined to hold 0 or more total values.</para></listitem>

<listitem><para>The <computeroutput>static_p</computeroutput> flag indicates whether this attribute value is
shard by all instances of a type, as with static member fields in C++. Static
attribute are like group level attributes in OpenACS 3.x.</para></listitem>
</itemizedlist>


<para>The final part of the knowledge level model keeps track of relationship
types. We said above that object relationships are used to generalize the 3.x
notion of <emphasis>group member fields</emphasis>. These were fields that a developer
could store on each member of a group, but which were contextualized to the
membership relation. That is, they were really &quot;attached&quot; to the
fact that a user was a member of a particular group, and not really attached
to the user. This is a subtle but important distinction, because it allowed
the 3.x system to store multiple sets of attributes on a given user, one set
for each group membership relation in which they participated.</para>

<para>In OpenACS 4, this sort of data can be stored as a relationship type, in <anchor id="object-system-design-relsmodel"/>
<computeroutput>acs_rel_types</computeroutput>. The key parts of this table look like this:</para>

 

<programlisting>

<computeroutput>create table acs_rel_types (
        rel_type        varchar(1000) not null
                        references acs_object_types(object_type),
        object_type_one not null
                        references acs_object_types (object_type),
        role_one        references acs_rel_roles (role),
        object_type_two not null
                        references acs_object_types (object_type),
        role_two        references acs_rel_roles (role)
        min_n_rels_one  integer default 0 not null,
        max_n_rels_one  integer,
        min_n_rels_two  integer default 0 not null,
        max_n_rels_two  integer
);
</computeroutput>

</programlisting>


<para>Things to note about this table:</para>

<itemizedlist>
<listitem><para>The main part of this table records the fact that the relation is between
instances of <computeroutput>object_type_one</computeroutput> and instances of
<computeroutput>object_type_two</computeroutput>. Therefore, each instance of this relation type
will be a pair of objects of the appropriate types.</para></listitem>

<listitem><para>The <computeroutput>role</computeroutput> columns store human readable names for the roles
played by each object in the relation (e.g. &quot;employee&quot; and
&quot;employer&quot;). Each role must appear in the
<computeroutput>acs_rel_roles</computeroutput>.</para></listitem>

<listitem><para>The <computeroutput>min_n_rels_one</computeroutput> column, and its three friends allow the
programmer to specify constraints on how many objects any given object can be
related to on either side of the relation.</para></listitem>
</itemizedlist>


<para>This table is easier to understand if you also know how the <link linkend="object-system-design-acs-rels"><computeroutput>acs_rels</computeroutput> table</link> works.</para>

<para>To summarize, the <computeroutput>acs_object_types</computeroutput> and
<computeroutput>acs_attributes</computeroutput> tables store metadata that describes every object
type and attribute in the system. These tables generalize the group types
data model in OpenACS 3.x. The <computeroutput>acs_rel_types</computeroutput> table stores
information about relation types.</para>

<para>This part of the data model is somewhat analogous to the data dictionary
in Oracle. The information stored here is primarily metadata that describes
the data stored in the <link linkend="objects-design-op-level">operational level</link> of the data
model, which is discussed next.</para>
</sect3>

<sect3 id="objects-design-op-level">
<title>Operational-level Data Model</title>

<para>The operational level data model centers around the
<computeroutput>acs_objects</computeroutput> table. This table contains a single row for every
instance of the type <computeroutput>acs_object</computeroutput>. The table contains the
object&#39;s unique identifier, a reference to its type, security
information, and generic auditing information. Here is what the table looks
like:</para>

 

<programlisting>

<computeroutput>create table acs_objects (
        object_id               integer not null,
        object_type             not null
                                references acs_object_types (object_type),
        context_id              references acs_objects(object_id),
        security_inherit_p      char(1) default &#39;t&#39; not null,
                                check (security_inherit_p in (&#39;t&#39;, &#39;f&#39;)),
        creation_user           integer,
        creation_date           date default sysdate not null,
        creation_ip             varchar(50),
        last_modified           date default sysdate not null,
        modifying_user          integer,
        modifying_ip            varchar(50)
);
</computeroutput>

</programlisting>


<para>As we said in Section III, security contexts are hierarchical and also
modeled as objects. There is another table called
<computeroutput>acs_object_context_index</computeroutput> that stores the context hierarchy.</para>

<para>Other tables in the core data model store additional information related
to objects. The table <computeroutput>acs_attribute_values</computeroutput> and
<computeroutput>acs_static_attr_values</computeroutput> are used to store attribute values that
are not stored in a helper table associated with the object&#39;s type. The
former is used for instance attributes while the latter is used for
class-wide &quot;static&quot; values. These tables have the same basic form,
so we&#39;ll only show the first:</para>

 

<programlisting>

<computeroutput>create table acs_attribute_values (
        object_id       not null
                        references acs_objects (object_id) on delete cascade,
        attribute_id    not null
                        references acs_attributes (attribute_id),
        attr_value      varchar(4000),
        primary key     (object_id, attribute_id)
);
</computeroutput>

</programlisting>


<para>Finally, the table <computeroutput>acs_rels</computeroutput> <anchor id="object-system-design-acs-rels"/>is used to store object pairs
that are instances of a relation type.</para>

 

<programlisting>

<computeroutput>create table acs_rels (
        rel_id          not null
                        references acs_objects (object_id)
                        primary key
        rel_type        not null
                        references acs_rel_types (rel_type),
        object_id_one   not null
                        references acs_objects (object_id),
        object_id_two   not null
                        references acs_objects (object_id),
        unique (rel_type, object_id_one, object_id_two)
);
</computeroutput>

</programlisting>


<para>This table is somewhat subtle:</para>

<itemizedlist>
<listitem><para><computeroutput>rel_id</computeroutput> is the ID of an <emphasis>instance</emphasis> of some relation
type. We do this so we can store all the mapping tables in this one
table.</para></listitem>

<listitem><para><computeroutput>rel_type</computeroutput> is the ID of the relation type to which this object
belongs.</para></listitem>

<listitem><para>The next two object IDs are the IDs of the objects being mapped.</para></listitem>
</itemizedlist>

<para>All this table does is store one row for every pair of objects that
we&#39;d like to attach with a relation. Any additional attributes that
we&#39;d like to attach to this pair of objects is specified in the
attributes of the relation type, and could be stored in any number of places.
As in the 3.x user/groups system, these places include helper tables or
generic skinny tables.</para>

<para>This table, along with <computeroutput>acs_attributes</computeroutput> and
<computeroutput>acs_attribute_values</computeroutput> generalize the old user/group tables
<computeroutput>user_group_map</computeroutput>, <computeroutput>user_group_member_fields_map</computeroutput> and
<computeroutput>user_group_member_fields</computeroutput>.</para>
</sect3>
<sect3 id="objects-design-discussion">
<title>Summary and Discussion</title>

<para>The core tables in the OpenACS 4 data model store information about instances
of object types and relation types. The <computeroutput>acs_object</computeroutput> table
provides the central location that contains a single row for every object in
the system. Services can use this table along with the metadata in stored in
the knowledge level data model to create, manage, query and manipulate
objects in a uniform manner. The <computeroutput>acs_rels</computeroutput> table has an analogous
role in storing information on relations.</para>

<para>These are all the tables that we&#39;ll discuss in this document. The rest
of the Kernel data model is described in the documents for <link linkend="subsites-design">subsites</link>, the <link linkend="permissions-design">permissions</link> system and for the <link linkend="groups-design">groups</link> system.</para>

<para>Some examples of how these tables are used in the system can be found in
the discussion of the API, which comes next.</para>
</sect3>
</sect2>

<sect2 id="object-system-design-api">
<title>API</title>


<para>Now we&#39;ll examine each piece of the API in detail. Bear in mind that
the Object Model API is defined primarily through PL/SQL packages.</para>

<sect3 id="objects-design-object-types" xreflabel="Object Types and Attributes">
<title>Object Types and Attributes</title>

<para>The object system provides an API for creating new object types and then
attaching attributes to them. The procedures <computeroutput>create_type</computeroutput> and
<computeroutput>drop_type</computeroutput> are used to create and delete type definitions.</para>

<para>The two calls show up in the package <computeroutput>acs_object_type</computeroutput>.</para>

 

<programlisting>

<computeroutput>  procedure create_type (
    object_type         in acs_object_types.object_type%TYPE,
    pretty_name         in acs_object_types.pretty_name%TYPE,
    pretty_plural       in acs_object_types.pretty_plural%TYPE,
    supertype           in acs_object_types.supertype%TYPE
                           default &#39;acs_object&#39;,
    table_name          in acs_object_types.table_name%TYPE default null,
    id_column           in acs_object_types.id_column%TYPE default &#39;XXX&#39;,
    abstract_p          in acs_object_types.abstract_p%TYPE default &#39;f&#39;,
    type_extension_table in acs_object_types.type_extension_table%TYPE
                            default null,
    name_method         in acs_object_types.name_method%TYPE default null
  );

  -- delete an object type definition
  procedure drop_type (
    object_type         in acs_object_types.object_type%TYPE,
    cascade_p           in char default &#39;f&#39;
  );
</computeroutput>

</programlisting>


<para>Here the <computeroutput>cascade_p</computeroutput> argument indicates whether dropping a type
should also remove all its subtypes from the system.</para>

<para>We define a similar interface for defining attributes in the package
<computeroutput>acs_attribute</computeroutput>:</para>

 

<programlisting>

<computeroutput>  function create_attribute (
    object_type         in acs_attributes.object_type%TYPE,
    attribute_name      in acs_attributes.attribute_name%TYPE,
    datatype            in acs_attributes.datatype%TYPE,
    pretty_name         in acs_attributes.pretty_name%TYPE,
    pretty_plural       in acs_attributes.pretty_plural%TYPE default null,
    table_name          in acs_attributes.table_name%TYPE default null,
    column_name         in acs_attributes.column_name%TYPE default null,
    default_value       in acs_attributes.default_value%TYPE default null,
    min_n_values        in acs_attributes.min_n_values%TYPE default 1,
    max_n_values        in acs_attributes.max_n_values%TYPE default 1,
    sort_order          in acs_attributes.sort_order%TYPE default null,
    storage             in acs_attributes.storage%TYPE default &#39;type_specific&#39;,
    static_p            in acs_attributes.static_p%TYPE default &#39;f&#39;
  ) return acs_attributes.attribute_id%TYPE;

  procedure drop_attribute (
    object_type in varchar,
    attribute_name in varchar
  );

</computeroutput>

</programlisting>


<para>In addition, the following two calls are available for attaching extra
annotations onto attributes:</para>

 

<programlisting>

<computeroutput>  procedure add_description (
    object_type         in acs_attribute_descriptions.object_type%TYPE,
    attribute_name      in acs_attribute_descriptions.attribute_name%TYPE,
    description_key     in acs_attribute_descriptions.description_key%TYPE,
    description         in acs_attribute_descriptions.description%TYPE
  );

  procedure drop_description (
    object_type         in acs_attribute_descriptions.object_type%TYPE,
    attribute_name      in acs_attribute_descriptions.attribute_name%TYPE,
    description_key     in acs_attribute_descriptions.description_key%TYPE
  );
</computeroutput>

</programlisting>


<para>At this point, what you must do to hook into the object system from your
own data model becomes clear:</para>

<itemizedlist>
<listitem><para>Create a table that will store the instances of the new type.</para></listitem>

<listitem><para>Call <computeroutput>acs_object_type.create_type()</computeroutput> to fill in the metadata
table on this new type. If you want your objects to appear in the
<computeroutput>acs_objects</computeroutput> table, then your new type must be a subtype of
<computeroutput>acs_object</computeroutput>.</para></listitem>

<listitem><para>Call <computeroutput>acs_attribute.create_attribute()</computeroutput> to fill in information
on the attributes that this type defines.</para></listitem>
</itemizedlist>

<para>So, suppose we are writing a new version of the ticket tracker for 4.0. We
probably define a table to store tickets in, and each ticket might have an ID
and a description. If we want each ticket to be an object, then
<computeroutput>ticket_id</computeroutput> must reference the <computeroutput>object_id</computeroutput> column in
<computeroutput>acs_objects</computeroutput>:</para>

 

<programlisting>

<computeroutput>create table tickets ( 
    ticket_id references acs_objects (object_id),
    description varchar(512), 
    ... 
) ;
</computeroutput>

</programlisting>


<para>In addition to defining the table, we need this extra PL/SQL code to hook
into the object type tables:</para>

 

<programlisting>

<computeroutput>declare
 attr_id acs_attributes.attribute_id%TYPE;
begin
 acs_object_type.create_type (
   supertype =&gt; &#39;acs_object&#39;,
   object_type =&gt; &#39;ticket&#39;,
   pretty_name =&gt; &#39;Ticket&#39;,
   pretty_plural =&gt; &#39;Tickets&#39;,
   table_name =&gt; &#39;tickets&#39;,
   id_column =&gt; &#39;ticket_id&#39;,
   name_method =&gt; &#39;acs_object.default_name&#39;
 );

 attr_id := acs_attribute.create_attribute (
   object_type =&gt; &#39;ticket&#39;,
   attribute_name =&gt; &#39;description&#39;,
   datatype =&gt; &#39;string&#39;,
   pretty_name =&gt; &#39;Description&#39;,
   pretty_plural =&gt; &#39;Descriptions&#39;
 );

 ... more attributes ...

commit;
end;
</computeroutput>

</programlisting>


<para>Thus, with a small amount of extra code, the new ticket tracker will now
automatically be hooked into every generic object service that exists. Better
still, this code need not be changed as new services are added. As an aside,
the most important service that requires you to subtype
<computeroutput>acs_object</computeroutput> is <link linkend="permissions-design">permissions</link>.</para>
</sect3>
<sect3 id="objects-design-objects">
<title>Objects</title>

<para>The next important piece of the API is defined in the
<computeroutput>acs_object</computeroutput> package, and is concerned with creating and managing
objects. This part of the API is designed to take care of the mundane
bookkeeping needed to create objects and query their attributes.
Realistically however, limitations in PL/SQL and Oracle will make it hard to
build generic procedures for doing large scale queries in the object system,
so developers who need to do this will probably have to be fairly familiar
with the data model at a lower level.</para>

<para>The function <computeroutput>acs_object.new()</computeroutput> makes a new object for you. The
function <computeroutput>acs_object.del()</computeroutput> deletes an object. As before, this
is an abbreviated interface with all the long type specs removed. See the
data model or developer&#39;s guide for the full interface.</para>

 

<programlisting>

<computeroutput> function new (
  object_id     in acs_objects.object_id%TYPE default null,
  object_type   in acs_objects.object_type%TYPE
                           default &#39;acs_object&#39;,
  creation_date in acs_objects.creation_date%TYPE
                           default sysdate,
  creation_user in acs_objects.creation_user%TYPE
                           default null,
  creation_ip   in acs_objects.creation_ip%TYPE default null,
  context_id    in acs_objects.context_id%TYPE default null
 ) return acs_objects.object_id%TYPE;

 procedure delete (
  object_id     in acs_objects.object_id%TYPE
 );
</computeroutput>

</programlisting>


<para>Next, we define some generic functions to manipulate attributes. Again,
these interfaces are useful to an extent, but for large scale queries,
it&#39;s likely that developers would have to query the data model directly,
and then encapsulate their queries in procedures.</para>

<para>For names, the <computeroutput>default_name</computeroutput> function is used if you don&#39;t
want to define your own name function.</para>

 

<programlisting>

<computeroutput> function name (
  object_id     in acs_objects.object_id%TYPE
 ) return varchar;

 function default_name (
  object_id     in acs_objects.object_id%TYPE
 ) return varchar;

</computeroutput>

</programlisting>


<para>The following functions tell you where attributes are stored, and fetch
single attributes for you.</para>

 

<programlisting>

<computeroutput> procedure get_attribute_storage ( 
   object_id_in      in  acs_objects.object_id%TYPE,
   attribute_name_in in  acs_attributes.attribute_name%TYPE,
   v_column          out varchar2,
   v_table_name      out varchar2,
   v_key_sql         out varchar2
 );

 function get_attribute (
   object_id_in      in  acs_objects.object_id%TYPE,
   attribute_name_in in  acs_attributes.attribute_name%TYPE
 ) return varchar2;

 procedure set_attribute (
   object_id_in      in  acs_objects.object_id%TYPE,
   attribute_name_in in  acs_attributes.attribute_name%TYPE,
   value_in          in  varchar2
 );
</computeroutput>

</programlisting>


<para>The main use of the <computeroutput>acs_object</computeroutput> package is to create
application objects and make them available for services via the
<computeroutput>acs_objects</computeroutput> table. To do this, you just have to make sure you
call <computeroutput>acs_object.new()</computeroutput> on objects that you wish to appear in the
<computeroutput>acs_objects</computeroutput> table. In addition, all such objects must be
instances of some subtype of <computeroutput>acs_object</computeroutput>.</para>

<para>Continuing the ticket example, we might define the following sort of
procedure for creating a new ticket:</para>

 

<programlisting>

<computeroutput> function new_ticket (
  package_id        in tickets.ticket_id%TYPE 
            default null,
  description       in tickets.description%TYPE default &#39;&#39;,
     ...
  ) return tickets.ticket_id%TYPE 
  is 
   v_ticket_id tickets
  begin
   v_ticket_id := acs_object.new(
      object_id =&gt; ticket_id,
      object_type =&gt; &#39;ticket&#39;,
        ...
     );
    insert into tickets
    (ticket_id, description)
    values
    (v_ticket_id, description);
    return v_ticket_id;
  end new_ticket;
</computeroutput>

</programlisting>


<para>This function will typically be defined in the context of a PL/SQL
package, but we&#39;ve left it stand-alone here for simplicity.</para>

<para>To summarize: in order to take advantage of OpenACS 4 services, a new
application need only do three things:</para>

<itemizedlist>
<listitem><para>Define a data model to describe application objects. This can just be a
normal SQL table.</para></listitem>

<listitem><para>Create an object type, using code like in the example from the previous
section.</para></listitem>

<listitem><para>Make sure application objects are created using
<computeroutput>acs_object.new()</computeroutput> in addition to whatever SQL code is needed to
insert a new row into the application data model.</para></listitem>
</itemizedlist>

<para>One of the design goals of OpenACS 4 was to provide a straightforward and
consistent mechanism to provide applications with general services. What we
have seen here is that three simple steps and minimal changes in the
application data model are sufficient to make sure that application objects
are represented in the <computeroutput>acs_objects</computeroutput> table. Subsequently, all of
the general services in OpenACS 4 (i.e. permissions, general comments, and so on)
are written to work with any object that appears in <computeroutput>acs_objects</computeroutput>.
Therefore, in general these three steps are sufficient to make OpenACS 4 services
available to your application.</para>
</sect3>
<sect3 id="objects-design-relat-types">
<title>Relation Types</title>

<para>The relations system defines two packages: <computeroutput>acs_rel_type</computeroutput> for
creating and managing relation types, and <computeroutput>acs_rel</computeroutput> for relating
objects.</para>

<para>These two procedures just insert and remove roles from the
<computeroutput>acs_rel_roles</computeroutput> table. This table stores the legal relationship
&quot;roles&quot; that can be used when creating relation types. Examples of
roles are, say, &quot;member&quot;, or &quot;employer&quot;.</para>

 

<programlisting>

<computeroutput> procedure create_role (
    role        in acs_rel_roles.role%TYPE
  );

  procedure drop_role (
    role        in acs_rel_roles.role%TYPE
  );
</computeroutput>

</programlisting>


<para>The main functions in the <computeroutput>acs_rel_type</computeroutput> package are used to
create and drop relation types.</para>

 

<programlisting>

<computeroutput>  procedure create_type (
    rel_type            in acs_rel_types.rel_type%TYPE,
    pretty_name         in acs_object_types.pretty_name%TYPE,
    pretty_plural       in acs_object_types.pretty_plural%TYPE,
    supertype           in acs_object_types.supertype%TYPE
                           default &#39;relationship&#39;,
    table_name          in acs_object_types.table_name%TYPE,
    id_column           in acs_object_types.id_column%TYPE,
    abstract_p          in acs_object_types.abstract_p%TYPE default &#39;f&#39;,
    type_extension_table in acs_object_types.type_extension_table%TYPE
                            default null,
    name_method         in acs_object_types.name_method%TYPE default null,
    object_type_one     in acs_rel_types.object_type_one%TYPE,
    role_one            in acs_rel_types.role_one%TYPE default null,
    min_n_rels_one      in acs_rel_types.min_n_rels_one%TYPE,
    max_n_rels_one      in acs_rel_types.max_n_rels_one%TYPE,
    object_type_two     in acs_rel_types.object_type_two%TYPE,
    role_two            in acs_rel_types.role_two%TYPE default null,
    min_n_rels_two      in acs_rel_types.min_n_rels_two%TYPE,
    max_n_rels_two      in acs_rel_types.max_n_rels_two%TYPE
  );

  procedure drop_type (
    rel_type            in acs_rel_types.rel_type%TYPE,
    cascade_p           in char default &#39;f&#39;
  );
</computeroutput>

</programlisting>


<para>Finally, the <computeroutput>acs_rel</computeroutput> package provides an API that you use to
create and destroy instances of a relation type:</para>

 

<programlisting>

<computeroutput>  function new (
    rel_id              in acs_rels.rel_id%TYPE default null,
    rel_type            in acs_rels.rel_type%TYPE default &#39;relationship&#39;,
    object_id_one       in acs_rels.object_id_one%TYPE,
    object_id_two       in acs_rels.object_id_two%TYPE,
    context_id          in acs_objects.context_id%TYPE default null,
    creation_user       in acs_objects.creation_user%TYPE default null,
    creation_ip         in acs_objects.creation_ip%TYPE default null
  ) return acs_rels.rel_id%TYPE;

  procedure delete (
    rel_id      in acs_rels.rel_id%TYPE
  );
</computeroutput>

</programlisting>


<para>A good example of how to use relation types appears in the OpenACS 4 data
model for <emphasis>groups</emphasis>. As in 3.x, group membership is modeled using a
mapping table, but now we create this mapping using relation types instead of
explicitly creating a table. First, we create a helper table to store state
on each membership fact:</para>

 

<programlisting>

<computeroutput>create table membership_rels (
        rel_id          constraint membership_rel_rel_id_fk
                        references acs_rels (rel_id)
                        constraint membership_rel_rel_id_pk
                        primary key,
        -- null means waiting for admin approval
        member_state    varchar(20) constraint membership_rel_mem_ck
                        check (member_state in (&#39;approved&#39;, &#39;banned&#39;,
                                                &#39;rejected&#39;, &#39;deleted&#39;))
);
</computeroutput>

</programlisting>


<para>Then, we create a new object type to describe groups.</para>

 

<programlisting>

<computeroutput> acs_object_type.create_type (
   object_type =&gt; &#39;group&#39;,
   pretty_name =&gt; &#39;Group&#39;,
   pretty_plural =&gt; &#39;Groups&#39;,
   table_name =&gt; &#39;groups&#39;,
   id_column =&gt; &#39;group_id&#39;,
   type_extension_table =&gt; &#39;group_types&#39;,
   name_method =&gt; &#39;acs_group.name&#39;
 );
</computeroutput>

</programlisting>


<para>In this example, we&#39;ve made groups a subtype of
<computeroutput>acs_object</computeroutput> to make the code simpler. The actual data model is
somewhat different. Also, we&#39;ve assumed that there is a helper table
called <computeroutput>groups</computeroutput> to store information on groups, and that there is
a helper table called <computeroutput>group_types</computeroutput> that has been defined to store
extra attributes on groups.</para>

<para>Now, assuming we have another object type called <computeroutput>person</computeroutput> to
represent objects that can be group members, we define the following
relationship type for group membership:</para>

 

<programlisting>

<computeroutput> acs_rel_type.create_role (&#39;member&#39;);

 acs_rel_type.create_type (
   rel_type =&gt; &#39;membership_rel&#39;,
   pretty_name =&gt; &#39;Membership Relation&#39;,
   pretty_plural =&gt; &#39;Membership Relationships&#39;,
   table_name =&gt; &#39;membership_rels&#39;,
   id_column =&gt; &#39;rel_id&#39;,
   object_type_one =&gt; &#39;group&#39;,
   min_n_rels_one =&gt; 0, max_n_rels_one =&gt; null,
   object_type_two =&gt; &#39;person&#39;, role_two =&gt; &#39;member&#39;,
   min_n_rels_two =&gt; 0, max_n_rels_two =&gt; null
 );
</computeroutput>

</programlisting>


<para>Now we can define the following procedure to add a new member to a group.
All this function does is create a new instance of the membership relation
type and then insert the membership state into the helper table that we
define above. In the actual implementation, this function is implemented in
the <computeroutput>membership_rel</computeroutput> package. Here we just define an independent
function:</para>

 

<programlisting>

<computeroutput>function member_add (
    rel_id              in membership_rels.rel_id%TYPE default null,
    rel_type            in acs_rels.rel_type%TYPE default &#39;membership_rel&#39;,
    group               in acs_rels.object_id_one%TYPE,
    member              in acs_rels.object_id_two%TYPE,
    member_state        in membership_rels.member_state%TYPE default null,
    creation_user       in acs_objects.creation_user%TYPE default null,
    creation_ip         in acs_objects.creation_ip%TYPE default null
  ) return membership_rels.rel_id%TYPE
  is
    v_rel_id integer;
  begin
      v_rel_id := acs_rel.new (
      rel_id =&gt; rel_id,
      rel_type =&gt; rel_type,
      object_id_one =&gt; group,
      object_id_two =&gt; person,
      context_id =&gt; object_id_one,
      creation_user =&gt; creation_user,
      creation_ip =&gt; creation_ip
    );

    insert into membership_rels
     (rel_id, member_state)
    value
     (v_rel_id, new.member_state);
  end;
</computeroutput>

</programlisting>


<para>Another simple function can be defined to remove a member from a
group:</para>

 

<programlisting>

<computeroutput>  procedure member_delete (
    rel_id  in membership_rels.rel_id%TYPE
  )
  is
  begin
    delete from membership_rels
    where rel_id = membership_rel.delete.rel_id;

    acs_rel.del(rel_id);
  end;
</computeroutput>

</programlisting>

</sect3>
<sect3 id="objects-design-discuss">
<title>Summary and Discussion</title>

<para>The Object Model&#39;s API and data model provides a small set of simple
procedures that allow applications to create object types, object instances,
and object relations. Most of the data model is straightforward; the relation
type mechanism is a bit more complex, but in return it provides functionality
on par with the old user/groups system in a more general way.</para>
</sect3>
</sect2>

<sect2 id="object-system-design-future">
<title>Future Improvements/Areas of Likely Change</title>


<para>Nothing here yet.</para>

</sect2>

<sect2 id="object-system-design-authors">
<title>Authors</title>


<para>Pete Su generated this document
from material culled from other documents by Michael Yoon, Richard Li and Rafael Schloming. But, any remaining lies
are his and his alone.</para>

</sect2>

<sect2 id="object-system-design-revision-hist">
<title>Revision History</title>


 
<informaltable>
<tgroup cols="4">
<tbody>
<row>
<entry><emphasis role="strong">Document Revision #</emphasis></entry>
<entry><emphasis role="strong">Action Taken, Notes</emphasis></entry>
<entry><emphasis role="strong">When?</emphasis></entry>
<entry><emphasis role="strong">By Whom?</emphasis></entry>
</row>

<row>
<entry>0.1</entry>
<entry>Creation</entry>
<entry>9/09/2000</entry>
<entry>Pete Su</entry>
</row>

<row>
<entry>0.2</entry>
<entry>Edited for ACS 4 Beta</entry>
<entry>9/30/2000</entry>
<entry>Kai Wu</entry>
</row>

<row>
<entry>0.3</entry>
<entry>Edited for ACS 4.0.1, fixed some mistakes, removed use of term
&quot;OM&quot;</entry>
<entry>11/07/2000</entry>
<entry>Pete Su</entry>
</row>
</tbody></tgroup></informaltable>


</sect2>

</sect1>