[Catalyst] Users hierarchy
Jonathan Hall
jonh at nmgi.com
Mon Jun 2 17:02:45 BST 2008
I make no claim at being an expert, but here's what we do... If there's =
a better way, I'm all ears!
We use two tables. A Users table, and a Parents table that acts as a =
map between users and all of their possible parents.
<PostgreSQL>
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
parent_id INT REFERENCES users(user_id)
...
);
CREATE TABLE parents (
user_id INT REFERENCES users(user_id) ON DELETE CASCADE,
parent_id INT REFERNECES users(user_id) ON DELETE CASCADE,
level INT,
PRIMARY KEY (user_id,parent_id)
);
</PostgreSQL>
Somewhere you need logic that populates the "parents" table any time a =
user is added/deleted, or re-parented (if supported by your =
application). The "parents" table should have one entry for every =
parent/child relationship in existence, including each users's =
relationship with themself. Yes, this means lots of duplicate data.
Suppose your users tree contains 5 users:
User1
+-- User2
+-- User3
+ User4
+ User5
Your "users" table will look like this:
user_id | parent_id
---------+-----------
1 |
2 | 1
3 | 1
4 | 3
5 | 3
And your "parents" table should look like this:
user_id | parent_id | level
---------+-----------+-------
1 | 1 | 0
2 | 1 | 1
2 | 2 | 0
3 | 1 | 1
3 | 3 | 0
4 | 1 | 2
4 | 3 | 1
4 | 4 | 0
5 | 1 | 2
5 | 3 | 1
5 | 5 | 0
The "level" column represents how far away you are from the "top" level =
user. If your needs are simple enough, this column may not be strictly =
necessary. If your needs are complex enough, you may also need an =
opposite level column--a "from_top" and "from_bottom" may be more =
appropriate.
We keep this table up-to-date with a plperl stored procedure:
<PostgreSQL>
CREATE FUNCTION update_parents_table() RETURNS TRIGGER AS $$
my @ids =3D ($_TD->{new}{user_id});
while ( @ids ) {
my $id =3D shift @ids;
my $sth =3D spi_query("SELECT user_id FROM users WHERE parent_id=3D$id");
while ( my $row =3D spi_fetchrow($sth) ) {
push @ids, $row->{user_id};
}
my $parent =3D $id;
my $level =3D 1;
spi_exec_query("DELETE FROM parents WHERE user_id=3D$id") if =
$_TD->{event} eq 'UPDATE';
spi_exec_query("INSERT INTO parents (user_id,parent_id,level) VALUES =
($id,$id,0)");
while ( $parent ) {
my $rv =3D spi_exec_query("SELECT parent_id FROM users WHERE =
user_id=3D$parent");
$parent =3D $rv->{rows}[0]->{parent};
spi_exec_query("INSERT INTO parents (user_id,parent_id,level) VALUES =
($id,$parent,$level)");
$level++;
}
}
return;
$$ LANGUAGE plperl;
CREATE TRIGGER update_parents_table
AFTER INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE update_uid_cache();
</PostgreSQL>
The same thing should be easily accomplished in any other stored =
procedure language, or even in DBIC or your application.
Then, to do the query (the part you're actually trying to do!) it's just =
a matter of a join. To select all of the users that are at user 3 or =
lower in the tree (this will include user 3, 4, and 5 from my example):
SELECT * FROM users JOIN parents ON (users.user_id =3D parents.user_id) =
WHERE parents.parent_id =3D 3;
user_id | parent_id | user_id | parent_id | level
---------+-----------+---------+-----------+-------
4 | 3 | 4 | 3 | 1
5 | 3 | 5 | 3 | 1
3 | 1 | 3 | 3 | 0
Or for your requirement... ( if ( user_id is =
child,grandchild,gradgrandchild.... of logged in $c->user ) );
SELECT 1 FROM users JOIN parents ON (users.user_id =3D parents.user_id) =
WHERE parents.parent_id =3D ? AND users.user_id =3D ?;
$c->user->user_id, $target->user_id.
In DBIC (Someone can probably think of better relationship names):
Users.pm:
__PACKAGE__->has_many("parents","Parents", { parent_id =3D> "user_id" });
Parents.pm:
__PACKAGE__->belongs_to("user_parent","Users", { user_id =3D> "parent_id" }=
);
Then in Catalyst:
if ( $c->model->search(
{ 'users.user_id' =3D> $target_user_id,
'parents.parent_id' =3D> $c->user->user_id },
{ join =3D> "parents" })->first ) {
# Has permission
} else {
# Does not have permission
}
That's it... in a nutshell. Perhaps a bit daunting if it's your first =
go at something like this, but once it makes sense in your head, it =
should be pretty straight-forward to implement and use.
--
Jonathan
Gabriel Vieira wrote:
> I usually use a subid to identify child's parent as you do. So I run
> bottom to top to get to the major user. With this I can see if the
> some ID have access to other.
>
> This may be slow. Let's see what gurus have to say. :)
>
> 2008/6/2 jakac <jakac at iprom.si>:
> =
>> Hello!
>>
>> I need a help on building a model for user that has a permission to edit
>> other users that were created by this user or any of his children,
>> grandchildren etc.
>> (difficult sentence, I know)
>>
>> To make this a little more understandable here's my database table:
>>
>> - user_id
>> - username
>> - password
>> - various other data such as fname, lname, address etc.
>> - parent_id
>>
>> Column "parent_id" has a value of "user_id" that created one user. There=
is
>> also
>> a "superadministrator" with parent_id '0' that can edit everybody.
>>
>> Now I would like to build a model that I can use in my controllers like:
>>
>> if ( user_id is child,grandchild,gradgrandchild.... of logged in $c->use=
r )
>> {
>> # has permisson to edit
>> } else {
>> # doesn't have a permission to edit
>> }
>>
>> Example:
>> - superadmin
>> |__- foo
>> |__- bar
>> |____- john
>> |______- doe
>>
>> In this example:
>> - superadmin can edit anybody,
>> - bar can edit john & doe
>> - john can edit only doe
>> - foo can't edit anybody since he has no children
>> And there can be unlimited levels of users...
>>
>> There is no problem with permission to edit first child since I can just
>> compare
>> logged in user's ID with edited user's parent_id but when edited user is
>> grandchild,
>> grandgrandchild, (grand * n) child of $c->user then I need some kind of
>> model
>> to return true/false value.
>>
>> I've never done that and I am also new to Catalyst so any help would be
>> appreciated.
>> Thank you!
>>
>>
>> JakaC.
>> _______________________________________________
>> List: Catalyst at lists.scsys.co.uk
>> Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
>> Searchable archive: http://www.mail-archive.com/catalyst@lists.scsys.co.=
uk/
>> Dev site: http://dev.catalyst.perl.org/
>>
>>
>> =
>
>
>
> --
> Gabriel Vieira
>
> _______________________________________________
> List: Catalyst at lists.scsys.co.uk
> Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
> Searchable archive: http://www.mail-archive.com/catalyst@lists.scsys.co.u=
k/
> Dev site: http://dev.catalyst.perl.org/
> =
-- =
Inbound and outbound email scanned for spam and viruses by the
DoubleCheck Email Manager: http://www.doublecheckemail.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.scsys.co.uk/pipermail/catalyst/attachments/20080602/2d421=
e90/attachment.htm
More information about the Catalyst
mailing list