Best method of many to many database? - ruby-on-rails

I am currently working on an app for sporting clubs, this allows club admins to manage the divisions, teams, and ultimately the players.
At present I have my database/relationships as follows
class Sport < ActiveRecord::Base
has_many :clubs
has_many :teams
attr_accessible :club_id
class Club < ActiveRecord::Base
belongs_to :sport
has_many :teams
has_many :users
has_many :divisions
attr_accessible :sport_id
class Division < ActiveRecord::Base
has_many :teams
belongs_to :club
attr_accessible :club_id
class Team < ActiveRecord::Base
has_many :users
has_many :schedules
belongs_to :division
belongs_to :club
attr_accessible :division_id, :club_id
class User < ActiveRecord::Base
belongs_to :club
belongs_to :team
attr_accessiable :club_id, :sport_id
essential what I would like is a more concise way of managing this. i.e.
User can only belong to 1 club, within 1 division, but can have multiple teams
Team can only belong to 1 division, within 1 club, within 1 sport, but have multiple users
Division can only belong to 1 club, within 1 sport, but have multiple teams
Club can only belong to 1 sport, but have multiple teams, and multiple divisions
At present the above is working, but i dont think the relationships/structure is at its best

You only have singular sport clubs in your country? Here in Germany different divisions of a club can have different sports (my own club e.g. is in Athletics, Cycling, Volleyball, Basketball, Tabletennis and Swimming). In most of my apps I just skip the sport and the divisions: The sport is given from the app's context, and I view club and division as identical. My Athletic-apps just deal with the club, not with the club's division. I view this sacrifice as necessary, as the complexity otherwise overwhelms me – your mileage may vary, of course :-)
Moreover: A user only temporarily belongs to a club, so I have an intermediate relationsship between users and the teams; "Startberechtigung" in german, something like "license" in english I 'ld say; it belongs_to team and belongs_to user and has "start_date" and "stop_date" as additional attributes. That way I even have histories for clubs and users. Important for record lists (again: Athletics)!
After that I would throw away the superfluous transitive relationsships: A user belongs to a team which in turn belongs to the club => use has_many :through's more; you dont need the user belongs_to club.
Now your models should be a bit cleaner.
Update:
Dont use classname "User" for the players, this will ultimately lead to a conflict with your user/admins-model. "Player" or "Athlete" is just fine for me.
Update 2:
You dont need the :through in each and every case, of course. A scope with includes or a class method might be preferable for you, esp. if you want to bridge over more than one intermediate class.
How about something like (please fill the holes from the recommended guide):
class Sport < ActiveRecord::Base
has_many :clubs
has_many :teams, :through => :clubs, :readonly => false
class Club < ActiveRecord::Base
belongs_to :sport
has_many :teams
class Team < ActiveRecord::Base
belongs_to :club
has_many :athlete_teams
has_many :athletes, :through => :athlete_teams, :readonly => false
class AthleteTeam < ActiveRecord::Base
belongs_to :teams
belongs_to :athletes
class Athlet < ActiveRecord::Base
has_many :athlete_teams
has_many :athletes, :through :athlete_teams, :readonly => false

Help about ActiveRecord Associations can be found on http://api.rubyonrails.org
hopefully, this will help you.

Related

Tournament -> Team -> Player Associations

I'm a relative beginner to Rails, but am learning as I go. I'm trying to create a Tournament Entry portal, where a team would enter players for a given tournament. I've done a bit of reading about associations, but am having some trouble wrapping my head around how to apply them in this instance.
As a basic overview:
One tournament, has many teams.
Each team has many players
Therefore one tournament also has many players (through the teams
entered)
Here's my code for this, but I'm not sure it's right because I'm unable to get any tournament_ids associated to players.
(tournament.rb)
class Tournament < ApplicationRecord
has_many :teams
has_many :players, :through => :teams
end
(team.rb)
class Team < ApplicationRecord
belongs_to :tournament
has_many :players
end
(player.rb)
class Player < ApplicationRecord
has_one :team
has_one :tournament, :through => :team
end
Within the Players table there is both team_id & tournament_id fields, however I'm only able to populate the team_id field through association when I try in console.
I'm wondering if there's something amiss with my associations.
The usage of 'belongs_to', 'has_many', 'has_one' depends on the data model in database of course.
If you have team_id foreign key in players table, then you need to define Player class as:
class Player < ApplicationRecord
belongs_to :team
has_one :tournament, :through => :team
end
In addition, I really believe that Tournament <-> Team should have many-to-many association (if team can participate in many tournaments of course). I would suggest adding model TeamTournament and define final model structure as:
class Tournament < ApplicationRecord
has_many :team_tournaments
has_many :teams, :through => :team_tournaments
has_many :players, :through => :teams
end
class TeamTournament < ApplicationRecord
belongs_to :team
belongs_to :tournament
end
class Team < ApplicationRecord
has_many :team_tournaments
has_many :tournaments, :through => :team_tournaments
has_many :players
end
the Player class should have belongs_to associations with Team and Tournament
class Player < ApplicationRecord
belongs_to :team
belongs_to :tournament
end
OK. I assume your question is about your models associations rather than how to set up association for getting tournament_id from player and so on. So I'll try to hand you some tips about your project and associations could be set up for it.
As I got your portal idea... You want the tournament to has many teams and the team to has many players. But then you want to get tournament_id from player. I believe you don't want to do that because in real life tournament indeed may "has" some players but every single player don't has to belong to some tournament. He can take part in many tournaments. So you don't need to set up association for that. Same thing with tournament and teams. But since team has the player he has to belong to that team. So you need association for that.
Wrapping up my setup for you will be like:
(tournament.rb)
class Tournament < ActiveRecord::Base
has_many :teams
end
(team.rb)
class Team < ActiveRecord::Base
has_many :players
end
(player.rb)
class Player < ActiveRecord::Base
belongs_to :team
end
And an example about how you can get the tournament where certain team take part in without the direct association:
team = Team.first # just take some team
Tournament.includes(:teams).where(teams: { id: team.id })
The same way you can achieve your other goals (get the tournament certain player belongs to and so on). But such cases don't need associations. Associations are needed when the object relates to another conceptually.

rails multi-levels of "through", database schema layering

my data is currently structured in the following way.
STATE (ie California) "has_many" SCHOOLS,
SCHOOL "has_many" GRADES (ie 8th grade or 9th grade),
GRADE "has_many" CLASSES (ie art, history, chemistry),
CLASSES "has_many" STUDENTS.
My goal is to get low-level detail from a high-level view, otherwise, I would like to get all students (lowest denominator within the data structure) within the STATE (highest level component within the structure). What's the easiest and most efficient way to structure this so I can have a command to get all students within a state, ie "State.find(name: "California").students".
I tried a two level "through" association but it seems to be limited.
A potential basic data design would be something like the following:
class State < ActiveRecord::Base
has_many :schools
has_many :students, through: :schools
end
# Links students to schools
class Enrollment < ActiveRecord::Base
belongs_to :school
belongs_to :student
belongs_to :grade
end
class School < ActiveRecord::Base
has_many :enrollments
has_many :students, through: :enrollments
has_many :grades
has_many :subjects, through: :grades
belongs_to :state
end
# Denotes a educational step as in 1st grade
class Grade
belongs_to :school
has_many :subjects
end
# We can't use Class since it clashes with the Class object.
class Subject
belongs_to :grade
end
class Student < ActiveRecord::Base
has_many :enrollments
has_many :schools, through: :enrollments
end
State.find(1).students would join through School and Enrollment.
Flattening the structure would require a states_students join table which is pretty unsexy due to data duplication. The duplication means more slow insert and update queries when creating / updating students. The same applies to subjects and grades.
This does not address differentiating between current and former students which could be solved by adding an end date or a enum denoting the status to Enrollment.
Enougher issue is dealing with data that might be shared by all Grades or Classes. Like for example goals that would be setup on a state level and not on a school level. Another case is a subject which might stretch though several grades.

Which Association Should I Use?

My app has 5 core models. I'm trying to figure out the best way to associate the models. How many tables should I build and which kind etc?
Here are the associations I would like to include and their respective models:
User
Has many boards
Has many lists
Has many cards
Has many comments
Board
Has many users
Has many lists
Has many cards
List
Belongs to board
Has many cards
Card
Belongs to board
Belongs to list
Has many comments
Comment
Belongs to card
Belongs to user
class User < ActiveRecord::Base
has_and_belongs_to_many :boards
has_many :lists, as: listable
has_many :cards, as: cardable
has_may :comments, as: commentable
end
class Board < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :lists, as: listable
has_many :cards, as: cardable
end
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class List < ActiveRecord::Base
belongs_to :listable, :polymorphic => true
has_many :cards, as: cardable
end
class Card < ActiveRecord::Base
belongs_to :cardable, :polymorphic => true
has_many :comments, as:commentable
end
To establish HABTM relation you have to create a table named 'users_boards'
As Board and User are having many to many relationship, there will be a new table for it, if you want HABTM you can use it.
User(id, name, other_attributes...)
Board(id, name,...)
List(id, name, user_id(fk),...)
Card(id, name, user_id(fk), list_id(fk), board_id(fk),...)
Comment(id, comment_msg, user_id(fk), card_id(fk),...)
Board_User(board_id(fk), user_if(fk)) --- M-M relation
Few attributes might change if there is a has_many through relation.
FK-- Foreign key, you can use has_many through depending on your requirements.
Using polymorphic associations has some limitations, so please do through it then decide to use a polymorphic association
http://codeblow.com/questions/pros-and-cons-for-ruby-on-rails-polymorphic-associations/

Two Linked Models + User Model?

I am trying to create the following basic structure with RoR. The key is that all Users also be linked to a School and a Major. Users will write articles based on their School and Major. The linking is not exclusive: many users can be in one of many schools, and one of many majors. However, each user cannot be in more than one school, and cannot be in more than one major. Eventually, I would like to be able to show posts/filter articles based on the following:
All Users in both Major X and at School Y
All Majors at School Y
All Schools with Major X
I've done a little research, not sure if any of this is correct... (still learning) should I be using has_and_belongs_to_many below as compared to has_many?
Table
major_schools #(linking the two models below)
Models
class School < ActiveRecord::Base
has_many :major_schools
has_many :majors, :through => :major_schools
end
class Major < ActiveRecord::Base
has_many :major_schools
has_many :schools, :through => major_schools
end
#school.majors #now gives a list of all the majors this school has
#major.schools #still gives a list of all schools that have this major
What I need to do is also incorporate a user model with the two above:
class User < ActiveRecord::Base
has_and_belongs_to_many :major_schools
end
And I am quite stuck... How can I pull in the User model data to the above models?
Your domain model is a tad tangled here, but it works.
Here is one way to load all Users in both Major with id X and at School with id Y:
class MajorSchool < ActiveRecord::Base
belongs_to :major
belongs_to :school
has_and_belongs_to_many :users
end
# Load all users from this school/major combination
MajorSchool.where(major_id: X, school_id: Y).users
Why not simply do:
class School < ActiveRecord::Base
has_many :major_schools
has_many :majors, :through => :major_schools
has_many :users
end
class Major < ActiveRecord::Base
has_many :major_schools
has_many :schools, :through => major_schools
has_many :users
end
class User < ActiveRecord::Base
belongs_to :school
belongs_to :major
end
Then you should be able to do:
# all students of the school
#school.users
# all students of the school and major (each line should return the same results)
#school.users.where(major_id: #major.id)
#major.users.where(school_id: #school.id)
User.where(school_id: #school.id, major_id: #major.id)

rails has_many through with independent through table

I have a User model, Person model and Company model.
a User has many companies through Person and vice versa.
But i would like to be able to populate People and Companies that are not tied to Users that can be tied later.
class User < ActiveRecord::Base
attr_accessible :name
has_many :people
has_many :companies, :through => :people
end
class Person < ActiveRecord::Base
attr_accessible :user_id, :company_id
belongs_to :users
belongs_to :companies
end
class Company < ActiveRecord::Base
attr_accessible :name
has_many :people
has_many :users, :through => :person
end
now in the console i want to be doing the following
User.find(1).companies
then it should find me the companies in which user(1) is a person of interest.
Have I got this wrong, is there a small change that I should be making.
Your Person model can't directly "belong_to" more than one, your belongs_to :users and belongs_to :companies associations won't work that way. Companies-to-people need to be connected through another join table that describes the relationship between them, for example Employment which points to one instance of each model:
class Person < ActiveRecord::Base
has_many :employments
has_many :companies, :through => :employments
end
class Employment < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :employments
has_many :people, :through => :employments
end
You can then use the :through option to associate the many companies/people on the other side of that employment relationship in the middle.
Similarly, if a Person can be owned by more than one User then you will need a join model between those two entities as well.
Just as a followup, in a has_many :through relationship, there is nothing that says you cannot use your join table (in your case, Person) independently. By nature of the relationship, you are joining through a completely separate ActiveRecord model, which is what most notably distinguishes it from the has_and_belongs_to_many relationship.
As Brad pointed out in his comment, you need to pluralize 'person' to 'people' in your relationship. Other than that, it looks like you set it up correctly. Exposing :user_id and :company_id with attr_accessible will enable you to mass-assign these values later from a postback, but often times you want to shy away from doing so with role-based associations, as you may not want to leave them exposed to potential HTTP Post attacks.
Remember, in your controller you can always do something like this with or without attr_accessible:
#person = Person.new
#person.user = User.find(...)
#person.company = Company.find(...)
#person.save
Hope that helps.

Resources