Getting form_for() to work with non-ActiveRecord models

I ran into a bit of strangeness when trying to get RESTful forms working with non-ActiveRecord models — in this case, models that I was using to represent data stored in Redis. It turned out to not be straightforward. I wanted to create a simple form for creating a new object. Because I created things using the generators, I had a skeleton page already (even if I’m not using ActiveRecord it’s still a bit of a time-saver):

mymodels/new.html.erb:

<h1>New Thing</h1>

<% form_for(@mymodel) do |f| %>
    <%= f.error_messages %>
    <p>
      <%= f.label :name %><br/>
      <%= f.text_field :name %>
    </p>
    <p>
      <%= f.label :a_link %><br/>
      <%= f.text_field :a_link %>
    </p>
    <p>
      <%= f.submit 'Create' %>
    </p>
<% end %>

Pretty ordinary. What happens with this, though, is that when you load the page, it calls the controller’s new() method first. The page expects that method to pass in an instance of the model, @mymodel. The form_for() call then ends up introspecting that instance to determine what to do with it. When the form is submitted, then the controller’s create() method is called…or is it? Actually, if form_for() determines that the instance is brand-new, then yes, create() is called. Otherwise, the controller’s update() method is invoked.

This is what was throwing me off. I put things together, and discovered that update() was being invoked instead of create. Why? Well, I ended up having to dig into the Rails source code to find out, specifically in the apply_form_for_options() method. The trick turns out that it really wants to call a method on the model called new_record?() to determine whether or not the instance is brand new. If the model doesn’t respond to that method, or if it returns false, then form_for() decides that this must be an existing instance, so it sets up the form to invoke the update() action.

Once figured out, it was easy enough. I added to my model:

  def new_record?
    (id.nil? || (id == 0))
  end

And that fixed the problem. If my instance has a nil or zero id, then it’s new. I do have to point out that while this form_for() behavior is perhaps convenient for coding, it’s not a very efficient thing to do. The side effect is that every time the form is loaded a model instance is created, which is then thrown away after the page is delivered; clearly, that’s a waste. It would make more logical sense to allow form_for() to operate on a nil object, treating it as obviously new.

In any case, hopefully this info will help save someone else some time.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: