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”

  1. Pat Maddox Says:

    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.

  2. David Black Says:

    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

  3. Danno Says:

    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.

  4. Aria Says:

    alias :proto :new

    All done!

  5. David Black Says:

    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).

  6. Pat Maddox Says:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    
    class ActiveRecord::Base
      class << self
        def proto
          o = new
          def o.save
            raise "This is just a prototype"
          end
          def o.create
            save
          end
          o
        end
      end
    end
    

    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.

  7. David Black Says:

    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 :-)

  8. Simian Says:

    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

  9. David Black Says:

    For good measure, here’s a reference implementation of what I have in mind—similar to Pat’s though not identical:

    class << ActiveRecord::Base
      def proto
        n = new
        class << n
          undef_method(:save)
          undef_method(:create)
        end
        n
      end
    end

    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”.

  10. Greg Says:

    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
    end
    

    Might as well cache it. No need to create an immutable object more than once.

  11. David Black Says:

    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.

  12. Calamitous Says:

    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?

  13. David Black Says:

    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.

  14. James McCarthy Says:

    I’m all for calling it prototype rather than K-paxian…

    This to me raises 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.

  15. David Black Says:

    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.

Sorry, comments are closed for this article.