views:

28

answers:

0

The central problem: How do you merge attribute collections by a key during mass assignment from a nested form.

The details: I am using the following models:

class Location < ActiveRecord::Base
  has_many :containers,
    :dependent => :destroy,
    :order => "container_type ASC"

  validates_associated          :containers
  accepts_nested_attributes_for :containers,
    :allow_destroy => true,
    :reject_if => proc {|attributes| attributes["container_count"].blank? }
end

class Container < ActiveRecord::Base
  belongs_to :location, :touch => true

  validates_presence_of     :container_type
  validates_uniqueness_of   :container_type, :scope => :location_id
  validates_numericality_of :container_count,
    :greater_than => 0,
    :only_integer => true
end

So there is a constraint of having only one container type per location. The following views render the location and associated containers:

admin/containers/_index.html.erb

<% remote_form_for [:admin, setup_containers(@location)] do |f| -%>
  <% f.fields_for :containers do |container_form| -%>
    <%= render "admin/containers/form", :object => container_form %>
  <% end -%>
  <%= f.submit "Speichern" %>
<% end -%>

admin/containers/_form.html.erb

<% div_for form.object do -%>
  <span class="label">
    <%- if form.object.new_record? -%>
      <%= form.select :container_type, { "Type1" => 1, "Type2" => 2, ... } %>
    <%- else -%>
      <%= form.label :container_count, "#{form.object.name}-Container" %>
      <%= form.hidden_field :container_type %>
    <%- end -%>
  </span>
  <span class="count"><%= form.text_field :container_count %></span>
  <%- unless form.object.new_record? -%>
    <span class="option"><%= form.check_box :_destroy %> Löschen?</span>
  <%- end -%>
<% end -%>

module Admin::ContainersHelper

def setup_containers(location)
  return location if location.containers.any? {|l| l.new_record? }
  returning location do |l|
    all_container_types = [1, 2, ...]
    used_container_types = l.containers.try(:collect, &:container_type) || []
    next_container_type = (all_container_types - used_container_types).first
    l.containers.build :container_type => next_container_type if next_container_type
  end
end

Essentially, the helper adds an new container to the collections except all types have already been associated or there is already a new container in the collection. This container is preinitialized to the first not-yet-defined container type. This works out pretty well so far. Adding containers works. Deleting containers works.

The problem is: I want to achieve that choosing and adding a container type which is already in the collection should sum up their counts (instead it would violate the unique constraint). I'm not sure what would be the best way without implementing/reinventing the complete accepts_nested_attributes_for magic - actually I wanted to reduce - not increase - code and complexity by using that.