Topic: Polymorphic Forms...

In my application I have two types of user accounts -- "Personal" and "Shared".  Shared accounts are used by group entities such that all the officers in the organization share a username/password.  (I'm tired of them just creating "people" with the firstname='teamname' and lastname='officers".)

I'm using STI, so I have a "User" Model with the email, password, and username fields.  PersonalAccount adds a foreign key to a Person (which gets to firstname, lastname, etc), while SharedAccount just adds a "Display Name". 

So far almost all of the code can deal with User interface exclusively.  However the new account creation process is causing me a few conceptual issues.  I'm using a wizard like process -- step one establishes all the common fields and uses radio buttons to select between the two account types.  Step two then shows either the firstname,lastname combination or the displayname field.  (Of course onces its all working, this is a prime place for an AJAX enchancement to just change what fields are on the form.)

How would you end up structuring the code in the view for the step two?  I've been thinking about using two partials with an if (changing to a case if I ever add a third account type) in the view code.  However that screams non-OOP to me.  A slightly more OOPy approach would be to have the User subclasses respond_to a "partial_for" or similar message, but that seems to add a view dependency to the model which is normally a no-no.  Can anyone help straighten me out here?

(I haven't started tackling the controller yet, but I assume I'll bump into the same types of issues there...)

My RoR journey  -- thoughts on learning RoR and lessons learned in applying TDD and agile practices.

Re: Polymorphic Forms...

NielsenE wrote:

I've been thinking about using two partials with an if (changing to a case if I ever add a third account type) in the view code.  However that screams non-OOP to me.

This seems by far the simplest approach. I don't think you even need partials, you could just do this in the one view (unless it's quite a bit more complicated than this).

<% if @user.kind_of? PersonalAccount %>
  <p>First Name: <%= text_field :user, :first_name %></p>
  <p>Last Name: <%= text_field :user, :last_name %></p>
<% else %>
  <p>Name: <%= text_field :user, :displayname %></p>
<% end %>

You're correct though, this doesn't feel object-oriented, but I think it is the best solution. It is simple and, as you mentioned, the view code doesn't belong in the model.

Edit: If you really want to make this more object oriented, you can create a method in the User like "has_first_and_last_name?" which the two subclasses could override and return the correct response. This way the view isn't changing its behavior based on the class itself, but whether or not it has a first and last name. I don't really see the benefit in this because it just adds complication, but if you are doing this check in multiple places then it might be worth it.

Last edited by ryanb (2006-09-06 22:03:59)

Railscasts - Free Ruby on Rails Screencasts

Re: Polymorphic Forms...

Yeah, I don't really need a partial as I don't think I'll need to re-use those view components, without rather drastic alterations anyways.  However, I thought that the clarity of the resulting partial based code might help keep the slightly unusual view structure understandable, ie

<% form_for :user do |f| -%>
<%= render :partial=>form_partial_for(@user) %>
<%= submit_tag 'Create Account' %>
<% end -%>

I haven't tried partials with form_for yet, so that mght need some tweaking.

form_partial_for(@user) would be a helper method that determines the appropriate partial to render.  Whether its done by kind_of? or duck_typing tests, doesn't really matter, I think I'd do the duck-typing as it seems more railish.

I think sticking it into the helper at least avoids a weird dependency on the view from the model.

The if version with partials, but without the helper would look like:

<% form_for :user do |f| -%>
<% if @user.has_first_and_last_name? %>
  <%= render :partial=>'_new_account_personal' -%>
<% else -%>
  <%= render :partial=>'_new_account_shared' -%>
<% end -%>
<%= submit_tag 'Create Account' %>
<% end -%>

Now comparing your version and these two versions, I think I like the either my first version or your version best, given the current amount of markup.  (If I start adding label elements, or if either form starts to acquire more fields or descriptive/explanative/assistive text, I think the partials would be needed to help illuminate the structure.

My RoR journey  -- thoughts on learning RoR and lessons learned in applying TDD and agile practices.

Re: Polymorphic Forms...

Now another option, of course, would be to restructure the two step process.

Step 1: Only selecting personal or shared.
Step 2. Full form (ie the common partial, plus specific extra fields), on a named view (ie the view isn't generic now)  However this option would probably lead to speciallized controllers for each subclass (possibly RESTful)  (Speaking of REST, has anyone seen any discussion on how REST and subclasses interact with suggested design principles?)

This would probably end up making a controller hierachy that mimics the User model hierarchy -- this starts to smell though.  Especially as its not really an inheritence relationship.  Seems like a Module could be more appropriate, something like

Accounts
Accounts::Base
Accounts::SharedAccountController
Accounts::PersonalAccountController

where both the Controllers can include the Base.

As I think that the shared code is likely to be utility functions, not the actual actions, which if shared would seem to better imply inheritence.

Or am I just babbling now?  (of course this is changing the scope from view (forms) to controllers, but it decisions on one seem to influence the design to some degree)

My RoR journey  -- thoughts on learning RoR and lessons learned in applying TDD and agile practices.

Re: Polymorphic Forms...

I would stick with the simplest thing first, then if you add fields and the view becomes unwieldy you can always move portions of it into a partial.

If you do end up going with a partial, you may want to consider doing this:

<% form_for :user do |f| -%>
  <%= render :partial => "new_#{@user[:type].underscore}", :local => { :f => f } %>
<% end %>

You can then create new_personal_account and new_shared_account partials.

If you do end up doing form_partial_for method, I'd rename it to form_partial_for_user because the form_partial_for is too generic and seems like it can accept many different models.

Railscasts - Free Ruby on Rails Screencasts

Re: Polymorphic Forms...

NielsenE wrote:

Accounts
Accounts::Base
Accounts::SharedAccountController
Accounts::PersonalAccountController

where both the Controllers can include the Base.

Cool idea, but I'm not sure you need that level of abstraction.  The cost of complication seem to outweigh the benefits. I would start simple then if you start to have a lot of case conditions which depend upon the class of the user, consider refactoring into that approach.

Railscasts - Free Ruby on Rails Screencasts