Posted by Richard White
Thu, 27 Apr 2006 14:54:00 GMT
AjaxScaffold has been deprecated in favor of ActiveScaffold
This release mainly addresses issues people with non-standard (by Rails definition) table names.
- Fixed default sort to work with non-standard (overridden) table names and primary keys.
- Fixed bug that was allowing models to be generated in the plural form and in subdirectories.
- Fixed edit/delete links so that they work with non-standard primary keys (ie not id).
- Removed test methods from the controller functional test since they were all outdated and broken anyways.
- Changed controller actions to explicitly call RJS templates so there aren’t any errors with conflicting RHTML templates. This was a common problem people had when upgrading from 2.0 or generating an ajax_scaffold over top of a
basic scaffold. If there was new.rjs and new.rhtml the RHTML would win out every time. I fixed this by changing return if request.xhr? to return render :action => ‘new.rjs’ if request.xhr?.
Thanks to Jamis Buck for his prompt email reply on this issue that allowed me to sneak the fix into this release.
- Changed the helper methods for column header and element row id’s to be more properly namespaced. The current way they were done allowed for the possibility of the top level id for the scaffold (scaffold_id-content) being repeated on a column header if that column had the name ‘content’. A very unfortunate thing would result in that case:

I’d also like welcome Jim Morris as the newest member of the AjaxScaffoldGenerator development team (actually the only other member outside of myself). If you’ve been on the forums at all I’m sure you’ve seen him around helping many of you out. Jim will be helping with some of the upcoming improvements/tweaks to the way scaffold_columns work.
Also, I should mention that I’ve made the Backpack page which outlines upcoming scaffold development public so feel free to take a look to see where we are going and make any suggestions.
I’d also like to apologize to those of you that have emailed me or posted questions on the forum lately. I’ve been very busy this week in preparation for my trip to the west coast for Startup School 2006. I’ll try to get back to everyone as soon as possible, in the meantime thanks to the rest of the community for filling in and helping out the newbies on the forum. Thanks.
Posted by Richard White
Fri, 21 Apr 2006 14:21:00 GMT
The one major thing that was lost in the shift to RJS templates was the default Rails debugging output when something went awry. Often times things
would just fail silently and leave you sitting there with only the loading spinner to comfort you. Most of the problems people have reported since the
switch to RJS are solved as soon as they look in the log and notice the syntax error that is causing all the problems.
I was originally pointing people to this code snippet that embeds Prototype AJAX requests and responses into the page. That script however was always escaping the HTML in the request and responses, which while generally a good idea, is annoying when Rails responds the standard error page. I found it tedious to look at the HTML code for the error page when all I really want is to see it rendered. Therefore I embedded the following in the bottom of any page I wished to debug:
<div id='debug'></div>
<script type="text/javascript">
Ajax.Responders.register({
onCreate: function(request, transport) {
$('debug').innerHTML = '<p><strong>' + request.url + '</strong></p>';
},
onComplete: function(request, transport) {
if (transport.responseText.match(/<html>/) != null) {
$('debug').innerHTML = transport.responseText;
} else {
$('debug').innerHTML = '<p><strong>' + request.url
+ '</strong></p><pre>' + transport.responseText.escapeHTML()
+ '</pre>';
}
}
});
</script>
The major addition here that if an <html> tag is found in the response it displays the response inline and does not escape it. This is invaluable since it means you get a legible error page when something goes wrong. I took this one step further. I created a debug a partial (shared/debug.rhtml) which I included (render :partial => ‘shared/debug’) at the bottom of my application/layout.rhtml. This allowed me to have a link on every page that I could use to toggle the display of debug information.
shared/debug.rhtml
<style type="text/css">
#debug-container {
clear:both;
margin-top: 15px;
}
#debug {
background: #ffd;
border: solid 1px #999;
padding: 5px;
margin-top: 15px;
overflow:auto;
}
#debug p {
margin: 0;
}
</style>
<div id="debug-container">
<% if params[:debug] == "true" %>
<strong><p><strong><%= link_to "Debug ON", :debug => false %></strong></p></strong>
<div id='debug'></div>
<script type="text/javascript">
Ajax.Responders.register({
onCreate: function(request, transport) {
$('debug').innerHTML = '<p><strong>' + request.url + '</strong></p>';
},
onComplete: function(request, transport) {
if (transport.responseText.match(/<html>/) != null) {
$('debug').innerHTML = transport.responseText;
} else {
$('debug').innerHTML = '<p><strong>' + request.url
+ '</strong></p><pre>' + transport.responseText.escapeHTML()
+ '</pre>';
}
}
});
</script>
<% else %>
<p><strong><%= link_to "Debug OFF", :debug => true %></strong></p>
<% end %>
</div>
While I’ve obviously tailored this solution for Rails you could easily use it on any application that relies on Prototype for its AJAX calls. If you want to check out a working debug go to ajaxscaffold.com where the debug toggle is at the bottom of every page. Enjoy!
Since this a more generally applicable post then I usually write I ask that you take a moment to Reddit and/or Digg it.
Posted by Richard White
Tue, 18 Apr 2006 09:03:00 GMT
AjaxScaffold has been deprecated in favor of ActiveScaffold
Didn’t expect to see me again so soon… that makes two of us :)

- Added support for defining your own scaffold columns in models by assigning an array of ScaffoldColumn’s to @scaffold_columns. If @scaffold_columns is not defined it will be created defaulting to columns for model.content_columns. I’ll explain this in more detail in the proceeding walkthrough.
- Added cancel as an action. There was enough inline code in _new_edit.rhtml that I thought, for consistency, it should be refactored into a server-side method.
- Refactoring: All common server side code has been moved into lib/ajax_scaffold.rb and namespaced more properly. include AjaxScaffoldUtil becomes include AjaxScaffold::Controller and include AjaxScaffold::Helper replaces include ajax_scaffold_helper
- Changed CSS styling of sorted columns to a more subtle darker blue from the yellow.
- Changed CSS and DOM structure (only slight changes) so support nested scaffolds
- Fixed the fact that clicking on a page link wasn’t showing the loading indicator
The rest of this post will be a walkthrough a couple scenarios (Based on this demo) for customizing your scaffolds for model associations (belongs_to and has_many) and in the process explain how to customize the scaffold columns. At the end of this post is a fairly detailed description of how to migrate existing 3.0.x scaffolds.
I realize that a blog post isn’t always the best place to be reading reems of code: download the demo code
Handling associations from the belongs_to side of things
For the purpose of having a contrived scenario to base all this on I’m going to have two models: person.rb and pet.rb. A person has_many pets and a pet belongs_to a person (that’s right there are no strays in demoland). I’m going to assume that you know how to create a db migration file and generate scaffolds for pets and people and move straight into customization of these scaffolds. First thing’s first so lets define our associations in our models and also customize the scaffold columns (These are the columns you’ll see in the scaffold table, not to be confused with those on a create/edit form).
models/pet.rb
require 'ajax_scaffold'
class Pet < ActiveRecord::Base
<strong>belongs_to :person, :foreign_key => "owner_id"
@scaffold_columns = [
AjaxScaffold::ScaffoldColumn.new(self, { :name => "name" }),
AjaxScaffold::ScaffoldColumn.new(self, { :name => "owner",
:eval => "pet.person.name", :sort_sql => "people.name" })
]</strong>
end
models/person.rb
require 'ajax_scaffold'
class Person < ActiveRecord::Base
<strong>has_many :pets, :foreign_key => "owner_id"
@scaffold_columns = [
AjaxScaffold::ScaffoldColumn.new(self, { :name => "name" }),
AjaxScaffold::ScaffoldColumn.new(self, { :name => "pets",
:eval => "person.pets.collect{ |pet| pet.name }.join(', ')", :sortable => false })
]</strong>
end
So besides adding the standard Rails belongs_to and has_many definitions I defined my custom column set by @scaffold_columns. ScaffoldColumn takes four possible options:
- :name (required) : unique identifier for the column. ScaffoldColumn will attempt to figure out the other variables, if not defined explicitly by you, based on the assumption that this is the name of a content_column for this model. That is to say if you are doing a column for a basic attribute of your model you can just supply the :name and be done with it.
- :eval : code that is eval’d to create the display value of the column. Eval isn’t the most performant call in Ruby but it is the most flexible since you can define whole blocks of code to be run to generate the display value for each column. You should note that I put pet.person.name not just person.name.
- :label : the human readable label for the column (aka the header text).
- :sort_sql : SQL statement to be used when this column is the sorted column. Note that you need to use the table name here, as opposed to the instance name you used with :eval, so people.name instead of person.name. If any of your sortable columns are from associations, such as with people.name in the above example, you need to add the model of the association to the :include option in the controller paginate call. Don’t worry if you don’t know what I’m talking about, I’ll do it just up ahead.
- :sortable : speaks for itself. This is for those instances where there may not be any logically way to sort the column, such as
with person.pets, or you just don’t want that column to be sortable.
Next we add the :include to the @paginate call in def component ...
controllers/pet_controller.rb
...
@paginator, @pets = paginate(:pets, :order_by => @sort_by, <strong>:include =>
:person</strong>, :per_page => default_per_page)
...
... add the drop down to the form …
views/pets/_form.rhtml
...
<div class="form-element">
<label for="pet_person">Owner%lt;/label>
<%= select 'pet', 'owner_id' , Person.find_all.collect {|p| [ p.name, p.id ] } &>
</div>
...
... and Viola!

Handling associations from the has_many side of things
For the other side of the coin, I’m going to generate a seperate controller (ruby script\generate ajax_scaffold Pet person_pets) for the pet model to use as the scaffold I’m going to embed into the person scaffold. You could say that this violates the DRY principle, and you’d be right, but there a number of small changes that need to be made to the embedded scaffold and I’d rather do it this way than using if/else on a parameter everywhere. Obviously I have the luxury in this contrived example of not having a lot of other code besides what was generated that will be repeated, if you actually have a fairly customized scaffold you might want to go with the latter route.
Create a link to editing pets…
views/person/_person.rhtml
...
<td class="indicator-container">
<%= loading_indicator_tag(@options) %>
</td>
<strong><td>
<% pets_options = @options.merge(:action => 'pets') %>
<%= link_to_remote "Pets",
{ :url => pets_options,
:loading => "Element.show('#{loading_indicator_id(@options)}');" },
{ :href => url_for(pets_options) } %>
</td></strong>
<td>
<% edit_options = @options.merge(:action => 'edit') %>
...
... create a view in which to embed the other scaffold. This will look a lot like _new_edit.rhtml so I’ll highlight the important differences …
views/person/_pets.rhtml
<% if not request.xhr? %>
<table class="ajax-scaffold" cellpadding="0" cellspacing="0">
<tbody>
<% end %>
<tr id="<%= element_row_id(@options) %>" <%= "style=\"display:none;\"" if
request.xhr? %>>
<td id="<%= element_cell_id(@options) %>" class="update" colspan="<%= num_columns
%>">
<strong><h4>Pets for <%= @person.name %></h4></strong>
<% if request.xhr? %>
<div id="<%= element_messages_id(@options) %>"
class="messages-container"></div>
<% else %>
<%= render :partial => 'form_messages' %>
<% end %>
<strong><% child_component_params = params.merge(:scaffold_id => [
@options[:scaffold_id], @options[:id], "pets"].join("-"), :parent_id
=>
@options[:id]) %>
<div id="<%= child_component_params[:scaffold_id] %>" class="ajax-scaffold">
<div id="<%= scaffold_content_id(child_component_params) %>">
<%= render_component :controller => '/demo/person_pets', :action =>
'component', :params => child_component_params %>
</div>
</div></strong>
<p class="form-footer">
<strong><% done_params = @options.merge(:controller => '/demo/person', :action
=> 'close_pets') %></strong>
<%= link_to_remote "Done",
{ :url => done_params,
:loading => "Element.show('#{loading_indicator_id(@options)}');" },
{ :href => url_for(done_params) } %>
<%= loading_indicator_tag @options %>
</p>
<strong><script type="text/javascript">
Rico.Corner.round('<%= child_component_params[:scaffold_id] %>', {color:
'#005CB8', bgColor: '#fff', compact: true});
</script></strong>
</td>
</tr>
<% if not request.xhr? %>
</tbody>
</table>
<% end %>
... create pets action (parallel in function to edit) and close_pets (indentical to pets) in the controller …
controllers/person_controller.rb
def pets
@person = Person.find(params[:id])
@successful = true
return if request.xhr?
# Javascript disabled fallback
if @successful
<strong>@options = { :action => "pets", :id => params[:id] }
render :partial => "pets", :layout => true</strong>
else
return_to_main
end
end
def close_pets
@person = Person.find(params[:id])
@successful = true
return if request.xhr?
return_to_main
end
... create pets.rjs (parallel to edit.rjs) ...
views/person/pets.rjs
@options = { :scaffold_id => params[:scaffold_id], <strong>:action => "pets"</strong>, :id
=> params[:id] }
@view_options = @options.merge(:action => "view")
if @successful
page.hide element_row_id(@view_options)
page.insert_html :bottom, scaffold_tbody_id(@options), <strong>:partial => 'pets'</strong>,
:locals => { :hidden => true }
page << "new TableRow.MoveAfter('#{element_row_id(@view_options)}',
'#{element_row_id(@options)}');"
page.show element_row_id(@options)
page.replace_html element_messages_id(@options), :partial => 'form_messages'
else
page.replace_html scaffold_messages_id(@options), :partial => 'messages'
end
page.hide loading_indicator_id(@view_options)
... create close_pets.rjs (parallel to cancel.rjs) ...
views/person/close_pets.rjs
@options = { :scaffold_id => params[:scaffold_id], :action => "view", :id => params[:id] }
@pets_options = @options.merge(:action => "pets")
if @successful
page.remove element_row_id(@options)
page.insert_html :bottom, scaffold_tbody_id(@options), :partial => 'person', :locals =>
{ :hidden => true }
page << "new TableRow.MoveAfter('#{element_row_id(@pets_options)}',
'#{element_row_id(@options)}');"
page.remove element_row_id(@pets_options)
page.show element_row_id(@options)
page << "AjaxScaffold.stripe('#{scaffold_tbody_id(@options)}');"
page.replace_html scaffold_messages_id(@options), :partial => 'messages'
else
page.replace_html element_messages_id(@pets_options), :partial => 'form_messages'
page.hide loading_indicator_id(@pets_options)
end
Now we’ve finished modifying the parent person controller and assorted actions we’ll move on to making our adjustments to the person_pets controller and actions. First change the helper for the embedded scaffold to override showing all defined @scaffold_columns and show only the name column …
helpers/person_pets_helper.rb
...
def scaffold_columns
[ Pet.scaffold_columns_hash["name"] ]
end
...
... add the :conditions to @paginate in def component of the person_pets_controller so that it only displays those pets associationed with the parent_id passed in …
controllers/person_pets_controller.rb
...
@paginator, @pets = paginate(:pets,
<strong>:conditions => [ "owner_id = ?", params[:parent_id] ],</strong>
:order_by => @sort_by, :per_page => default_per_page)
...
... add the parent_id when creating a new pet …
controllers/person_pets_controller.rb
...
def new
@pet = Pet.new
<strong>@pet.owner_id = params[:parent_id]</strong>
@successful = true
...
... add the owner_id as a hidden tag into _new_edit.rhtml …
views/person_pets/_new_edit.rhtml
...
<%= hidden_field_tag "scaffold_id", @options[:scaffold_id] %>
<strong><%= hidden_field "pet", "owner_id" %></strong>
...
... and Viola!

Migrating
Here are a bunch of quick notes I made to help those of you upgrading your current 3.x scaffolds to 3.1. Go ahead and generate a 3.1 scaffold in a new directory to use as a template for copying code into older scaffolds.
Delete the following:
- helpers/ajax_scaffold_helper.rb. Any customization you have made to this or the ajax_scaffold_lib can be migrated into ajax_scaffold.rb under the proper modules (should be fairly straightforward which ones)
- lib/ajax_scaffold_lib.rb
- lib/content_column_patch.rb. Remember to take the require ‘content_column_patch’ out of your environment.rb if you were using this
Changes to controllers:
Changes to helpers:
Changes to models:
- Add require ‘ajax_scaffold’ to your existings models
Changes to views:
- Move the class attribute that was on the <TR> tag in _new_edit.rhtml onto the <TD> tag instead.
- Add class ‘form-footer’ to the <p> at the bottom of _new_edit.rhtml that contains the submit button and cancel link
- Swap out _column_headings.rhtml with that from a new scaffold. Be sure to set the :controller parameter correctly though
- Swap out lines 6-10 of _widget.rhtml with code from a new scaffold
Closing Thoughts
I hope this update helps those that were struggling with modifying 3.0 scaffolds to work with associations. I’ll have another writeup later on today about some other useful code/modifications you can do to your scaffolds. Thanks for the patience and feedback, keep it coming.
Posted by Richard White
Sun, 16 Apr 2006 20:36:55 GMT
AjaxScaffold has been deprecated in favor of ActiveScaffold
I’ve been listening to all the feedback from the 3.0 release and come to a simple conclusion: I screwed up. I got pretty wrapped up in some parts of 3.0 and neglected to think about how “you guys” would be able to bend generated scaffolds to your will.
Case in point is my lame excuse for table data customization. Defaulting to show all the fields on a model is fine, but having the only easy option for customizing this be to defined some subset of model attributes is just weak. What about associations? belong_to, has_many and so on. And what if I’m doing single table inheritance?
Speaking of has_many and belongs_to, how can I modify the generated scaffolds to be used for creating my has_many child objects?
Fear not gentle reader (I’ve been dieing to work in the phrase ‘gentle reader’ into a post for a while now), I am currently working on a release that should make column customization much easier and more flexible. It also should make all that crusty code in _column_headings and _item a bit more readable. I currently have all the code written for what will be 3.1 and am putting it through its paces on a couple demos for associations, specifically a demo with nested scaffolds (Which is also getting some CSS love to make it look less craptacular).
I would expect everything to be released/written up in the next 48 hours.