Forum OpenACS Development: relational segments: how do you use them

In a previous thread I was struggling with acs_rels, and Don mentioned using relational segments to "gather togeather" users into a subgroup. As usual I am at a total loss as to how this is done. I understand that a relational segment is a party, therefore you can assign permissions to the party, unlike an acs_rel. But what I don't understand is once I create a rel_segment, which is a party, how do I associate users with this segment, and how do the permissions assigned to the rel_segment apply? Can you be a member of rel_segment?

Collapse
Posted by Tom Jackson on

As an example, if you have a group of a type unicycle_club, which is composed of a set of mini-groups of type uni_group, and possibly direct members as well. Each of these uni_groups has members with a membership_rel subtype of unicyclist_rel, parent_rel or coach_rel. (I had created these rel types as a subtype of membership_rel, but the mistake I made when adding new rels was to add using acs_rel__new, I think I need to use membership_rel__new, but set the rel_type to 'parent_rel', etc.)

Would a relational segment be useful, and how, in gathering togeather all the unicyclists, parents and coaches that are members of a unicycle_club group by virtue of the composition rel between the uni_group and the unicycle_club? Or how and why might you use a relational segment in this situation?

Collapse
Posted by Tom Jackson on

Okay, so I tried the following: I added an acs_rel of type parent_rel to the uni_group, creating a relationship between a user and this group as a parent. I also added a relational segment to the same uni_group on rel type parent_rel. When I looked in the party_approved_member_map, I didn't see any extra rows, I was thinking that one would show up for the relational segment, but it didn't.

So instead of using acs_rel__new, I used membership_rel__new to add the parent_rel. Looking in the mapping table I had the following:


v=# select * from party_approved_member_map where member_id = 823;
 party_id | member_id | tag
----------+-----------+------
     1169 |       823 | 1174
     1161 |       823 | 1174
       -1 |       823 |  824
      291 |       823 |  824
       -2 |       823 |  824
      823 |       823 |    0

The new membership_rel is 1174, so the top two rows were created because of this relationship. 1169 is due to the relational segment, 1161 is due to the new membership of 823 in the uni_group 1161. So I could now assign privs on either of these two party_id's, this is the join point to the acs_permissions grantee_id. The relational segment would be helpful if I wanted to assign the same permissions to all parties to that segment, ie, all uses with rel_type of parent_rel. Assigning pemissions to the 1161 would apply to all members. If I needed specific privileges to a specific member, they would have to be directly assigned.

Does this sound right?

Collapse
Posted by Tom Jackson on

Once the uni_group is created and a membership_rel added of subtype parent_rel, I added this group via a composition_rel of subtype club_cyclist_rel to a group of type unicycle_club.

I then created a relational segment on the unicycle club group for rel_types of parent_rel. As I expected, the parent of the uni_group is now part of the relational segment for the unicycle club.


=# select * from party_approved_member_map where member_id = 823;
 party_id | member_id | tag
----------+-----------+------
     1185 |       823 | 1174
     1177 |       823 | 1174
     1169 |       823 | 1174
     1161 |       823 | 1174
       -1 |       823 |  824
      291 |       823 |  824
       -2 |       823 |  824
      823 |       823 |    0

The composition relationship has created the top two rows, 1177 due to the membership_rel now applying to the unicycle_club group, and 1185 due to the parent_rel now applying to the relational segment on the club group. So, adding one composition rel has resulted in the unicyclist being a member of the club, and their parent being a member of the parents rel segment for the club.

Collapse
Posted by Ola Hansson on
Take a look at acs__add_user() for an example of how to add a user as a member of a relational segment (a.k.a subgroup!). That is happening for every new user that registers with an OpenACS site, I believe - they become members of the "Main Site Members" rel-seg. "Main Site Members", in turn, has a membership relation to the "Registered Users" group which it is a segment of. Members of the relseg are automatically members of the surrounding group, I think.

You may want to create a version of acs__add_user() that makes the rel-seg association more general...

Just my $.02

Collapse
Posted by Tom Jackson on

I think the answer is: create a relational segment on the "registered users" group for rel_type = membership_rel. Adding a member to this relational segment only involves creating an acs_rel through membership_rel__new between the user and the -2 group (registered users). Every subsite gets a similar relational segment:

v=# select * from rel_segments;
 segment_id |         segment_name         | group_id |     rel_type
------------+------------------------------+----------+------------------
        291 | Main Site Members            |       -2 | membership_rel
        649 | GTSubsite Members            |      646 | membership_rel
        798 | test Members                 |      795 | membership_rel
       1169 | Tommys Parents               |     1161 | parent_rel
       1185 | My Unicycle Club Parents     |     1177 | parent_rel
       1190 | My Unicycle Club Unicyclists |     1177 | club_cyclist_rel

Collapse
Posted by Don Baccus on
Also for more complex examples please read the .LRN user management stuff.  Every member of .LRN is added to a global rel segment (user, stafff, etc) and every member of a class is added in a rel segment (professor, student etc)

I'm well aware of the fact this needs good documentation ... unfortunately rel segs were added very late in the life of ACS 4.x Tcl (everyone got moved to Java or laid off, apparently) and as they were at first considered experimental apparently documentation lagged.

Collapse
Posted by Randy O'Meara on
Ola,
    I looked at the acs__add_user() pl code. It does an acs_user__new() and then a membership_rel__new() that relates the new user_id to the magic_object 'registered_users' (actually -2). Is this the relational segment you alluded to above? Is there another step that creates another relational segment that relates the user_id to "Main Site Members"? I didn't find it.

Tom,
    This discussion is very timely. I've been studying essentially this same problem for a long time now. I really would like to create subsite sub-groups and then assign subsite-wide permissions to users depending on their membership in these groups, creating a multiple-role system consisting of administrators, service managers, users, and auditors.

    Were you successful in creating sub-groups and then assigning permissions based on sub-group membership? If so, would you mind sharing the code, tables and pl functions that you developed to implement your "role" system?

Thanks,

Randy

Collapse
Posted by Tom Jackson on

I think the problem is the terminology we are using. Actually you don't have to think about adding a party to a relational segment. A relational segment is a simple way of grouping parties based on their rel_type to a group. It says in effect all parties that have rel_type x to group y are in the segment, like a segment of the population.

So the first step is to think of what kinds of relationships you want to be valid on a group. You then create the rel_type. The rel_type has to have a supertype that traces back to either membership_rel or composition_rel.

Once you have a group of the right type, and a party of the right type, you can make the party a member of the group using either membership_rel__new or composition_rel__new, making sure the rel_type attribute is set to your new rel_type.

Now if you want a relational segment to group parties based on their rel_type to the group, you use rel_segment__new to do that. You just need to name the segment, and know the group_id and the rel_type for the segment.

For every party with a rel_type of the right type to the group, rows will be inserted into party_approved_member_map (assuming you used 'approved' for the member_state attribute to membership_rel__new).

What do rows in this map represent? They represent aliases for the party. All registered users have an alias id of -2. In other words, a party can get permissions on an object_id by virture of permissions given to one of their aliases. If you look above at my printout for member_id = 823 from this mapping table, you will see all the 'alias' party_ids. These ids are joined to the grantee_id in the acs_permissions table for permission checks, therefore the map explodes the member_id into a list of grantee_ids.

All that is left is to assign the privilege you want to the alias (group, relational segment or party) or grantee. The key to understanding all this (for me) was this sublime piece of code:

select 1
 from acs_permissions p, party_approved_member_map m,
 acs_object_context_index c, acs_privilege_descendant_map h
                 where p.object_id = c.ancestor_id
                   and h.descendant = permission_p__privilege
                   and c.object_id = permission_p__object_id
                   and m.member_id = permission_p__party_id
                   and p.privilege = h.privilege
                   and p.grantee_id = m.party_id

Collapse
Posted by Tom Jackson on

Anything this sublime needs more commentary, don't you think? This join, used in acs_permission__permission_p, combines The Holy Trinity of OpenACS Core: Objects, Parties, Privileges. The result, when the join succeeds, is One. Maybe everyone should meditate on this join as their first assignment to understanding OpenACS. :)

Collapse
Posted by bob phillips on
how long since you slept, tom ?

oth, this is a devine thread. ;)

bob

Collapse
Posted by Tom Jackson on
how long since you slept, tom ?

Shoot, I was hoping noone would notice! Actually I spent most of last week flat on my back and in severe pain from a back strain/sprain. The fog cleared at the moment I saw Don's little jewel of code.

Collapse
Posted by Randy O'Meara on
Thank you, Tom.

I'll try to digest this and then come back.

Randy

Collapse
Posted by Ola Hansson on
Hi Randy,

"registered_users" is a group, not a relseg. The relseg I alluded to was "Main Site Members" which is related to this group. The call to membership_rel__new() (which is specific to the "membership_rel" relation type) creates a "relation" between the user and the group, as you say. Only indirectly does it make the user a *member* of the group and associated relsegs:

membership_rel__new() also inserts a row into the "membership_rels" table and there's an insert trigger on that table ("membership_rels_in_tr"). Among other things, this trigger calls party_approved_member__add() - if new.member_state is "approved", that is. party_approved_member__add() basically inserts into party_approved_member_map, both for the group in question and for any relational segment(s) that the relation type may be mapped to.

That is how the newly created user becomes a member of the "Registered Users" group and "Main Site Members" segment.

Evidently, although I havent looked for it, there must be a relational segment already defined in an earlier step in the default installation that goes under the name "Main Site Members", is associated with group -2 ("Registered Users"), and holds relations of the basic type "membership_rel".

It is the fact that there existed a relseg (which was associated with the group and relation type in question) in the first place, that made the user a (approved) member of the relseg, too, and not only of the group.

Does this make sense?

As a side note, there seems to be a rather ample supply of Tcl procs that handle these type/group/segment/relation issues. By using them we may avoid writing separate sets of Pl[Pg]SQL code for Oracle and PostgreSQL in our applications... (Now that we have a good support for APM Tcl callbacks, I mean.)

/Ola

Collapse
Posted by Ola Hansson on
Oops! Looks like I more or less repeated what Tom just said 8-)

At least I learned something while writing the message ...

Collapse
Posted by Don Baccus on
The "Main Site Members" relational segment is created in acs-kernel/sql/*/acs-install.sql.

It could be created when "Registered Users" is created, but I chose to delay it until acs-install because this is where the main site is created and mounted, and where "registered users" is transformed into an application group referencing the main subsite's package id.  Put all my kludges in one basket, so to speak.

I implemented this in 4.6.  Before the main subsite had its own membership group so registered users didn't belong to the main subsite, which is wrong.  aD had actually intended to copy registered users into the main subsite membership group: the 4.1->4.2 upgrade script did this but the registration code wasn't modified to track this change.  I decided it made more sense to just make registered users *be* the main site membership group.  Much simpler than copying users around willy-nilly.

The relational segment is in some sense redundant but doesn't cost anything as it's built on membership_rel as mentioned above - all the rows already exist for the relseg.  Having this relseg around should make it easier to write general API stuff built around relational segments (such as subsite admins or other "role"-implementing relsegs)

Without it in some cases code would need to check the subsite membership group, other cases various relsegs rather than just be oriented around interrogating relsegs.

This is infrastructure that's largely unused at the moment though Lars has been doing some admin UI work for 4.6.4 ...

And yes, please use the Tcl API in acs-subsite/tcl to manipulate groups, relsegments, etc.  If you need to do something that's not covered by the existing API and if that "something" is general enough, let's add it to the API so others can benefit.

And, Mr. Bob Phillips, I caught your code - your claims of being non-contagious while sputtering and coughing into our pitcher of beer notwithstanding!

Collapse
Posted by Tom Jackson on

I'll look at using the tcl code. In order to understand the data model and pl I had found it convenient to just use regular pl statements so I could source the create/drop scripts or other sql scripts while experimenting. I also had to create a custom drop procedure so that I could really drop the test users. Anyway, experiments are over. Time to do something productive.

Collapse
Posted by Randy O'Meara on
Whew! There sure is a great amount of datamodel and pl analytical information in this thread. And I'm beginning to think that I might be able to wend my way through it all.

However, what I'm really interested in is implementing a multi-role system (based on acs-subsite) where I can grant certain rights based on a member's role. I think that the tcl api is what I should use rather than reinventing this wheel. So, given this direction, I would like some help determining exactly what the implementation looks like... described by toolkit tcl calls. Unfortunately, I've searched through the source code (both the 4.6 cvs branch and dotlrn 1) and there are no working examples that I could find. Maybe I can start with what I assume is required to create a single role and grant permissions on the subsite to members in that role.

What I want to do...

Among other roles, I want to create a role called "Community Admin" and associate with it a Main Site registered user "u1" that will have permission to:

  • create and update objects associated with a subsite-aware application that is mounted under this subsite, and
  • accept other Main Site registered users' requests to join this particular community (subsite).

    Since this is all rooted at a subsite and the subsite exists, the subsite Application Group "Community Members" also already exists. Do I need to create any groups in addition to this existing Application Group, or can I just relate a segment of this group into a pseudo-group called "Community Admins" using a relational segment? This setup differs from Tom's in that I really don't require subgroups to further group subsite members.

    There are several parameters that I could use help with (bracketed by ?s below). I'm thinking that the sequence of calls I need to make looks (something) like:

      # Create a rel_type
      set m_rel_type [rel_types::new
          -supertype "membership_rel"
          -role_one ?role_one?
          -role_two ?role_two?
          "adm_rel"                    # rel_type
          "Community Admin"            # pretty_name
          "Community Admins"           # pretty_plural
          ?object_type_one??min_n_rels_one??max_n_rels_one??object_type_two??min_n_rels_two??max_n_rels_two?
    
      # Create a rel_seg
      set m_rel_seg  [rel_segments_new
          -context_id ?context_id?
          -creation_user $user_id
          -creation_ip $peer_addr
          $subsite_app_grp_id          # group_id
          "adm_rel"                    # rel_type
          ?segment_name?
    
      # Grant permissions
      # How to do this?
    
    Can you help me fill in the blanks or nudge me in another direction?

    Thanks,

    Randy

  • Collapse
    Posted by Lars Pind on
    Randy,

    You'll find parts of it in subsite::after_mount in acs-subsite/tcl/subsite-procs.tcl.

    It creates member and admin rel segments, and grants appropriate privileges to those:

    Snippet:


                # Create segment of registered users for administrators
                set segment_name "$truncated_subsite_name Administrators"
                set admin_segment_id [rel_segments_new $subsite_group_id admin_rel $segment_name]

                # Grant admin privileges to the admin segment
                permission::grant \
                    -party_id $admin_segment_id \
                    -object_id $package_id \
                    -privilege admin

                # Grant read/write/create privileges to the member segment
                foreach privilege { read create write } {
                    permission::grant \
                        -party_id $segment_id \
                        -object_id $package_id \
                        -privilege $privilege
                }

    The rel-type is created upon installation time.

    This is on oacs-4-6 tip, unreleased code, will go into an interim 4.6.4 release, and later 5.0 release.

    /Lars

    Collapse
    Posted by Tom Jackson on

    Lars, thanks for the tcl translation of all the hooie I went through above. Wow, the tcl api makes things simple.

    Randy, note that there are two relational segments created above, although Lars only included the Admin segment.

    On creating the rel_type:

    A relation type relates two objects. The type specifies which object_type each object must be in order to allow the relation to exist.

    In the case of making a rel_type for use by a relational segment, the further requirement is that the object_type_one must be a group_type, and object_type_two must be a derivative of a party. If the party type for object_type_two is a person/user or derivative, then the supertype will be membership_rel, otherwise you need a composition_rel.

    In your case, it looks like you want a membership_rel.

    The role_one and role_two must be chosen from what is in acs_rel_roles, but the data is essentially meaningless, you can leave them blank if you want.

    Max_* you can leave blank as well.

    I'll post my code for the examples I have been using, maybe today or tomorrow.

    Collapse
    Posted by Tom Jackson on

    Hmm, well I went about translating my pl code to tcl code, but quickly discovered I couldn't.

    First, I started to create the new rel_types I need. The corresponding tcl proc is rel_types::new. Unfortunately, this procedure does much more than wrap acs_rel_type__create_type, and at the same time it does less. First off, you can't pass in any old extension table name or primary key name, it autogenerates these for you. Second, it autogenerates the table for you.

    Second, there doesn't seem to be anything corresponding to acs_object_type__create_type.

    Third, there doesn't seem to be any proc to achieve the following for allowing additional rel_types to be applied to a group:

    insert into group_type_rels 
     (group_rel_type_id, rel_type, group_type)
      select nextval('t_acs_object_id_seq'), r.rel_type, 'unicycle_club'
      from group_type_rels r
      where r.group_type = 'group';
    

    Although I'm not sure these are even used, they may be solely for the subside admin UI.

    rel_segments_new looks like it will work.

    Collapse
    Posted by Randy O'Meara on
    Thank you sooo much, Tom and Lars! I now know enough to be dangerous. I'll go study the examples and your guidance and see if I can make it work.

    Thanks again,

    Randy

    Collapse
    Posted by Randy O'Meara on
    It's looking pretty good so far...

    Lars, I pulled down the 4.6 cvs tip and I'm running it now. I did a db drop and completely reinstalled from scratch. When I looked at the rel_segments table after creating a new subsite and my new segments, I noticed that there is no "Main Site Administrators" segment. Newly created subsites do have this segment. I'm wondering if, when you added the segment code to acs-subsite, you might have missed the fact that the main subsite has to be retrofitted since it goes through a "promotion" to become a real subsite after initial installation. Just a thought...

    Randy

    Collapse
    Posted by Lars Pind on
    Randy,

    Can you file a bug in the bug-tracker, pls.

    Collapse
    Posted by Randy O'Meara on
    Lars, it's filed under 4.6.4 acs-subsite.

    This could be considered a kernel-related issue because, per Don's post above, the "Members" rel-seg is created in acs-kernel/sql/*/acs-install.sql. The "Administrators" rel-seg should probably be created there too.

    Randy

    Collapse
    Posted by Randy O'Meara on
    I have spent the last month digging into the use of rel_segs to expand the existing acs-subsite rel_segs in order to provide more granular control over users' actions. My excursion resulted in gaining the knowledge required to expand the default two role (Members and Administrators) acs-subsite organization into a four role (added Guests and Managers) organization. This included a trip through roles, rel types, rel segs, and the associated tcl and db apis.

    Finally, I wrote this up into a general Howto with example code and posted it as another thread: http://openacs.org/forums/message-view?message_id=116231

    Have a look and let me know if you think it's useful.

    Randy