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