[Rails] Re: Using an unsaved object

Eric Anderson eric at afaik.us
Wed Jan 19 01:12:33 GMT 2005


Michael Koziarski wrote:
> What about if you change this to:
> 
> <%= text_field ("user", "username") %>
> 
> That's the 'rails way' to handle this.

To explain this would probably get a bit off-topic, but what the heck. 
I'm always interested in feedback. I was not completely honest in what 
the code actually is. I removed aspects that were not important. The 
actual code is:

<textbox name="username" value="<%=@usr.username%>" />

Of course this isn't (X)HTML. This is because I don't use the helper 
functions provide by rails. Instead I use a XSLT library that I have 
built over time with a number of other frameworks. From my experience 
there are 4 basic way to generate standard chunks of HTML content. The 
first is the way you described above. It is the functional method. So:

text_field "user", "username"

and obviously you get more complex as these "widgets" get more complex. 
I have seen all sorts of complex components developed with this simple 
functional interface. There are a couple of problems with this 
interface. The biggest is that over time you keep adding more options to 
deal with more situations. Pretty soon you have a function that looks like:

text_field "user", "username", "Enter Username", true, false, 
"onClick=\"my_func()\""

Now what does each of these arguments mean? You end up having to look at 
the function prototype often to remember what they are and what order 
they go in (unless you have a IDE that does some sort of intellisense). 
Even if you know what they mean because you use them so often do you 
really want to type those arguments every time. It would be nice to have 
defaults. And something like:

text_field "user", "username", nil, nil, nil, "onClick=\"my_func()\""

isn't really much better. So the next stage up is using named arguments 
(or hashes in Ruby) with good defaults. With this you end up with:

text_field( object => "user",
	attribute => "username",
	default => "Enter Username",
	editable => true,
	required => false,
	htmlExtra => "onClick=\"my_func()\"" )

This works pretty good. Especially if the passed in hash is merged with 
a good set of defaults so you can do something like:

text_field( object => "user", attribute => "username" )

and rely on the defaults for everything else. This way the simple case 
is easy, but the hard case is still possible. Also the order of the 
arguments don't matter anymore.

So what is the problem with this method? What if you want a more complex 
object that cannot be specified easily with a simple hash. For example, 
what if you want to create a list component which displays a list of 
items. This component will handle putting things like header cells for 
each column as well as a title to the list. Also it could support things 
like putting a checkbox on each item so you can select items to take an 
action on. It would also provide useful features like alternating rows 
to different colors, allowing you to click anywhere on the list row to 
enable the checkbox, and specify a series of actions that can be taken 
for selected objects. It will also set things up so that when you submit 
an action it will direct it to the right script (controller and action). 
So we are talking about a highly interactive list instead of just a 
plain HTML table. How do you specify this with the hash interface? It 
because difficult as you continue to add features to your component. So 
the next step up is a "Builder" object interface. So you do something 
like this:

<%
list = HTMLList.new
list.odd_color = "#d4e0dd"
list.even_color = "#de03df"
new_action = HTMLListAction.new
new_action.action = 'new'
new_action.text = 'New User'
list.add_action new_action
edit_action = HTMLListAction.new
edit_action.action = 'edit'
edit_action.text = 'Edit User'
list.add_action edit_action
delete_action = HTMLListAction.new
delete_action.action = 'delete'
delete_action.text = 'Delete User'
delete_action.vertical_location = :bottom
list.add_action delete_action
list.add_column( "Username" );
list.add_column( "Active" );
list.add_row( 'eric', true );
list.add_row( 'hunter', false );
%>
<%=list.output%>

I am just making up this interface on the fly. Obviously you can make it 
better or worse, but the concept is the same. You create objects to 
generate content. Objects have default values for attributes and you can 
set the optional attributes. When you have you object built you call 
some sort of output method which generates your HTML content from the 
information you have given it.

This seems to solve all our problems, but introduces a new one. We can 
do complex stuff but the easy stuff has now become hard. What if you 
just want to output what we started with? You end up with:

<%
username = TextBox.new
username.object = @usr
username.attribute = :username
%>
<%=username.output%>

This type of mess really gets in the way of your view code and makes 
things confusing.

The last way (and my preferred way) is to use a tag based markup method. 
So if you want to output a calender input box to enter the user's date 
of birth you have:

<calendar name="dob" value="<%=@usr.dob%>" />

This directly matches traditional HTML which you are already using:

<textarea name="description"><%=description%></textarea>

It will allow you to have defaults and put things in any order you want 
like the hash method. So you could have:

<textfield name="user" value="<%=@usr.username%>" required="false" 
editable="true" onclick="my_func()" />

for the complex case but also just:

<textfield name="user" value="<%=@usr.username%>" />

for the easy case. It also handles the really complex case like the list 
component. So you can have:

<list odd="#d4e0dd" even="#de03df">
	<action name="new" label="New User" />
	<action name="edit" label="Edit User" />
	<action name="delete" label="Delete User" vpos="bottom" />

	<header>Username</header>
	<header>Active</header>
	<row>
		<column>eric</column>
		<column><checkbox checked="true"/></column>
	</row>
	<row>
		<column>hunter</column>
		<column><checkbox checked="false"/></column>
	</row>
</list>

Obviously the content for the list can be generate from an array of 
objects with a little ERB sprinked about much like the rest of your 
content is generated by tags with ERB sprinkled throughout.

In addition to it working for all situations you get the following 
additional benefits.

First, my special tags are cross language and cross framework. I have 
used them in Perl, ASP, PHP and many other environments. Some of my tags 
are tightly bound to a particular language or framework but many can be 
used in any system.

Second, some browsers (Mozilla and IE recent versions) support 
processing XSLT on the client side. So you can configure things to just 
send the content and XSLT files and the pure HTML content will be 
generated client-side. This reduces your load on the server and reduces 
your bandwidth (after the first download of your XSLT files it doesn't 
have to download them again if they are used on other pages or the same 
page is displayed again). So basically instead of sending the final 
display you are simply sending the content (a mixture of HTML and your 
own tags) and you tell the client how to make the final display for 
themselves. After the first hit you simply send content. The browser 
already knows how to generate the final output.

Also for backwards compatibility you can process server-side if their 
User-Agent string is an older browser. So there is no backwards 
compatibility issue. You can even use the user-agent string (and other 
info) to generate different implementations of a widget (if they don't 
support javascript then simply send the dropdown lists, but if they do 
then send them the implementation from 
http://www.dynarch.com/projects/calendar/ for a little added pizazz.

I use mod_xslt as an output filter for Apache so it doesn't matter what 
language (Ruby, Perl, PHP) or framework (Rails, PEAR, etc.) I am 
developing in I can use my special tags. Some tags are general purpose 
components but other tags are specific to an application or industry 
domain. But if you wanted a pure Ruby way you could find a XSLT ruby 
library (if there isn't one I would imagine you could bind to libxslt 
easily) and put it as a "after_filter" on ActionController. Put it in 
the ApplicationController and you are set to go.

There are a couple of downside. Unless you go completely server-side and 
use XSLT extensions you cannot interact with server resources very well. 
  I find ERB makes up for this issue fine. Also it takes a while to get 
used to XSLT. It is more functional in concept and if you aren't used to 
that way of programming it can cause some aggravation. Also browser 
support for XSLT is a bit buggy currently so you may have to 
occasionally work around an issue or take things completely server-side.

So this is why I am not using the "Rails way". This is my first Rails 
app so I am still working out the details of using XSLT in Rails but so 
far it seems to work quite good.

Eric
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 187 bytes
Desc: OpenPGP digital signature
Url : http://one.textdrive.com/pipermail/rails/attachments/20050118/1f8739f8/signature.bin


More information about the Rails mailing list