views:

22

answers:

1

Simply, a Contact can have various associated Time Windows, which may or may not be Active as a Schedule. To wit:

Models

class Contact < ActiveRecord::Base
  has_many :schedules
  has_many :time_windows, :through => :schedules
  accepts_nested_attributes_for :schedules, :allow_destroy => true
end

class TimeWindow < ActiveRecord::Base
  has_many :schedules
  has_many :contacts, :through => :schedules
end

class Schedule < ActiveRecord::Base
  belongs_to :contact
  belongs_to :time_window
end

View

  <% TimeWindow.all.each do |tw| %>
    <% schedule = Schedule.find_by_contact_id_and_time_window_id(@contact.id, tw.id)
       schedule ||= Schedule.new %>
    <p>
      <%= f.label tw.description %>
      <%= hidden_field_tag "contact[schedules_attributes][][id]", schedule.id %>
      <%= check_box_tag "contact[schedules_attributes][][time_window_id]",
 tw.id, @contact.time_windows.include?(tw) %>
      <%= check_box_tag "contact[schedules_attributes][][active]", nil,
 schedule.active %>
    </p>
  <% end %>

This submits something like this:

Parameters: { "commit" => "Update", "contact" => {
  "group_ids" => ["2"], "enabled" => "1",
  "schedules_attributes" => [ { "time_window_id"=>"1", "id"=>"46"},
   { "time_window_id" => "2", "id" => "42", "active" => "on" },
   { "time_window_id" => "3", "id" => "43"},
   { "time_window_id" => "4", "id" => "44", "active" => "on"}],
  "last_name" => ... 

The update action in the controller is basically stock, except to handle another instance of another related model which I coded using the "Handling Multiple Models" example from the Advanced Rails Recipes book.

According to this API doc, I think the above ought to work. However, nothing about the Schedules is getting updated. This shows up in the server log:

  [4;35;1mSchedule Update (0.2ms)[0m   [0mUPDATE `schedules` SET `updated_at` = '2010-09-30 20:39:49', `active` = 0 WHERE `id` = 42[0m
  [4;36;1mSchedule Update (0.1ms)[0m   [0;1mUPDATE `schedules` SET `updated_at` = '2010-09-30 20:39:49', `active` = 0 WHERE `id` = 44[0m

(NetBeans is giving me those stupid "[0m"'s in the output. I don't know what's wrong there.)

The SQL shows that the "active" boolean field is getting set to 0 where checked. How do I get this to correctly set the active bit?

As a followup, how would I organize this to get rid of the Schedule "connection" at all? I'm thinking I need to submit a :_delete with the Schedule from the form, but how would I do that conditionally when a checkbox is involved?

Thanks for any help you can provide. Rails is turning out to be a vast subject for me, and I want to do it "right." I'm really close here, but there's got to be a way to make this -- not just correct -- but elegant. The view code just feels way too cumbersome to be proper Rails. ;-)

A: 

I've kept trying different approaches to this problem, and I've come up with this, which works. Mostly. The only problem is that it doesn't handle NOT having a "Schedule" for each "Time Window". The form will render, and I'll get a disabled check_box (to prevent me from trying to delete something that isn't there), but I don't have a way to add it back, and submitting without it throws off the params hash (and causes Rails to give me an "Expected Hash (got Array)" error)

<% TimeWindow.all.each do |tw| %>
  <% schedule = Schedule.find_by_contact_id_and_time_window_id(@contact.id, tw.id)
     schedule ||= Schedule.new %>

<% f.fields_for "schedules_attributes[]", schedule do |sf| %>
  <p>
    <%= sf.label tw.description %>
    <%= sf.hidden_field :id %>
    <%= sf.check_box :_destroy, :disabled => schedule.new_record? %>
    <%= sf.check_box :active %>
  </p>
<% end %>

<% end %>

Note that the "schedules_attributes[]" array will automatically give you an existing ID within the braces in your HTML (which is nice), but the _attributes hash is expecting an "id" alongside the other attributes in order to make sense of the sub-hashes.

One of the big lessons I've learned here is that the "check_box_tag" method doesn't (seem to) give me a paired-up hidden field for Rails to parse in the unchecked case. I would have expected this. Adding one in by hand made a mess, which led me to finally giving into the "fields_for" method, and trying many incarnations before finding the appropriate syntax to get what I wanted out of it.

I've realized that my model isn't quite appropriate in this setup, so I'm going to change it, but I was so close to this answer, I wanted to at least get to the point of being able to see the end before I moved on.

David Krider