advanced (nexted) has_many :through queries in Ruby on Rails (double-JOIN) - ruby-on-rails

I am having a somewhat too nested database layout, however, I seem to need it. That is, Our website visitors may each have a single account with maintaining multiple users (think of identities) within.
Now they may create tickets, which are grouped by ticket sections, and we have ticket manager (operator) to process the incoming tickets.
Not every ticket manager may see every ticket but only those this manager is a member of the given ticket section for.
Now, I am totally fine in querying via raw SQL statements, but I failed to verbalizing those two special queries the Rails way.
Here is the (abstract) model:
# account system
class Account < ActiveRecord::Base
has_many :users
has_many :tickets, :through => :users
has_many :managing_ticket_sections, ... # TicketSection-collection this account (any of its users) is operate for
has_many :managing_tickets, ... # Ticket-collection this account (any of its users) may operate on
end
class User < ActiveRecord::Base
belongs_to :account
has_many :tickets
has_many :managing_ticket_sections, ... # TicketSection-collection this user is operate for
has_many :managing_tickets, ... # Ticket-collection this user may operate on
end
# ticket system
class Ticket < ActiveRecord::Base
belongs_to :author, :class_name => "User"
belongs_to :assignee, :class_name => "User"
belongs_to :section, :class_name => "TicketSection"
end
class TicketSection < ActiveRecord::Base
has_many :tickets
has_many :operators
end
class TicketSectionManager < ActiveRecord::Base
belongs_to :manager, :class_name => "User"
belongs_to :section
end
I am aware of basic has_many :through-constructs, however, here, I am accessing more than three tables to get the tickets.
Something that actually works for in the User's model is:
class User < ActiveRecord::Base
has_many :managing_relations, :class_name => "TicketSectionManager" # XXX some kind of helper, for the two below
has_many :managing_sections, :class_name => "TicketSection", :through => :managing_relations, :source => :section
has_many :managing_tickets, :class_name => "Ticket", :through => :managing_relations, :source => :section
end
Here I am using a helper relation (managing_relations), which is absolutely never used except by the two has_many relations below.
I were not able to describe a User.managing_sections nor User.managing_tickets relation without this helper, which is, where I need an advice for.
Secondly, the customer is to have a look at all of the tickets he can manage on any User (think of an identity) he has logged in, so what I need, is a way to collect all tickets (/sections) this Account is permitted to manage (identified by being a member of the corresponding TicketSection)
Here I even were not able to express this relation the ruby way, and I had to work around it by the following:
class Account
def managing_tickets
Ticket.find_by_sql("SELECT t.* FROM tickets AS t
INNER JOIN ticket_section_managers AS m ON m.section_id = t.section_id
INNER JOIN users AS u ON u.id = m.user_id
WHERE u.account_id = #{id}")
end
end
I'd appreciate any kind of advice, and
many thanks in advance,
Christian Parpart.

Related

has_many :through relationships explained

I'm new to Rails and have some doubts about the kind of relationship do I need to use. Here is the case.
I have two models Offer and User, a user could belong to to many offers and offers can have many user. Also the users create the offers.
I think I have to use a has_many :through ralationship. For example I've created another model "Applicant". Applicant belongs_to user and belongs_to offer. But how is the relationship from the user and offer model? For example:
User Model
has_many :offer, :through => :applicant
Offer Model
has_many :user, :through => :applicant
My doubt is because I already have this two relationship
User Model
has_many :offers, :dependent => :destroy
Offer Model
belongs_to :user
After solve this, I guest I have to save the record in the applicant model from the applicanst_controller, right?
Thanks in advance
What you have described is a many-to-many relationship using a join table. You're actually pretty close but you just need to remove the has_many :offers, :dependent => :destroy from your user model and the blongs_to :user in your offer model. It should look something like this:
class User < ActiveRecord::Base
has_many :offers, :through => :applicants
end
class Applicant < ActiveRecord::Base
belongs_to :users
belongs_to :offers
end
class Offer < ActiveRecord::Base
has_many :users, :through => :applicants
end
You don't have to worry about the dependent destroy part as associations are automatically removed as the corresponding objects are removed. With a many to many association it doesn't really matter how you go about building the relationship. Either of the following will work:
#user.offers << #offer
#offers.users << #user
If you don't need to store any information specific to your applicant join table (e.g., time stamps, descriptions) you might instead want to look at a has_and_belongs_to_many relationship. Check out choosing between has_many_through and has_and_belongs_to_many for reference.
Edit
Heres the code for a HABTM relationship:
class User < ActiveRecord::Base
has_and_belongs_to_many :offers
end
class Offer < ActiveRecord::Base
has_and_belongs_to_many :users
end

Modelling multiple many-many relationships in Rails

After reading RailsGuides, some answers here and other docs I am still confused about how to model this relationship.
Thinking of a model where there are objects User and Group, where users can be ordinary members of a group or have management privilege (create group, invite user, remove user, remove group) I want both objects to have two many-many relationships with each other.
user.memberOfGroups = a list of groups the user belongs to
group.userMembers = a list of all users who are members of the group
user.managedGroups = a list of groups the user has management
privileges for
group.managingUsers = a list of users with management privilege for
the group
Is this going to work?
class User < ActiveRecord::Base
has_many :managedGroups, :class_name => "Group", :foreign_key => "managingUsers"
has_many :memberOfGroups, :class_name => "Group", :foreign_key => "userMembers"
end
class Group < ActiveRecord::Base
has_many :managingUsers, :class_name => "User", :foreign_key => "managedGroups"
has_many :userMembers, :class_name => "User", :foreign_key => "memberOfGroups"
end
I have found in a guide how to use scaffold to generate the model objects which is fine with me, I just want to create a basic web service for POST and GET. If the above is correct it should be as simple as creating the model with scaffold and then editing to include the lines shown?
Some issues:
Membership and access control should be modeled by different tables.
Does management of a group imply membership?
The foreign key design as described would only allow for each user to manage one group/one group to have one manager. In all many:many relationships you'll need a third join model.
This would be a good starting point:
class User < ActiveRecord::Base
has_many :users_groups
has_many :groups, :through => :users_groups
end
class Group < ActiveRecord::Base
has_many :users_groups
has_many :users, :through => :users_groups
end
class UsersGroup < ActiveRecord::Base # will be the users_groups table
belongs_to :user
belongs_to :group
end
class Permission < ActiveRecord::Base
belongs_to :user
belongs_to :group
validate_presence_of :name # The name of the permission e.g. "manage"
end

Has Many Through Association Callbacks with multiple associations using the same join table

So this might be really bad form. I'm relatively new to rails. I'm not sure.
I have a project model and I want there to be many owners (who can read and write everything) and many collaborators (who can read and write some stuff).
In my project.rb file I have:
has_many :project_user_relationships, :dependent => :destroy
has_many :collaborators, :through => :project_user_relationships, :source => :user
has_many :project_owners_relationships, :class_name => "ProjectUserRelationship", :foreign_key => "project_id",
:before_add => Proc.new { |p,owner_r| owner_r.owner = true }, :conditions => "`project_user_relationships`.owner = true"
has_many :owners, :through => :project_owners_relationships, :source => :user
So this works reasonably well. If I add a new owner, that user is also a collaborator which is what I want. The issue I'm not sure how to solve is if I add a user that is already collaborator as an owner, I get two entries in the join table. I'd like for it to just amend the record that's already there. How do I do that?
Here's the data model I would suggest for this:
class Project < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
...
end
class Membership < ActiveRecord::Base
belongs_to :project
belongs_to :user
...
end
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :projects, :through => :memberships
...
end
And then the membership table will have the following attributes:
:id
:user_id
:project_id
:is_owner (boolean)
A scope defined on the membership class:
scope :owner, where("is_owner")
And a special method for User instances:
def owned_projects
memberships.owner.includes(:projects).inject([]) {|array, m| array << m.project; array}
end
will allow you to retrieve a user's owned projects with the user.owned_projects call.
And just a call to user.projects to see a user's projects that they either collaborate on or own.
You have better data normalization with this data model, and a simple boolean attribute to define whether or not a user is a project owner.
This data model is used in this project, with the exception that s/Project/Group/, and there's some additional functionality to handle inviting users to the Project.
This doesn't answer your "real question", but I think part of the issue is that a data model where collaborators are owners are stored in the same table is needed to minimize redundancies and the need to manage two separate tables.

Rails model advice

I need some suggests for the model relationship that I'm going to develop:
I want to create a research table that keep track of all the reasearches performed inside the platform.
In the platform I'm developing, an user can search for other users.
In order to do it, I thought to create 3 fields in the research table: Performer (user_id that submit the research) Research_string (The string that the performer submit) and Results (that is one or more user).
Of course the relationship among user and research tables will be many to many, but note that the research table has 2 different column that involves the user_id (performer and results) so how can I specify to rails ? I thought something like that :
class User < ActiveRecord::Base
has_many :performed_research, :through => :research_table
class Research < ActiveRecord::Base
has_many :users
But how can I specify that the has_many users in the Research tables implies two different relations depending by the column ?
Tnx
EDITED: my solution
Your solution is not correct, because the user has only the research performed and not the research where he is resulted. I made another solution, that it's not the best of clear but it works, I would like to have your judge:
class Research < ActiveRecord::Base
belongs_to :searcher, :class_name => 'User', :foreign_key=> 'submitter_id'
has_many :found_users, :through=>:user_researches,:source=>:user
#It is necessary to let the association work without class_name it would look for
userResearch class
has_many :user_researches, :class_name =>'User_research'
end
class User_research < ActiveRecord::Base
belongs_to :user
belongs_to :research
end
class User < ActiveRecord::Base
# It returns a list of research performed by the user
has_many :researches, :foreign_key => 'submitter_id'
# It is necessary to let the searcher relationship works
has_many :user_researches, :class_name =>'User_research'
#==> Searcher will return an Array of research where the user's skill has been researched
has_many :follower_researches, :through => :user_researches, :source=>:research
end
I say that it;s not the best because the follower_research of the user model, show an array of research when he has been results ... and not an Array of submitter that searching him, so to obtain them, I have to scroll the array of research and then take the searcher field ... Are you able to perform an improvement (hopefully less complex than this)
Do you mean something like this?
class User < ActiveRecord::Base
has_many :research_entries
class ResearchEntry < ActiveRecord::Base
has_one :performer, :class_name => 'User'
has_and_belongs_to_many :resulting_users, :class_name => 'User'
Update: I changed this to use the has_and_belongs_to_many relationship which will allow you to use a join table to connect many users into "resulting_users" field of the ResearchEntry
Something like this:
class User < ActiveRecord::Base
has_many :submitted_researches, :class_name => 'Research', :foreign_key => 'submitter_id'
has_manu :researches, :through => :performed_researches
class Research < ActiveRecord::Base
belongs_to :submitter, :class_name => 'User'
has_many :researchers, :through => :performed_researches, :class_name => 'User'
class PerformedResearch < ActiveRecord::Base
belongs_to :user
belongs_to :research
See: http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
Update: I think I misread something. What is results? and how is it related to users?
Update 2: Still not sure I understand but here's another stab
I originally thought you were doing something related to academic research and the Research model was for research papers with the performer being scientists that did the research.
From re-reading your question I'm now thinking you have a rails application with some users and a search feature that lets a user search for other users and for each search you are trying to keep track of which user did the search and which users they found.
Let me know if any of these assumptions are wrong.
Based on that:
class User < ActiveRecord::Base
has_many :searches #all the searches this user performed
class Search < ActiveRecord::Base
belongs_to :searcher, :class_name => 'User' # the user who performed the search
has_many :found_users, :though => :user_search_results, :foreign_key => 'user_id' # using foreign_key because I want to change the name to 'found_users'
#alternatively if you don't want to use foreign_key use the line bellow instead
#has_many :users, :thorough => :user_search_results
class UserSearchResult < ActiveRecord::Base
belongs_to :user
belongs_to :search

Rails join tables or HABTM associations

In the application I'm building, I want to have Events. People can then register for the event by creating a team, by directly registering for an event (no team), or by joining an existing team. I originally set it up as Event has_many teams, Team has_many users, user belongs_to Team and Event. However, I foresee problems if a registered user wants to join another event (or team for another event), and also when trying to list things like #user.events or #user.teams in the control panel. Someone suggested using a join table and creating a Registration model/migration to join these. Can I have a join model that belongs to Event, Team, and User? Am I approaching this all wrong?
You can create a the join model Registration, and make the belongs_to polymorphic so that it can refer to any type of object. Here's an example:
models/team.rb:
class Team < ActiveRecord::Base
has_many :registrations, :as => :owner
end
models/user.rb
class User < ActiveRecord::Base
has_many :registrations, :as => :owner
end
models/registration.rb
class Registration < ActiveRecord::Base
belongs_to :owner, :polymorphic => true
end
models/event.rb
class Event < ActiveRecord::Base
has_many :registrations
end
db/migrations/1234_add_registrations.rb
...
t.belongs_to :owner, :polymorphic => true
t.belongs_to :event
...
Now you can look at look into registrations to see who has signed up for your event. You can use has_many :events, :through => :registrations, :source => :owner to get back to users, but note that there are some limitations, like has_many :owners, :through => :registrations, that are addressed in plugins like has_many_polymorphs.

Resources