Jeremy Sklarsky

The World Wide Web is My Oyster.

Get Informed About Form_for Formed Forms

Over the weekend, in building a super-fun, and very simple one page app called Am I Ruby? with Kate, Rachel, and Sophie, we stumbled upon a most curious feature of Rails.

Embracing object oriented design to the best of our abilities, we attempted to implement the so-called “fat mode, skinny controller” principle. Simply, most of the application specific logic should be in a model and the controller should serve only as a traffic cop routing information to and from views.

Seeing as this was a simple one page and no immediate need for persistence, our search model was a tableless. In other words, it was a plain old Ruby class.

After much of our application was built, we set up our controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class SearchesController < ApplicationController

  def index
    @search = Search.new
  end

  def show
  end

  def create

    @result = Search.new.am_i_ruby(search_params)
    respond_to do |f|
      f.html
      f.js
    end
  end

  private

  def search_params
    params[:search][:keyword]
  end

end

The controller would define a new Search model in order to serve up an object to the form_for @search in our main page’s view.

1
2
3
4
<%= form_for(@search, remote: true) do |f| %>
  <%= f.text_field :keyword, id: 'keyword' %>
  <%=f.submit "Search"%>
<% end %>

We were feeling pretty good…until we saw this: undefined method 'model_name' for #<Search:0x007fbc18f959d0>. OUCH!! Especially since we never even wrote a method model_name…what gives?

A quick glance at the trace shows us this:

Actionpack, activeview, activesupport…a lot of things I kind of recognize but am not super familiar about. About halfway down this huge trace I finally see one I do know about for sure: activerecord. Since our Search class was a tableless class, we never told it to inherit from ActiveRecord. So at some point, form_for tried to call model_name on a class that doesn’t have access to that method. Let’s assume that has something to do with ActiveRecord.

Model Name

As it turns out, rails form helpers rely heavily on ActiveRecord. It’s not magic: at some point rails has to introspect on the object and be able to know what controller and action to send the submitted data and how to format the params hash. That’s really it.

model_name is a part of the ActiveModel module that helps rails with its naming conventions, magically pluralizing and singularizing our model names at will. How do we fix this? We have to give our class access to this method. We search for it and find where it lives in the rails source code.

1
2
3
class Search
  extend ActiveModel::Naming
end

Refresh the page and we get a new error: undefined method 'to_key' for #<Search:0x007fbc1849fcb0>. to_key is also an activemodel method but not in the Name class, its in the Conversion class. What it does is return an array of all the object’s attributes as keys. We’re going to assume that those will later be used in form_for’s creation of the params hash.

1
2
3
4
class Search
  extend ActiveModel::Naming
  include ActiveModel::Conversion
end

Refresh again, and we get another error: undefined method 'persisted?'. We know the drill by now: persisted? is an activerecord method that returns a boolean that tells us whether or not an object has been persisted to the database. At this point we have a couple of options.

We can define persisted? and basically be done. As long as the Search class has a defined attribute for every field in the form, the page will load error free.

1
2
3
4
5
6
7
8
9
10
class Search

  extend ActiveModel::Naming
  include ActiveModel::Conversion

  def persisted?
    false
  end

end

That’s not too bad. But I have an inquiring mind and want to keep pushing this further.

We can keep going down the rabbit hole of including/extending modules based on the error messages we find.

1
2
3
4
5
6
7
8
9
class Search

  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveRecord::Persistence
  include ActiveRecord::Core
  extend ActiveRecord::ModelSchema::ClassMethods

end

But since I’ve now added 5 modules, 3 of which are a part of ActiveRecord and its well after midnight and there’s no end in sight, I’m starting to get really tired of this. I have this feeling that all roads will at some point lead to ActiveRecord::Base.

When we look at the activerecord source code, we find that require 'active_model' is literally the third line. In fact, Base has no code of its own - it just requires, includes, and extends nearly all the other Activemodules in the Rails source code. So why not just make the class inherit from ActiveRecord and cover all our (ActiveRecord) bases?

1
2
class Song < ActiveRecord::Base
end

So even though our model is not going to talk to a database, we can still give it all the functionality of ‘tabled’ class that allows form_for to do its thing. But, if you’re afraid this might confuse another developer (or future you), this is all you need to add to your class to allow it to interact with form_for:

1
2
3
4
5
6
7
8
9
10
class Search

  extend ActiveModel::Naming
  include ActiveModel::Conversion

  def persisted?
    false
  end

end