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:
<%= form_for @user do |f| %>
<table>
<tr><th>Completed</th><th>Time spent</th><th>Task</th></tr>
<%= f.fields_for :completions, available_completions_for_milestone(@user, @milestone) do |cf| %>
<tr>
<td><%= cf.check_box :status, {disabled: cf.object.persisted?}, "done", "unreported" %></td>
<td><%= cf.text_field :time_spent_text %></td>
<td><%= cf.object.task.description %></td>
</tr>
<%= cf.hidden_field :task_id %>
<% end -%>
With a helper method:
def available_completions_for_milestone(user, milestone)
user_completions = user.completions.in_milestone(milestone)
available = []
milestone.tasks.each do |t|
c = user_completions.select{|c| c.task_id == t.id}.first
if !c then # make it
c = user.completions.build( task: t )
end
available << c
end
available
end
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:
has_many :completions
accepts_nested_attributes_for :completions, :reject_if => proc { |attrs| attrs['status'] == 'unreported' }
I also had to make completions_attributes
attr_accessible on the User model. If you make task_ids
accessible then update
will delete completions that were left out of the PUT
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With