Forum OpenACS Development: acs_rels: why and when to use

Posted by Tom Jackson on

I created a few new relationship types 'parent_rel' and 'coach_rel' for an application. I naively assumed that somehow I could ascribe some kind of permission to any relationship created, hey that happens with membership_rel, when you become an approved member of a group, then you get read permission on the group, due to the party_approved_member_map.

When I setup these new rels I had to specify a supertype, but it turns out this is the acs_object supertype. It still has the same meaning: i.e. my parent_rel is a subtype of membership_rel.

However, membership_rels and composition_rels are maintained in separate tables, they are not generic relationship types. This seems to imply that new relationship types are really discouraged. If I want to map one object type to another, why not just use a simple mapping table?

So what is the reason for acs_rels? It seems aD was unable to even use this for the most basic type of relationships.

There is also the acs_rel_rols. Does anyone know the purpose of the role in a relationship? It seems more like a tag than to serve any purpose.

Posted by Don Baccus on
I don't understand why you say you have to have a supertype of acs_object.  Check out the dotlrn sources.  The hierarchy there is membership_rel->user_profile_rel->dotlrn_user_rel or something along those lines.

You need to derive from membership_rel in order to get the semantics associated with that kind of relationship.  If you do so you can then do the permission stuff you apparently want to do.  The permission system answers the question "can party foo do action bar to object fubared" and knows about the special membership_rel and composition_rel relations, returning true if the party has the permission due to belonging to a group or relationship segment that has the permission.

If your parent_rel and coach_rel relations are drawn from the same group, use two relationship segments to gather the two sets into two parties "coaches" and "parents".  Relationship segments can be thought of as being subgroups ...

Why do membership_rels and composition_rels have separate tables even though there's nothing in them other than the id field?  Because the acs_objects system doesn't allow one to reuse type info tables in derived types, which is something I'd love to fix and probably will someday unless someone else beats me to it.  If I'm deriving from a type and am not adding new columns - a perfectly normal thing to do in object oriented programming - I shouldn't have to provide a dummy table.

Why would you use a separate mapping table since both tables have object ids as their primary keys and they're the same key value for the parent type table and the child type table?  Maybe I don't understand the question.

The roles are useless IMO.  I imagine they were there originally so you could answer the question "which members of Group A have role R?" but relationship segments allow one to answer that question in a slicker way.  But bear in mind that relsegs were added after the original ACS 4.0 was released (not until 4.2 I believe) ...

acs-rels are of heavier weight than necessary for group/subgroup management because there's no need for each row to be an object in order to implement the given semantics.  However there are cases where it makes sense so it would be wrong to toss out the heavyweight model.  On the other hand (as has been covered in other threads) it might be nice to have two variants of a relationship facility for objects, one lightweight to complement acs-rels.  The content repository has such a facility ... I'd say this is fairly far down any reasonable sense of priorities, though.

Posted by Tom Jackson on

Don, your new speedy permission_p check was about the only piece of code that allowed me to understand how permissions get set for group members. The groups data model and pl has a bunch of table that cause a lot of confusion.

I still haven't looked at .LRN for relational segments. I just looked at the table for it, and each segment consumes an object_id and stuffs data into parties as well. So I wasn't sure why this is ligher weight than using a group or

I wish there was some easy way to diagram what I am trying to do, but I'll try.

User types: unicyclist, parent, coach

Group types: unicycle_club, uni_group

When a new user who is a unicyclist is added to the system, a group of type uni_group is created. The unicycling user is then given a unicyclist_rel relation to this group. The user is also granted admin permission on this group, and the context_id of the user is set to the group_id. So even though the usual direct permissions granted to the user (read/write) are not added to acs_permissions, the user still has read/write and now admin on their user_id.

Parents, or coaches, may want to read/write/admin for the unicyclist, so I was thinking of using acs_rels (parent_rel) to identify which unicyclist groups the parent was related to, and assign permission on the group depending on what was desired, but after looking at the dm, this seems very heavyweight for just saying object_a has relationship of type b to object_c. Acs_rels still seems to serve the purpose of something similar to parties for the user/group split, maybe relational segments is a more targeted type of relationship, being derived from parties? Anyway, I can't seem to understand how a relational segment would help, but surely and acs_rel isn't needed. How can I group parents into a relational segment? Would that somehow allow me to assign the permissions I want in a easier way?

So there are two separate systems here: relationships and permissions. But when you create a new membership_rel, this triggers inserts into party_approved_member_map, which leads to permissions, so in this case, the two systems get mashed togeather.

Posted by Dave Bauer on

I struggled with this, and I still don't have a good explanation for how it all works.

What I do know, is that the system makes no assumptions on how you want to use it. If you create a new relationship type, there are no semantics attached to it by the system.

So what I think you want to do is this: create a new relationship type. Then you will create a tcl proc that creates an instance of this relationship. This is what will grant the permissions on the objects based on your application.

Well, writing that gave me a little bit more understanding into how it works. Mainly the key is that the system is very, very flexible, and with that you can use it in many different ways. We probably should generate some documentation that shows common uses of groups with examples.

Posted by Tom Jackson on

Here is how I can see an acs_rel working in my case.

A parent logs in to the system and is presented with a list of unicyclists that they can read/write/admin for. They choose one and kind of soft login as that unicyclist.

When they want to add some information, they do it in their name, for instance a blog entry:

skill_log -- simplified

Previously the system set up a parent_rel, an acs_object, with the context_id set to the uni_group group_id for the unicyclist.

When this entry is added, the user_id of the parent is used in the skill_log table, but the context_id is set to the parent_rel rel_id. This preserves the route that the information was added for the unicyclist, but I don't know how easy it will be to grab all the log entries for the user/party. Maybe the party_id in the skill_log table should refer to the uni_group group_id, and the context_id to the rel_id. Then who edited it should go in the creation_user field for the acs_object. By looking at the acs_object__name for the context_id of any log entry, you could determine who added the entry and in what role, for instance if you auto named the acs_rel 'Joe Blow, parent of Sam'.

This data model would then allow entries to go in for other groups: unicycle_club groups for instance, or if several unicyclists were working togeather on a set of routines in an expanded uni_group.

So the helpfulness of the acs_rel usage would be to simplify the query of what a person can do on the site, what role they want to play, and what group of objects they want to look at. By choosing a certain parent_rel or coach_rel, that would in one pick identify both.

Posted by Randy Ferrer on

Ok...I'm a bit confused. If I may, let me try another example:

Let's start with the current set-up where once a user registers, that user then has a "Membership Relation" to "Registered Users" relational segment as is standard now.

Now, for a specific package I would like to create various groups that would be permissioned to view or read only certain pages of the output presented by the package. In other words a user must first be a "Registered User" and then needs to be permissioned to see specific pages or sections of the output for the package. Of course, a user that is only a "Registered User" can't see any pages generated from this package." To do this, would I need to create an acs_rel relation "Registered User" -> "Group 1" and so on for all my other groups? Or am I spinning my wheels here. The idea is that an admin can then assign individual viewers to the section they are permissioned to see. Another use of this might be to authorize someone to view say a set or reports after purchasing them on line. Is this the way to go or is there a better approach?

Thanks for any insights into this.

Posted by Tom Jackson on

You might also check out the thread on Relational Segments: how do you use them.

Create your group for your application, the one that can see everything. Give a membership_rel to all users you want to be able to 'read', for instance. A membership_rel is a specific type of acs_rel. It puts a row in membership_rels, which triggers a bunch of pl code to at least create rows in party_approved_member_map. Any general permissions you want everyone to have could be assigned to the group, or you could create a relational segment. Lets say the group_id is 100. If you created a relational segment that said "all users with rel_type = 'membership_rel' to group 100 are part of this segment", then you would end up with two rows in party_approved_member_map, one for the group and one for the rel_segment. You can assign permissions to either of these. If you wanted a different grouping of users, say the 'advanced readers', you would first create a subtype of membership_rel say 'advanced_readers_rel'. Once you have this subtype, you can create a relational segment that says "users with rel_type = 'advanced_readers_rel' to group 100 are part of this segment, named 'Group 100 Advanced Readers'. Then you would use membership_rel__new, or whatever the tcl api call is to add the new rel. You use the same call, but for the rel_type, you just substitute 'advanced_readers_rel' instead of the default 'membership_rel'.

Shoot, now I'm confused.

Posted by Randy Ferrer on

jajajaja. Ok...scary thing is I think I follow you Tom. Ok - let me see if I can make this example even clearer. Here are the users on my system and what they will be able to see.

  • Public user - This user can see content for which no registration is necessary. Like forum, blog etc. has no write permissions anywhere.
  • Registered user - This user can comment on blogs, forums and can do the general stuff most registered users do like on say
  • Group A Viewer - This viewer is a registered user who is also permissioned by virtue of being a member of Group A, to view content on a specific package - let's call it "Special Reports". So this package generates specific reports for different viewers. Let's say this group is the Southeast Marketing group and they will see the Southeast Market Sales Reports. Registered users who are members of this group are the only one's who can see this content.
  • Group B Viewer - This viewer sees the Northeast Market Sales Reports.
  • And so on....

An admin can then make a user part of the group for the reports they are authorized to view. I'll need to read all this again - now I'm confused again - not that it was clear to begin with...jajajaja. Does you description help achieve this???

Posted by Don Baccus on
One minor thing: "the public" is just the group "registered users" plus user 0, which is what ad_conn user_id returns for users who aren't logged in.

In 4.6 it's really person 0 but we've discussed making it user 0 (and Lars may've done so for 4.6.4) which means one can give the public write permissions on a particular forum (for instance) and allow "anonymous user" posts (once it's user rather than person 0 since forums joins with the users table to get first and last name ...)