开发者

Using Rails to properly model with has_many :through and belongs_to

I'm trying to find the best way to define models for team / member / person, where a person can be a member of many teams and a team can have many members. Along with the "member" relationship is the position which the person fills for the team. A team should also have exactly one head coach and one assistant coach. A person could be a head/assistant coach for more than one team.

Below is my current (futile) attempt:

class Team < ActiveRecord::Base
  has_many :members
  has_many :people, :through => :members
  belongs_to :head_coach :class => 'Person' 
  belongs_to :assistant_coach :class => 'Person'
end

class Person < ActiveRecord::Base
  has_many :teams
  has_many :teams, :through => :members
end

class Member < ActiveRecord::Base
  belongs_to :team
  belongs_to :person
  # has a "position" which is a string
end

This approach is causing me two problems:

  1. The Team's belongs_to :head_coach and :assistant_coach doesn't work. Maybe it should be a has_one, but then I'm not sure it makes sense to put the belongs_to in Person (I want a FK in Team to Person). The example below shows that how I have it set-up doesn't jive with ActiveRecord:

    irb(main):006:0> t = Team.find(1)
    => #<Team id: 1, name: "Champs", created_at: "2011-07-18 01:50:56", updated_at: "2011-07-19 01:47:26", head_coach: nil> 
    irb(main):007:0> t.head_coach
    => nil
    irb(main):008:0> t.head_coach = Person.find(1)
    => #<Person id: 1, name: "Chris", created_at: "2011-07-18 01:52:34", updated_at: "2011-07-18 01:52:34">
    irb(main):009:0> t.save
    => true
    irb(main):010:0> t.head_coach
    => #<Person id: 1, name: "Chris", created_at: "2011-07-18 01:52:34", updated_at: "2011-07-18 01:52:34">
    irb(main):011:0> Team.find(1).head_coach
    => nil
    
  2. The has_many :through seems to work but I haven't found a good way to list the positions for each person within a team. This is my current attempt within a view:

    <% @team.people.each do |person| %>
      <%开发者_JAVA百科= person.name +" "+ @team.members.find_by_person_id(person).position %>
    

Is there an overall better approach to representing these relationships?

Thanks for the help!

-Chris


I ended up going with the following approach using the explicit foreign_key which is working OK.

class Team < ActiveRecord::Base
  has_many :members
  has_many :people, :through => :members
  belongs_to :head_coach, :class => 'Person', :foreign_key head_coach_id 
  belongs_to :assistant_coach, :class => 'Person', :foreign_key assistant_coach_id
end

class Person < ActiveRecord::Base
  has_many :teams, :through => :members

end

class Member < ActiveRecord::Base
  belongs_to :team
  belongs_to :person
  # has a "position" which is a string
end

The only downside to date is I haven't been able to get the foreign keys to load automatically in the controller. I need to add code such as the following in order to save the associations:

# POST /teams
def create
  @team = Team.new
  @team.head_coach = Person.find(params[:head_coach])
  @team.assistant_coach = Person.find(params[:assistant_coach])

  ....


Could you have a boolean field for head_coach and assistant_coach in your members table and use a validate unique rule with a scope.

EG Add to members table

is_head_coach:boolean
is_assistant_coach:boolean 

Now in your member model have

validates_uniqueness_of :is_head_coach, :scope => [:team_id, :person_id]
validates_uniqueness_of :is_assistant_coach, :scope => [:team_id, :person_id]

Then you could use some named scopes in your person model or team model to find the head_coach.

As for listing all the positions couldn't you use:

@team.members.each do |member|
   "#{member.person.name} #{member.position}"

If you want to sort them in a specific way maybe you could add a field called sort_order:integer to the members table and sort by that.

Update:

I take your point that this solution above is not scalable. What about if you use the position field in your member model and create a scope aka 'named scope' for it like the following:

scope :head_coach, lambda { where('position = 'head coach') }

Then you could use something like:

team.members.head_coach

You could go even further with this scope

scope :get_position, lambda{|pos| where('position = ?', pos)}

and use

team.members.get_position('head_coach')

You could write a custom validate method to check that you end up with on head_coach per team, etc.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜