Thursday, May 20, 2010

habtm - accepts_nested_attributes_for

Background

I was reviewing Ryan Bates Railscast on deeply nested forms to use the new accepts_nested_attributes_for definition. Everything went swimmingly until I got to the objects with has_and_belongs_to_many definitions.

My objects were somthing like this (this is just an example):
class Album < ActiveRecord::Base
  has_many   :content, :dependent => :destroy
  accepts_nested_attributes_for :content, :allow_destroy => true
end

class Content < ActiveRecord::Base
  set_table_name "content"

  has_and_belongs_to_many :performers, :join_table => "content_performers"
  accepts_nested_attributes_for :performers, :allow_destroy => true
  has_and_belongs_to_many :producers, :join_table => "content_producers" 
  accepts_nested_attributes_for :producers, :allow_destroy => true
end

class Performer < ActiveRecord::Base
  has_and_belongs_to_many :content
 end

class Producer < ActiveRecord::Base
  has_and_belongs_to_many :content
end

The Problem

Using the helper methods and javascript outlined in the railscast, I set up my forms and started testing. Looking at webrick, all the correct data was coming through the post. The album object saved just fine along with the embedded content. However, when I went to verify in the database, both the performers and producers were empty.

The Answer

I did a lot of head scratching. Sifting through the code salad of the webrick output, I happened to notice this:
WARNING: Can't mass-assign these protected attributes: performers_attributes
WARNING: Can't mass-assign these protected attributes: producers_attributes
Modifying my Content class as follows:
class Content < ActiveRecord::Base
  set_table_name "content"

  has_and_belongs_to_many :performers, :join_table => "content_performers"
  accepts_nested_attributes_for :performers, :allow_destroy => true
  has_and_belongs_to_many :producers, :join_table => "content_producers" 
  accepts_nested_attributes_for :producers, :allow_destroy => true

  attr_accessible :performers_attributes, :legal_producers_attributes
end
I was then able to continue using the forms without problems. I'm not sure if this is a bug or just a misunderstading on my part about how accepts_nested_attributes_for works, but either way, this bug is squashed for now. It would seem to me though these accessible attributes would be a given when declaring accepts_nested_attributes_for.

JQuery, dialog() and forms

JQuery's dialog() and blatent disrespect for your DOM

In my Rails nested forms post I described a bit about using accepts_nested_attributes_for for deeply nested forms. It also happens that I wanted (I wouldn't say I wanted it, but the client did) some of those forms as an ajax popup after the content had been uploaded. My thought was I could just lay everything out like a normal form on a page, use JQuery's dialog() on the div encapsulating the ajaxified forms and on submission, ajax forms would pop up and when completed, I could submit the form normally and the backend would be so happy I didn't screw with anything.

Oh how I was wrong.

In all fairness, for JQuery to show a modal dialog, it has to move the containing element to a child of the body or things just won't work. My first error was not realizing that. After I had my forms set up, ajax goodness popping up and forms submitting with neat little javascript buttons, I went through a 30 minute period of rage wondering where the hell my form elements went on POST.

Upon initializing several dialogs on the same page:
function initialize_content_forms() {
    $(".content_form").dialog({ autoOpen: false, modal: true});
}

My forms immediately get placed as a child of the body. JQuery gives us plenty of hooks for the dialogs so I thought maybe I could get away with sticking my container back in the place it used to live when I close the dialog.

Magic happens here:
//many dialogs and forms are on screen for multiple uploads
//at this point I have used JQuery to select the proper dialog
//under a specific form and put it in the variable dialog
dialog.bind("dialogclose", function(event, ui) { form_close(this, form_id); });

function form_close(dialog, form_id) {
  //if we dont hide it, it will be put back
  //in its original place looking strange
  $(dialog).hide();
    
  //this is the container of the original form
  //again, there were many, so i had to go to the right
  //id and append the dialog (form)
  var content_container = $(dialog).attr('id') + '-fields';
  $(dialog).appendTo('#' + content_container);
    
  $('#' + form_id).submit();   
}

Now my form fields have been put back in the correct place in my DOM and upon POST everything is right in the world. However, when you put the dialog back in it original place, it still contains some JQuery added styles and such. I think this could be fixed with:

$(dialog).destroy();

but I am uncertain as my forms were hidden anyway and it didn't make much difference.

Bottom Line

Personally, I think that there should be an option to set that automatically returns your container element to it's original place in the DOM upon closing. Granted, the solution wasn't very complicated but the hair pulling running up to that solution is something I would have liked to avoid. Maybe I can get involved with the JQuery project and see what they think.

Sunday, May 16, 2010

Learning To Pencil, Then Program

First, this post is inspired from the incredible blog (and post) Better Explained - Learning to Learn: Pencil, Then Ink. In that blog post the author describes the aha! moment of learning to draw. The best artists don't create incredible drawings on their first pass with no mistakes. Drawing is an iterative process, with mistakes!, that combines to create a polished finished product.

Programming with Mistakes?


Not syntactical or semantic mistakes, mind you, but mistakes in design and direction. A lot of the end code that we see that may be celebrated, whether its an active open source application or even just a rails plugin, is a product of iterative development. However, this development is probably not the product of sequential keystrokes with no backspace.

Some of the best programmers I've worked with don't write the best code, or even the right code, the first time around. Often, this is part of the process. Exploratory code is much like the pencil sketch underlying the inked finished product. It gives you, as a programmer, a more solid understanding of the goals you are *actually* trying to accomplish and provides a visual model of your code instead of the whispy ideas floating in your head. It's often rewarding to take this visual block of clay and mold it into something efficient and clean.

The Disclaimer


For people like me, the unfortunate truth is there are plenty of brilliant people that write clean, beautiful code with their eyes closed. I am not one of those people. It takes some time to accept this fact as well as becoming comfortable writing scrappy code as a first draft. The end results, hopefully, will speak for itself in smaller, efficient, more readble code.