How to display a form for a subset of associated records, some of which don't exist yet?

拈花ヽ惹草 提交于 2019-12-10 13:48:16

问题


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?


回答1:


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.




回答2:


I'm answering this myself as a possible solution for any other readers, but it isn't satisfying so I'm not marking it as acceptable.

To work around this, in the form generation action I just create any missing associations in an 'inactive' state. (By adding a status field to the Completion model.) Then I use the "object[]" array editing feature of forms. RISCfuture has some helpful tips in the 2nd comment on the forms_for API docs.



来源:https://stackoverflow.com/questions/4876317/how-to-display-a-form-for-a-subset-of-associated-records-some-of-which-dont-ex

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!