I have Tasks and Users. When a user completes a task, I create a Completion which has a field for the user to indicate how long they spent. I need a form that shows all the tasks with their completion status and time_spent attribute. On submit, completions that existed should be updated and new ones should be created. I'd like to do this in Formtastic if possible but I'll be happy with a basic Rails 3 solution.
class Completion < ActiveRecord::Base
  belongs_to :task
  belongs_to :user
  # attribute time_spent
end
class User < ActiveRecord::Base
  has_many :completions
  has_many :tasks, :through => :completions
end    
class Task < ActiveRecord::Base
  belongs_to :milestone
  has_many :completions
  has_many :users, :through => :completions
end
An extra aspect is that I want to show just a certain set of tasks, such as those belonging to a Milestone. Should I have a form on the Milestone controller that posts to the Completions controller?
class Milestone < ActiveRecord::Base
  has_many :tasks
  has_many :completions, :through => :tasks
end
UPDATE I've looked for days now and I've found many dead ends. This Multiple objects in a Rails form is close, but it requires that all the linking objects already exist.
What sets this question apart is that some of the links don't exist yet and there is no single model for the links to be nested in. E.g. With Ryan Daigle's Nested Object Forms post) I've made this work in a form to edit all possible Completions for a user, but I need to edit a subset of possible Completions in one form. Do I need to make a redundant object MilestoneCompletions that has_many Completions and belongs_to User? Can an ActiveModel has_many?
                        
I finally solved this. One key is the collection argument to fields_for. The other is to generate the collection with a mix of existing and new records.
So in the view, something like:
With a helper method:
Notice in the view that completions already in the DB are checked and disabled so they can't be unchecked. The unchecked state gets the value "unreported" and the User model can filter out those records so they don't go in the DB:
I also had to make
completions_attributesattr_accessible on the User model. If you maketask_idsaccessible thenupdatewill delete completions that were left out of thePUT.