Advice concerning model hierarchy and associations

Hello,

I'm relatively new to Rails.. first post here :slight_smile:

Hope I'm not repeating an already asked question though I assume I'm
not the first one looking for how to best implement such an
association.. Anyway, haven't found the answers I was looking for when
searching for it, thus posting here a question. Apologise in advance
if it's a repeated one.. but would still appreciate any helpful
suggestion / direction / reference.. a lot.. :slight_smile:

I started working on a project where there are hierarchical groups of
users.
If I were to call the groups GroupAUsers, GroupBUsers, GroupCUsers,
GroupDUsers (may be more Groups, behaving the same way), then the
connection between the users in each group can be illustrated as
follows:

    * GroupAUsers have many GroupBUsers, plus have their own
groupAUser attributes. They also have full access to all other User
groups hierarchically ‚Äúbelow‚ÄĚ GroupBUsers.

    * GroupBUsers belong to GroupAUsers and have many GroupCUsers,
plus have their own specific GroupBUsers' attributes. For each User of
GroupCUsers they have full access to his/her GroupDUsers.

    * GroupCUsers belong to GroupBUsers and have many (each such user)
GroupDUsers, plus have their own specific attributes

    * GroupDUsers belong to GroupCUsers and have their own specific
attributes.

    * And so on.. (in case of additional such groups)

A better description of this would probably be (writing code only to
explain, this is not a copy paste of my code, though it's what I have
in mind right now as for associating the models)

class GroupAUser < ActiveRecord::Base
     has_many :group_b_users
     has_many :group_c_users, :through => :group_b_users

    # Not sure how should the
    # has_many :group_d_users
    # association look like …
    # it's a 'nested through'..
    # which means I need some help here as well in case that's what I
should be doing..
    # plus it becomes worse in case I have additional groups
end

class GroupBUser < ActiveRecord::Base
    belongs_to :group_a_user
    has_many :group_c_users
    has_many :group_d_users, :through => :group_c_users
end

class GroupCUser < ActiveRecord::Base
    belongs_to :group_b_user
    has_many :group_d_users
end

class GroupDUser < ActiveRecord::Base
    belongs_to :group_c_user
end

If this is the way I illustrate the associations, I guess my User
model would look something like this:

class User < ActiveRecord::Base
    has_many :group_a_users
    has_many :group_b_users
    has_many :group_c_users
    has_many :group_d_users
end

so I have access from each User's information to their suitable
information as being a user inside a group.
This is rather ugly, plus writing it down now, it seems wrong..
Reason I'm not using inheritance (say, from my User model) is I do not
wish to end up with one huge table, rails being STI.. mainly that..

Another important point is a user can belong to several groups and
should be able to see all relevant information according to his/her
role in each group.
Not sure yet how to best implement this ‚Äúfeature‚ÄĚ as well.. if anyone
has any suggestions he/she cares to share, I'd love to learn

I'm currently using Rails 2.3.8 & Ruby 1.8.7.

Also, for authentication and authorization ‚Äď planning on using
Authlogic and Declarative_Authorization.

If anyone can direct me to a ‚Äúbest practice‚ÄĚ of modelling hierarchical
associations or suggest me anything else (including upgrading
versions, though I did not get the impression there are significant
changes from reading the associations Rails documentation), or even a
‚Äúyou got it completely wrong‚ÄĚ (with some explanation if you can please
be kind enough) ‚Äď I'd *really* and truly appreciate it.

Hope I managed to be clear enough..

Many thanks,

Allison.

Hello guys,

Trying again..

I truly appreciate the RoR community and how helpful it is, I find it
beautiful.
I'm also very aware of the fact there are usually > 100 new posts and
questions every day as I'm following these, to learn (till I get
better and can contribute my share). So completely understand when a
question asked does not get answered and after a few minutes it's
already below a "pile" of new questions. I do.

I'm basically re-posting my question here, and hopefully somebody can
give me some input, please, even if it turns out my question was
pretty stupid.. please :slight_smile:
(I was actually trying to KISS :slight_smile: but not sure I didn't do just the
last 'S' part so far..)
I'm learning by myself and have no other programmers around to
consult.
Right now for what I'm trying to achieve I cannot tell for sure
whether it's ok or perhaps I have a mistake in understanding, which I
do not wish to go on with and really - you guys are the only ones who
can correct me and show me "the right way".. of course I'm not relying
on other people to make my work, I study all the time and do not seek
for the easy copy paste solutions.. I want to learn & understand.
And sorry for describing this all in details.

Also trying to figure out whether polymorphism could be of any help
here but it doesn't seem to be a "classic" polymorph case so it
doesn't seem to be keeping it simple, only trying to use great tools
for a wrong mission, but I'm still checking this option as well..

Really, any input..

Many thanks (even if don't get answered, which I still hope to
change..)

I can't say I fully understand your requirement (I have not much time
at the moment), but I am sure that to have different models for
GroupAUsers, GroupBUsers and so on cannot be the right way to go. I
would suggest starting with Users and Groups. Then you have a number
of groups (A, B etc). Then User belongs_to group and Group has_many
Users. (Is that right, a user can only be in one group).

Try starting with that approach and see how far you can get and come
back if you can't work it out.

Colin

Thank you Colin! :slight_smile:

I can't say I fully understand your requirement (I have not much time
at the moment), but I am sure that to have different models for
GroupAUsers, GroupBUsers and so on cannot be the right way to go. I
would suggest starting with Users and Groups. Then you have a number
of groups (A, B etc). Then User belongs_to group and Group has_many
Users. (Is that right, a user can only be in one group).

Actually - a User can belong to several groups.

Try starting with that approach and see how far you can get and come
back if you can't work it out.

The reason I was thinking of making each group a separate model is
they are all indeed users,
but a user should see different tabs (and of course has different
permissions, specifically on other users from other groups
hierarchically "below" this current user's group) relevant to his/her
group..
I think it can be thought of as a hierarchy of managers in a company,
all users but having different additional attributes and associated
models..

I do need to think more about the direction you offered. Logically,
even while apparently I wasn't clear enough, your way of putting this
together sounds (much) better..

I'm just not sure if I have a Group model and then I guess - several
groups inheriting from it.. oh.. think I making the same mistake over
again.. but I still should somehow be able to differentiate between a
user of a specific type (or that belongs to a specific group) to a
user who belongs to a different group (or is a different type of user,
doesn't seem that much of a difference at first glance..), plus
ideally allow a user be of several types, or somehow find a way around
this (was initially thinking of adding another column to the User
model with, say, a sum based on the 'types' this current user is, but
that adds some serious holes security-wise, unless I find some ways to
protect this column (less experienced with securing the DB, which I
need look into as well, but probably there's a better solution to this
as well, and hopefully much simpler).

Again, thank you.

You might want to look at one of the authorization systems out there, like CanCan, and see how they do it and if their model might work for you. There might be a way to avoid having so many different models.

Walter

Thanks :slight_smile:

You might want to look at one of the authorization systems out there,
like CanCan, and see how they do it and if their model might work for
you. There might be a way to avoid having so many different models.

Was actually thinking (as briefly mentioned in my first post) of using
Declarative_Authorization (and Authlogic for Authentication).
Will look into CanCan as well as also some other authentication
systems out there, maybe as you offered - will find some ideas
there..
the decl_auth_demo_app though, doesn't seem to be presenting the
simplest model association, that is:

User
    has_many :talk_attendees
    has_many :conference_attendees

TalkAttendee
     belongs_to :user
     belongs_to :talk

ConferenceAttendee
    belongs_to :user
    belongs_to :conference

Talk
    belongs_to :conference
    belongs_to :user
    has_many :talk_attendees
    has_many :attendees, :through => :talk_attendees, :source => :user

Conference
    has_many :talk_objs, :class_name => "Talk"
    has_many :conference_attendees
    has_many :attendees, :through => :conference_attendees, :source
=> :user

But - it's only a demo app, targeted to help developers understand its
authorization mechanism/use rather than trying to teach how to best
associate models (and define them).

And I may be wrong here again..

Will have a look at CanCan and see what I can learn from it :slight_smile:

see if this helps it kind of looks like what you are doing.

http://railscasts.com/episodes/163-self-referential-association

Thank you Colin! :slight_smile:

I can't say I fully understand your requirement (I have not much time
at the moment), but I am sure that to have different models for
GroupAUsers, GroupBUsers and so on cannot be the right way to go. I
would suggest starting with Users and Groups. Then you have a number
of groups (A, B etc). Then User belongs_to group and Group has_many
Users. (Is that right, a user can only be in one group).

Actually - a User can belong to several groups.

In that case I don't see how your original scheme would work, as a
user would have to be a GroupAUser and a GroupBUser.

Try starting with that approach and see how far you can get and come
back if you can't work it out.

The reason I was thinking of making each group a separate model is
they are all indeed users,
but a user should see different tabs (and of course has different
permissions, specifically on other users from other groups
hierarchically "below" this current user's group) relevant to his/her
group..

Don't worry about what you want on screen at this poiint. Think about
the objects and relationships in the underlying problem in the real
world first. If you get that modeling right then you will be on the
right track. Talk about the real world problem and identify the nouns
that use, these are then the candidates for model types.

I think it can be thought of as a hierarchy of managers in a company,
all users but having different additional attributes and associated
models..

In that case I think roles might be a better way to think about it
than groups. A user then has_many roles, but a role has_many users
also so you will probably need a join table (users_roles) to link
them. Then when working out the hierarchy for display you can use the
roles that a user has to determine what he can see and do.
Additionally you talk about a hierarchy of groups, is this really a
hierarchy of just a variable set of permissions that are implied by a
particular role?

Colin

> Thank you Colin! :slight_smile:

>> I can't say I fully understand your requirement (I have not much time
>> at the moment), but I am sure that to have different models for
>> GroupAUsers, GroupBUsers and so on cannot be the right way to go. I
>> would suggest starting with Users and Groups. Then you have a number
>> of groups (A, B etc). Then User belongs_to group and Group has_many
>> Users. (Is that right, a user can only be in one group).

> Actually - a User can belong to several groups.

In that case I don't see how your original scheme would work, as a
user would have to be a GroupAUser and a GroupBUser.

I was trying to "solve" this with the very ugly lines of

User
    has_many :group_a_users
    has_many :group_b_users
    has_many :group_c_users
    has_many :group_d_users

As you suggested - thinking of them as Roles rather than Groups of
Users, it seems a bit better.
Yet if changing the terminology - there are probably better solutions
as you already pointed out :slight_smile:

>> Try starting with that approach and see how far you can get and come
>> back if you can't work it out.

> The reason I was thinking of making each group a separate model is
> they are all indeed users,
> but a user should see different tabs (and of course has different
> permissions, specifically on other users from other groups
> hierarchically "below" this current user's group) relevant to his/her
> group..

Don't worry about what you want on screen at this poiint. Think about
the objects and relationships in the underlying problem in the real
world first. If you get that modeling right then you will be on the
right track. Talk about the real world problem and identify the nouns
that use, these are then the candidates for model types.

> I think it can be thought of as a hierarchy of managers in a company,
> all users but having different additional attributes and associated
> models..

In that case I think roles might be a better way to think about it
than groups. A user then has_many roles, but a role has_many users
also so you will probably need a join table (users_roles) to link
them. Then when working out the hierarchy for display you can use the
roles that a user has to determine what he can see and do.
Additionally you talk about a hierarchy of groups, is this really a
hierarchy of just a variable set of permissions that are implied by a
particular role?

Roles indeed :slight_smile:

After your previous response I started rethinking this problem and
actually you helped me a lot (I should mention I thank radhames as
well for bringing up the self referential associations) "letting go"
of what my mind seemed to be a bit fixed on..
Anyway - I have mainly Users. These Users have many Roles (or - there
are several "types" of Users).

Will try to illustrate this graphically for a moment, seems it might
help:

===== =====
RoleAUsers |
RoleA> >RoleA>

===== =====
                                                                        /

\ \ / | \

                                                                      /

Oh no - as should have expected - the "graphical" part got completely
messed up.. lol..
sorry about that..
ammm.. will try to think of a better way to put this thing..

Oh no - as should have expected - the "graphical" part got completely
messed up.. lol..
sorry about that..
ammm.. will try to think of a better way to put this thing..

--
Allison

> > Thank you Colin! :slight_smile:

> >> I can't say I fully understand your requirement (I have not much
> >> time
> >> at the moment), but I am sure that to have different models for
> >> GroupAUsers, GroupBUsers and so on cannot be the right way to go. I
> >> would suggest starting with Users and Groups. Then you have a
> >> number
> >> of groups (A, B etc). Then User belongs_to group and Group has_many
> >> Users. (Is that right, a user can only be in one group).

> > Actually - a User can belong to several groups.

> In that case I don't see how your original scheme would work, as a
> user would have to be a GroupAUser and a GroupBUser.

I was trying to "solve" this with the very ugly lines of

User
has_many :group_a_users
has_many :group_b_users
has_many :group_c_users
has_many :group_d_users

As you suggested - thinking of them as Roles rather than Groups of
Users, it seems a bit better.
Yet if changing the terminology - there are probably better solutions
as you already pointed out :slight_smile:

> >> Try starting with that approach and see how far you can get and come
> >> back if you can't work it out.

> > The reason I was thinking of making each group a separate model is
> > they are all indeed users,
> > but a user should see different tabs (and of course has different
> > permissions, specifically on other users from other groups
> > hierarchically "below" this current user's group) relevant to his/her
> > group..

> Don't worry about what you want on screen at this poiint. Think about
> the objects and relationships in the underlying problem in the real
> world first. If you get that modeling right then you will be on the
> right track. Talk about the real world problem and identify the nouns
> that use, these are then the candidates for model types.

> > I think it can be thought of as a hierarchy of managers in a company,
> > all users but having different additional attributes and associated
> > models..

> In that case I think roles might be a better way to think about it
> than groups. A user then has_many roles, but a role has_many users
> also so you will probably need a join table (users_roles) to link
> them. Then when working out the hierarchy for display you can use the
> roles that a user has to determine what he can see and do.
> Additionally you talk about a hierarchy of groups, is this really a
> hierarchy of just a variable set of permissions that are implied by a
> particular role?

Roles indeed :slight_smile:

After your previous response I started rethinking this problem and
actually you helped me a lot (I should mention I thank radhames as
well for bringing up the self referential associations) "letting go"
of what my mind seemed to be a bit fixed on..
Anyway - I have mainly Users. These Users have many Roles (or - there
are several "types" of Users).

Will try to illustrate this graphically for a moment, seems it might
help:

===== =====
RoleAUsers |
RoleA> >RoleA>

===== =====
/
> \ \ / | \
/
> \ \ / | \
=====
===== ===== ===== ===== =====
RoleBUsers |RoleB| |RoleB| |
RoleB> >RoleB> > > > >
=====
===== ===== ===== ===== =====
/ | \
\ | | \ / | \ / | \ / | \ /
> \
/ | \
\ | | \ / | \ / | \ / | \ /
> \
/ | \
\ | | \ / \
===== =====
===== ===== ===== =====
RoleCUsers |RoleC| |RoleC| |RoleC| |
RoleC> >RoleC> >RoleC>
====== ===== ======
====== ===== =====
/ | /
> \ | \ | \ / | \
/ | /
> \ | \ | \ / | \
/ | /
> \ | \ | \ /----/ | \
/ | /
> \| \ / \ / | \
===== ===== ===== =====
===== ===== ===== ===== =====
RoleDUsers |RoleD| | | | |
> > > > >RoleD> > > > >
> >
===== ===== ===== =====
===== ===== ===== ===== =====

And so on... :slight_smile:

(Of course - there are more Resources like "Location", "Attachments"
and others, but I illustrated the core of this hierarchical system,
which also presents most of the difficulty in my case)

This more "graphical" illustration was not such a great idea to put
this way, hope most of the people viewing it will be able to
understand what I was trying to demonstrate here..

I should also mention - and currently it seems more relevant to Users
of RoleD - they can belong to several User with RoleC assigned (and
might have a bit different information assigned to them by the
inserting RoleC User).. though a guess different DB entries for each
such association should be able to deal with such "doubling"/
overlapping or lack of it..

===============

So What I was illustrating here - again:

each User has many Roles (or - belongs to several 'types' of users),
which directly affects his/her place in this hierarchy. Of course, If
a user plays both "RoleA" and "RoleD" (have no better helpful names
for these, otherwise I would have used them to make this discussion
nicer) - I want him/her to be able to view the information in two
different ways, but as you suggested - this is not relevant right now
for mapping the basics (though feel I should keep that somewhere in my
mind..)

With what you suggested - a User has many Roles
and a Role has many Users (plus the join table)

so - each User is connected to other Users.
I want each User to be able to not only see which Roles they have but
also - be able to see and access (nature of accessing - whether read-
only or 'manage' will be handled using an Authorization system) all
Users "below" (probably manage) and "above" (read-only), where "below"
and "above" refer to the "user roles" as described in my "graphical"
explanation.

In case I do - a User also has many Users (and belongs to many Users),
I still need a way to be able to do something like the following:

@current_user.RoleBUsers.create(...)

[if current_user has permission to do it, i.e, either an Admin [OH.. I
probably need to put the Admin somewhere here as well..] or has RoleA
assigned to him/her]
I guess this is why I began to think of "groups" - because wanted to
gather all Users in a particular group as well and access their
hierarchical "parents" and "children"..

Now I'm thinking - maybe I should use a User self-referential
association for each Role-to-Role association (A-B, B-C, C-D), though
right now somehow it seems more of a mess but maybe I should just get
to the idea and play with it some more.. I also want a user to be able
to see more than 1 level above or below...

OK.

Sorry for lengthy email.. hopefully it's viewable..

Appreciate the answers very much, as mentioned before - I find it
irreplaceable talking to others while learning and trying to figure
out things and you've been very helpful. Thank you.

--
Allison

> Colin

> > I do need to think more about the direction you offered. Logically,
> > even while apparently I wasn't clear enough, your way of putting this
> > together sounds (much) better..

> > I'm just not sure if I have a Group model and then I guess - several
> > groups inheriting from it.. oh.. think I making the same mistake over
> > again.. but I still should somehow be able to differentiate between a
> > user of a specific type (or that belongs to a specific group) to a
> > user who belongs to a different group (or is a different type of
> > user,
> > doesn't seem that much of a difference at first glance..), plus
> > ideally allow a user be of several types, or somehow find a way
> > around
> > this (was initially thinking of adding another column to the User
> > model with, say, a sum based on the 'types' this current user is,
> > but
> > that adds some serious holes security-wise, unless I find some ways
> > to
> > protect this column (less experienced with securing the DB, which I
> > need look into as well, but probably there's a better solution to
> > this
> > as well, and hopefully much simpler).

> > Again, thank you.

> > --
> > Allison

> > --
> > You received this message because you are subscribed to the Google
> > Groups "Ruby on Rails:

...

read more ¬Ľ

--
You received this message because you are subscribed to the Google Groups
"Ruby on Rails: Talk" group.
To post to this group, send email to rubyonrails-talk@googlegroups.com.
To unsubscribe from this group, send email to
rubyonrails-talk+unsubscribe@googlegroups.com.
For more options, visit this group at
http://groups.google.com/group/rubyonrails-talk?hl=en.

this is similar to what I was trying to illustrate before,
a bit better:

http://gist.github.com/598105

it's only a general scheme (and still not a visually appealing one..)
to explain the Users' hierarchy

Thanks again to you all :slight_smile:

Sorry, I didn't look at that first post, and was responding to one of the follow-ups. I missed this. But do be clear about the difference between authentication and authorization. Devise, authlogic, etc. are all about authentication -- does this combination of username and password exist? -- while CanCan and others like it are centered on authorization -- what is the current user allowed to do in the current context?

Walter

Hi Walter,

Sorry, I didn't look at that first post, and was responding to one of
the follow-ups. I missed this. But do be clear about the difference
between authentication and authorization. Devise, authlogic, etc. are
all about authentication -- does this combination of username and
password exist? -- while CanCan and others like it are centered on
authorization -- what is the current user allowed to do in the
current context?

Thanks :slight_smile:
I am well aware of the difference between "authentication" and
"authorization" (plus which gems/plugins belong to each of the these
two "categories", just haven't looked into them all..)
In case I made a mistake in previous email with this terminology I
apologise and at least in this case - it was just a mistake and not a
lack of understanding.
Still - thanks for making sure and trying to help :slight_smile:

Ok, so I'm back with this "issue" of mine.. had to mainly deal with
other stuff the last couple of days :slight_smile:
[I know I'm top-posting but didn't feel adding the full quotes here
are that necessary, but will try not to repeat this often again]

First of all:

@Walter re-read what I previously wrote.. as you politely pointed out,
I did write authentication instead of authorization in one place.. how
embarrassing.. I apologise for that.. guess that's what happens when
you (I) edit a sentence you just wrote without carefully going through
it before hitting "send". Many thanks for being kind and making sure I
understood the difference, though in this case I was gladly already
aware of the difference. Just felt I didn't thank you in an
appropriate way, especially for your patience and caring enough to
explain.

Now - @Colin you were obviously right (Users, Roles, join table/
model)..
Tried to play with Users and Roles and got the conclusion I need some
more models there.. but then - maybe I'm just making things more
complicated than I should (again..)
Will put here what I have so far, though I must admit I don't feel
that happy with it.. have this feeling it's actually going to make
things more complicated along the way, especially when wishing to
access Users below a certain user more than one level down.

Note: I was a bit "loose" with naming my models (dictionary-wise),
looked for terms to express the hierarchy though I'm not that fond
with the terminology I came up with (and the spell-checker tends to
agree..)

Anyway, models for now are as follows:

User
    has_many :nominations
    has_many :roles, :through => :nominations

    has_many :subordinations
    has_many :subordinates, :through => :subordinations
    has_many :superordinations, :class_name =>
"Subordination", :foreign_key => "subordinate_id"
    has_many :superordinates, :through => :superordinations, :source
=> :user

Subordination
    belongs_to :role
    belongs_to :user
    belongs_to :subordinate, :class_name => "User"

Nomination # chose to use a join model (Users <--> Roles) and not
just a join table since I expect
                   # the need for additional columns here (e.g.,
"timestamp" columns, but think I'd
                   # want some more data to be stored here)
    belongs_to :user
    belongs_to :role

Role
    has_many :nominations
    has_many :users, :through => :nominations
    has_many :subordinations