More A than R: reconsidering the "new" AR object
December 23rd, 2006
I’ve been noticing that this is becoming a common controller idiom:
def new
@person = Person.new
end
Of course it’s for the sake of the view—specifically, for the benefit of simply_helpful, which lets you do:
<% form_for(@person) ... %>
and will create the right form for you, with the right HTTP method, based on whether @person is a new record or an existing one.
That “new” method, though, bothered me the first time I saw it, and it still does. What I don’t like about it is that it creates a new Person object, while having no intention whatever of saving that object.
Of course there’s no rule that says you have to save AR instances. But doing Person.new just so that you have an object that can answer a couple of questions about itself is overkill. Seeing that code gives me the same feeling I get when I see global variables (or even instance variables) used where a local variable would do. It works, but it’s a loose fit.
It reminds me of, say, walking into a health club and buying a membership so that you can go inside and use their bathroom once. The membership system is in place, and it will serve that purpose, but it wasn’t put in place for that purpose and it’s manifestly a kind of better-than-nothing solution to the problem being solved.
What we’ve got, I think, is the tail (the view) wagging the dog (the controller) a bit too much. I have no problem with the underlying idea of having the form creation be intelligent, but I’m not comfortable with this idiom for doing it.
I’m also willing to bet that someone—a lot of people, I would guess—see this:
@person = Person.new
and have trouble getting a handle on the fact that this has nothing whatever to do with the fact that, at the end of the whole process (new plus create), a new Person will have been created. It’s almost like this new Person is pretending to be THE new Person.
So, what are some alternatives?
I started out by thinking about flipping the whole thing so that the instance variable was no longer at the heart of it. (The docs for form_for do say, after all, “The big news is that form_for allows us to more easily escape the instance variable convention” :-) I ended up kind of reinventing what already exists. Also, Dave Goodlad pointed out that a benefit of querying an actual Person object, even a new one, is that you can pick up default values for attributes (at least those defined via overrides in the model).
My next thought—and this is what I currently favor—is to add a “proto” constructor to ActiveRecord. So instead of Person.new, you would do:
@person = Person.proto
The proto-person would essentially be an unsaveable Person object. Its reason for existence would be exactly the use to which you’re going to put it. When I see Person.new in this context, I always think: this is here because there’s no good way to do this, and creating a new instance is the closest thing available. My reaction to that is: if we’re going to be using a lot of AR objects that are more A than R, let’s design that into the system. The “proto” constructor would be a way to do that.
I also consider it more in keeping with the somewhat anomalous position of the “new” action in the RESTful context. new serves as a kind of table-setter for create. The main event hasn’t started yet; this is just a preliminary step. Viewing it in that context, I think the somewhat anomalous nature of the proto-Person is in fact exactly right.
Criticisms include:
- Get a life :-)
- No one’s going to accidentally save the object, so .new is OK.
- Another constructor is potentially just clutter.
I disagree with all of them, obviously, but I thought I’d list them just to indicate I’m aware of them.
15 Responses to “More A than R: reconsidering the "new" AR object”
Sorry, comments are closed for this article.
December 23rd, 2006 at 08:52 AM
I had to read this several times to figure out what your beef is. It all comes down to “and have trouble getting a handle on the fact that this has nothing whatever to do with the fact that, at the end of the whole process (new plus create)” (I think, anyway).
Someone new to Rails might think that Person created in your new action may somehow correspond to the Person that’s actually saved in your create action.
I suppose it’s a valid concern. I don’t think it’s all that important though, for 2 reasons
1. Person.new is an idiom, and a very simple one at that. It will take all of two seconds for a new Rails user to commit to memory
2. If there’s any source of confusion between the Person objects created in the new and create actions, it’s far more likely to be due to the fact that they’re both named @person. I think most people would intuitively understand that Person.new creates a new person object. The fact that @person is used in the new action, again in the view, and finally again in the create action may lead people to believe that @person refers to the same object throughout.
December 23rd, 2006 at 09:03 AM
The new/create confusion thing is really just speculative. Mostly I just dislike creating ActiveRecord objects that are not, and are not intended to be, records or related to records. Aside from the very minor possibility of a spurious save operation, it’s probably just an aesthetic or semantic thing. But it has to me the flavor of sort of re-purposing the “new” method and the in-memory object. There are no technical obstacles to doing so, but I find it unsatisfying.
I can’t come up with a slam-dunk case,though; what you see here is probably my best shot :-)
David
December 23rd, 2006 at 09:29 AM
Seems to me that what’s desired is for the form presentation to be automatically generated somewhere…
Maybe Person.fields?
I dunno, I’m out of practice with Rails (especially all the new hotness) and I need to get back into it.
December 23rd, 2006 at 09:32 AM
alias :proto :new
All done!
December 23rd, 2006 at 10:12 AM
Well, all done aliasing “new” to “proto”, but that’s not my goal :-) Mainly I’m interested in seeing what other people think about the existing practice and the idea of a different constructor. Implementing it would be close to trivial (even if it created instances that couldn’t be saved).
December 23rd, 2006 at 01:19 PM
Go me.
I don’t have a problem with the existing practice at all. I think if you were to start doing it, or a few others did, then it could be problematic. Anyone looking at your code would wonder what the proto method does, they’d have to look it up…maybe end up asking you what the intent is, despite the fact that it’s easy to figure out the behavior. This is something that would have to become an idiom, and I don’t think there’s a compelling reason for it.
December 23rd, 2006 at 03:06 PM
I totally agree. (Later addendum: not with the “no compelling reason” part, just the other part :-) I’m not going to just start using Person.proto in my code; that would be counterproductive. I’d rather use a common technique, even if I consider it suboptimal, than introduce an idiosyncratic way of doing something like this. After all, the throwaway Person.new does work; I just perceive it as a bit of an awkward fit for the job.
My sense is that there isn’t going to be a big groundswell in favor of an alternative constructor for these “AR – R” (i.e.,memory-only) objects. It’s been an interesting thing to think about, nonetheless; and even disliking a technique is a useful phenomenon since it gets one looking very closely at what’s going on :-)
December 23rd, 2006 at 03:29 PM
RANT:
Dave you heretic! Nothing is worth discussing unless it is officially supported by the rails mullahs.
Creating an AR object just so you can query it and then throw it away is just plain wrong! I don’t care how long it takes for a programmer to understand that the two are different. It just smells bad, and we are all supposed to be polite, until someone in “authority” bothers to acknowledge that there is stuff in rails which smells bad.
Good on ya Dave, Your book is the BEST DAMN book of the lot, and hopefully you’ll blog a little more often :-)
Rampant fan-boyism is hurting the community, not helping it AFAIAC.
/RANT
December 24th, 2006 at 07:01 AM
For good measure, here’s a reference implementation of what I have in mind—similar to Pat’s though not identical:
I think both this and Pat’s version demonstrate my point that it’s “close to trivial” to implement. I emphasize, however, that I have no intention of unilaterally introducing “proto”.
December 24th, 2006 at 03:36 PM
Actually, I’d claim that you want this:
class << ActiveRecord::Base def proto unless @ar_proto @ar_proto = new class << @ar_proto undef_method(:save) undef_method(:create) freeze end @ar_proto.freeze end @ar_proto end endMight as well cache it. No need to create an immutable object more than once.
December 25th, 2006 at 03:25 PM
Hi Greg—
That’s a good point. It actually makes the whole thing feel less cosmetic and more solidly positioned in the underlying logic: a lazily instantiated representative object, which would not be involved in database persistence but would provide a way to query an interface that was the same as objects intended for persistence. It makes a lot of sense to me.
December 26th, 2006 at 06:47 PM
This all sounds interesting, and the proposed implementations look good; the only problem I have is with the name “proto.” It sounds like a space-alien dog food brand.
Maybe you could use something a little more self-descriptive?
December 27th, 2006 at 03:04 AM
I think I set a record by making it through 11 comments on a proposed method without anyone criticizing the name :-)
I can’t really address the dog food point. I guess everything sounds like something silly to someone. Proto comes close to what I intended in connotation, I think. Anyway, it’s sort of moot for now. If I ever write a plugin or patch, I figure I’ll meditate some more on the name.
December 30th, 2006 at 07:01 AM
I’m all for calling it prototype rather than K-paxian…
This
to meraises the question of whether simply_helpful should just accept a class name rather than an instance variable e.g. form_for(“Person”) since you really don’t need an ActiveRecord instance until the form is submitted.December 30th, 2006 at 11:19 AM
The problem, though, is that you do need an instance if it’s an update rather than a create; and simply_helpful is trying to help fold those operations into one.