0

I am having the hardest time getting this to properly render and update. What is going on, is that I have a many to many association, with the association joining table containing multiple attributes. A case can have many screens, a screen many cases, and a case's screen can be ordered for each case. One other strange item that is occurring with my current setup, is that the association table, upon each time of submiting the form, is not clearing out the old association values. The new values are being appended to the table.

Classes:

class Case < ActiveRecord::Base
    attr_accessible :cases_screens_attributes

    has_many :cases_screens
    has_many :screens, :through => :cases_screens

    accepts_nested_attributes_for :cases_screens, 
                                  :allow_destroy => true,
                                  :reject_if => lambda { |p| p[:screen_id].nil? }
end

class Screen < ActiveRecord::Base
    has_many :cases_screens
    has_many :cases, :through => :cases_screens
end

class CasesScreen < ActiveRecord::Base
    attr_accessible :order

    belongs_to :case
    belongs_to :screen
end

Controller:

# GET /cases/1/edit
def edit
    @case = Case.find(params[:id])
    @cases_screens = @case.cases_screens.order("cases_screens.[order] ASC")
    @screens = Screen.where({:active => true})
end

# PUT /cases/1
# PUT /cases/1.json
def update
    @case = Case.find(params[:id])
    @cases_screens = @case.cases_screens.order("cases_screens.[order] ASC")
    @screens = Screen.where({:active => true})

    respond_to do |format|
      if @case.update_attributes(params[:case])
        format.html { redirect_to case_path(@case), :notice => t(:case_successfully_updated) }
        format.json { head :no_content }
      else
        format.html { render :action => "edit" }
        format.json { render :json => @case.errors, :status => :unprocessable_entity }
      end
    end
end

I have tried many attempts at using fields_for to no avail. This is what I currently have in my _form.erb.html file. The final goal is to, on the case form see a list of all screens with a checkbox next to each screen. Each checkbox also has an associated input for "order". Upon submission, the cases_screens table will be properly populated (old values removed if no longer selected).

<div class="controls">

  <% @screens.each do |s| %>

    <label>
      <%= check_box_tag("case[cases_screens_attributes][#{s.id}][screen_id]", s.id, @case.screen_ids.include?(s.id)) %>
      <%= s.name %>
    </label>
      <%= select_tag("case[cases_screens_attributes][#{s.id}][order]", options_for_select(1..@screens.size, 2)) %>
  <% end %>

</div>

I have been banging my head against the wall for a few days now on this one, and I just cannot seem to get it right. Any help out there would be greatly appreciated.

Additional Note:

I use simple form, I just did the following:

<%= f.association :screens %>

and everything works as expected (sans order attribute update). The association table is properly cleaned up. This is, of course, pointing me in the direction of nested attributes as the culprit, but I am unsure.

Update Wednesday 10:45 I am now convinced that the issue that is happening here is a lack of having the cases_screens_id present in the form. Normally during cleanup, rails will use this ID to clean up previous entries, then populate the new. Since I am looping over @screens, rather then the currently present cases_screens (associated to the case), I have no way of getting the id without a query going over each row.

I guess for now, my solution is going to write a before_save method into cases_screens that will clean up the current entries for cases_screens, then allow the new values to populate.

Update 1:15 I have gotten a little bit closer with at least one of the items so that records are only inserted if a checkbox is checked. I have added the updated :reject_if to my Case class above. I still have no solution as to why values are only appended to table, and why previous values are not removed.

Update 4:00 My latest attempt included a hidden field that contained the cases_screens ID if the record had only been set. This required me to make a subquery in the loop to get the current cases_screen_id. Along with the :reject_if parameter, values are now properly being updated. However, Currently set values can no longer be removed.

There is no way I am doing this right, it doesn't feel rails-like. Would this scenario be one of the ones where I should just process the data manually in the controller?

Venice
  • 1,858
  • 1
  • 11
  • 10
  • Err... shouldn't `cases_screen` be plural while you're using it with `has_many`? Like `has_many :cases_screens` ? – scaryguy Dec 04 '12 at 20:44
  • I updated the code to now reflect what my models have (pluralization included). It is still behaving the same way. – Venice Dec 04 '12 at 21:08

1 Answers1

0

At this time, I am chalking this up to a design issue.

I am now using

  <% @case.cases_screens.each do |cs| %>
    <%= hidden_field_tag("case[cases_screens_attributes][#{cs.id}][id]", cs.id) %>
    <%= hidden_field_tag("case[cases_screens_attributes][#{cs.id}][screen_id]", cs.screen_id) %>
    <%= hidden_field_tag("case[cases_screens_attributes][#{cs.id}][_destroy]", 1) %>
    <label><%= check_box_tag("case[cases_screens_attributes][#{cs.id}][_destroy]", 0, @case.cases_screen_ids.include?(cs.id)) %> <%= cs.screen.name %></label>
    <%= text_field_tag("case[cases_screens_attributes][#{cs.id}][ordinality]", cs.ordinality) %>
  <% end %>

This causes only the currently set case/screen linkups to show up. The user will need to add specific screens to the case via a seperate screen.

Venice
  • 1,858
  • 1
  • 11
  • 10