Question

I need some help with a rails development that I'm working on, using rails 3. This app was given to me a few months ago just after it's inception and I have since become rather fond of Ruby.

I have a set of Projects that can have resources assigned through a teams table.

A team record has a start date and a end date(i.e. when a resource was assigned and de-assigned from the project).

If a user has been assigned and deassigned from a project and at a later date they are to be assigned back onto the project, instead of over writting the end date, I want to create a new entry in the Teams table, to be able to keep a track of the dates that a resource was assigned to a certain project.

So my question is, is it possible to have multiple entries in a :has_many through association?

Here's my associations:

class Resource < ActiveRecord::Base
  has_many :teams
  has_many :projects, :through => :teams 
end

class Project < ActiveRecord::Base
  has_many :teams
  has_many :resources, :through => :teams
end

class Team < ActiveRecord::Base
  belongs_to :project
  belongs_to :resource
end

I also have the following function in Project.rb:

after_save  :update_team_and_job

private
  def update_team_and_job

    # self.member_ids is the selected resource ids for a project

    if self.member_ids.blank?
      self.teams.each do |team|
        unless team.deassociated
          team.deassociated = Week.current.id + 1
          team.save
        end
      end
    else
      self.teams.each do |team|

        #assigning/re-assigning a resource

        if self.member_ids.include?(team.resource_id.to_s)
          if team.deassociated != nil
            team.deassociated = nil
            team.save
          end
        else

          #de-assigning a resource

          if team.deassociated == nil
            team.deassociated = Week.current.id + 1
            team.save
          end
        end
      end

      y = self.member_ids - self.resource_ids
      self.resource_ids = self.resource_ids.concat(y)

      self.member_ids = nil
    end
  end
end
Was it helpful?

Solution

Sure, you can have multiple associations. has_many takes a :uniq option, which you can set to false, and as the documentation notes, it is particularly useful for :through rel'ns.

Your code is finding an existing team and setting deassociated though, rather than adding a new Team (which would be better named TeamMembership I think)

I think you want to just do something like this:

  1. add an assoc for active memberships (but in this one use uniq: => true:

    has_many :teams
    has_many :resources, :through => :teams, :uniq => false
    has_many :active_resources, 
             :through => :teams, 
             :class_name => 'Resource', 
             :conditions => {:deassociated => nil},
             :uniq => true
    
  2. when adding, add to the active_resources if it doesn't exist, and "deassociate" any teams that have been removed:

    member_ids.each do |id|
      resource = Resource.find(id) #you'll probably want to optimize with an include or pre-fetch
      active_resources << resource # let :uniq => true handle uniquing for us
    end
    
    teams.each do |team|
      team.deassociate! unless member_ids.include?(team.resource.id) # encapsulate whatever the deassociate logic is into a method
    end
    

much less code, and much more idiomatic. Also the code now more explicitly reflects the business modelling

caveat: i did not write a test app for this, code may be missing a detail or two

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top