2

I have this User and team model which has the following association:

user.rb

class User < ActiveRecord::Base
    belongs_to :team

team.rb

class Team < ActiveRecord::Base
    has_many :users
    has_one :leader, class_name: "User", foreign_key: "leader_id"
    belongs_to :manager, class_name: "User", foreign_key: "manager_id"

but it seems that I can't imagine representing it properly into a migration. At first, this is what I did:

class AddTeamIdToUsers < ActiveRecord::Migration
    def change
        add_column :users, :team_id, :integer
        add_index :users, :team_id
    end
end

class AddUsersToTeams < ActiveRecord::Migration
    def change
        add_reference :teams, :leader, index: true
        add_reference :teams, :manager, index: true     
    end
end

for sure, what I did on AddTeamToIdUsers was a many-to-one association since a Team can have many Users, but the leader position should only be exclusive for a specific team only (same goes to members as well, they should not belong to other teams). Managers, however, can have many teams to manage. Going back to my concern, how can I represent my scenario into a migration? Or are there any adjustments I should make in my associations? After the necessary adjustments and solutions considered, will the application automatically follow the association rules upon adding/updating teams?

oLraX
  • 217
  • 4
  • 14
  • If a user manages a team, are they said to be "part of" that team, ie would they be included in `team.users`? What about leaders? – Max Williams Jun 18 '14 at 15:49
  • yes for both of your answers. both leader and manager are values for the attribute _type_ for users (default is worker). – oLraX Jun 18 '14 at 15:52
  • Could a leader or worker also be a manager of a team, ie can a user have multiple types? – Joe Kennedy Jun 18 '14 at 16:10

2 Answers2

3

Your migrations look correct, but your associations are not complete:

class User < ActiveRecord::Base
  belongs_to :team
  has_one :leading_team, class_name: 'Team', foreign_key: 'leader_id'
  has_many :managed_teams, class_name: 'Team', foreign_key, 'manager_id'

class Team < ActiveRecord::Base
  has_many :users
  belongs_to :leader, class_name: "User"
  belongs_to :manager, class_name: "User"

And you should be all set.

PinnyM
  • 35,165
  • 3
  • 73
  • 81
2

Because a manager can have multiple teams, but is still "part of" the team, I'd suggest creating a join table for users and teams. We'll call it members. It will reference both user and team.

class CreateMembers < ActiveRecord::Migration
  def change
    create_table :members do |t|
      t.references :user
      t.references :team

      t.timestamps
    end
  end
end

Then, we'll need add the members association to the User model. Users will have many members, and, because of managers, have many teams as well. I've also included a function to get the team of a worker or leader, since there's only one.

class User < ActiveRecord::Base
  has_many :members
  has_many :teams, through: members, dependent: destroy

  validates_associated :members # More on this later

  # for workers and leaders
  def team
    self.teams.first
  end
end

Similar to the User model, we'll need to add the members association to the Team model. We'll also include a few functions to get the leader and manager of a team, and validation to make sure a team has exactly one leader and one manager.

class Team < ActiveRecord::Base
  has_many :members
  has_many :users, through: :members, dependent: destroy

  validate :has_one_leader_and_manager
  validates_associated :members # More on this later

  def manager
    self.users.where(type: 'manager').first
  end

  def leader
    self.users.where(type: 'leader').first
  end

  def has_one_leader_and_manager
    ['leader', 'manager'].each do |type|
      unless self.users.where(type: type).count == 1
        errors.add(:users, "need to have exactly one #{type}")
      end
    end
  end
end

Lastly, we'll set up the Member model. We can also include some validation to ensure that a team can only have one leader and one manager, and that workers and leader cannot belong to more than one team.

class Member < ActiveRecord::Base
  belongs_to :user
  belongs_to :team

  validate :team_has_one_leader_and_manager

  # Make sure player (worker or leader) is only on one team
  validates :user_id, uniqueness: true, if: :is_player?

  def is_player?
    ['worker', 'leader'].include? user.type
  end

  def team_has_one_leader_and_manager
    if ['leader', 'manager'].include?(user.type)
      if team.users.where('type = ? AND id != ?' user.type, user_id).count.any?
        errors.add(:team, "can't add another #{user.type}")
      end
    end
  end
end

Note that with the validation methods, you may want to move them around and/or refactor them, depending on how you add users and team, and how you'll add new members. However, this answer will hopefully give you enough information to get started.

Joe Kennedy
  • 9,365
  • 7
  • 41
  • 55
  • will my `team_id` foreign key from the users table still be necessary since you added a Members model? – oLraX Jun 19 '14 at 03:28
  • 1
    Oh great point. You won't need `team_id` in your `users` table. – Joe Kennedy Jun 19 '14 at 03:29
  • btw, for my form page, it should have a dropdown list for manager, and leader, so that it will be easy to identify.. for the dropdown list for members, i will try the select2.. so, how will i isolate the selections for manager and leader? – oLraX Jun 19 '14 at 05:58
  • You can use something along the lines of `User.where(type: 'manager')` as the list for possible managers. You can use something similar for leaders. – Joe Kennedy Jun 19 '14 at 06:06
  • ok, but what will be the content for `team_params` from `Team.new(team_params)` before saving? only `members` (since the leader and manager are part of the `members`)? – oLraX Jun 19 '14 at 06:29
  • If the same person can have a different role in different teams then you'd need to move the type field into Member. – Max Williams Jun 19 '14 at 09:16
  • You should only have to pass `members`, yes. But it definitely depends on the way your form is laid out. – Joe Kennedy Jun 19 '14 at 15:05
  • btw, your validation doesn't seem to work for me, i got false errors as a result.. – oLraX Jul 07 '14 at 10:46
  • What do you mean by "got false errors as a result"? What were the errors? Was the expected behavior different from the behavior you're observing? – Joe Kennedy Jul 07 '14 at 15:43
  • nevermind, i already solved it on my own. http://stackoverflow.com/questions/24582884/validation-error-through-factorygirl – oLraX Jul 08 '14 at 11:55