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
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
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
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.
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
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
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
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
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
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?
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
1 2 3 4 5 6 7 8 9 10