Re-learning The Meaning of ! in Ruby Methods

Hugo Bonacci has been tweeting a lot about the Eloquent Ruby book, so I decided to pick up a copy. In spite in my year or two of working with Ruby, I still feel like a n00b most of the time because I’m still stuck with a C# mentality most of the time. So, I decided to give this book a whirl and hope that I can finally cross the chasm of ruby idioms.


My Misunderstanding Of ! Methods

Early in the book, Russ talks about some basic method naming conventions and idioms, including methods with a ! at the end of them. After reading what he has to say, I’ve realized that my understanding of these methods was slightly off.

I used to think that a method with a ! on it, for example `Array#map!`, meant that the method would modify the object that the method was called on. In the case of the map! method this is true. There’s a non-! version of the method that returns a new array with the changes made in it, but the ! version of map! does modify the array directly.

However, my understanding of the ! convention was not complete and it often left me questioning the use of the symbol in other methods on other objects. Things just didn’t quite match up to my understanding. For example, in Rails and ActiveModel, `save!` doesn’t necessarily change anything in the in-memory object. It saves changes to the underlying data store and throws exceptions if any validation or other errors have occurred. The non-! version does the same work, but instead of throwing exceptions, it stores messages in the .errors collection. This was very puzzling to me. Why would they use a ! method name for something that doesn’t always change the object?


Adjusting My Understanding

Page 13 of the Eloquent Ruby book explains the ! convention in a manner that encapsulates my previous understanding and at the same time, expands what the convention covers so that methods like save! no longer seem odd to me. Here’s what Russ has to say:

Ruby programmers reserve ! to adorn the names of methods that do something unexpected, or perhaps a bit dangerous.

Aha! That explains not only my understanding of the map! method but also the save! method.

In the case of map vs map!, the ! version is dangerous and/or has unexpected behavior and the non-! version is “safe”. The non-! version doesn’t modify anything in the original array. It creates a new array and returns it (this is a very common idiom in itself; do get data from parameters and return a new object with a modified version of the input). The ! version is dangerous because is overwrites the original array’s contents. This may also be considered “unexpected” behavior because it’s not the normal return-a-modified-copy behavior.

In the case of save vs save!, the ! version is unexpected behavior and/or dangerous because it will raise exceptions when validation fails. The non-! version, by contrast, won’t. It will only populate the .errors collection. Calling save! is potentially dangerous because it can crash an app due to throwing exceptions.


Off To A Great Start

I’m glad I made the investment in this book, if even for this one little nugget. I’m also optimistic about the rest of the book, too. If the rest of the book is as useful as the first chapter has been, then I’m sure I’ll be singing it’s praises and blogging more about what I’m learning, soon.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs - the amazingly awesome podcast audio hosting service that everyone should be using, and where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Community, Ruby. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  •  I know what you mean, I come from a Delphi (object pascal) background, and every time I code something in Ruby, I think, “I’m sure there’s a cooler, more rubyesque way to do this… dang it!”

  • One mistake people sometimes make is using ! to mean “this modifies something.” As Donald Knuth pointed out, every function modifies something – if only the realtime clock.

    ! should only be used IMO where there are two versions of a method. In that sense, it’s a great “modifier”. I only wish ? could be used in variable names.

  • Wayne Conrad

    I’ve never agreed with Rails on this one: The one that throws exceptions should be “save”; the one that doesn’t should be “save!”.  Our team, to a man, thinks they are backwards.  Allowing save to fail without raising an exception is the dangerous behavior.

    • I’d love to hear your reasons for this

      • Wayne Conrad

        Hi Derick.  This one bites us because we do a fair amount of ActiveRecord work which is not in response to “user filled in form and clicked on ‘save’” (the scenerio for which save not throwing exceptions is good), but is instead just “the user clicked on a button with no other form elements.”  These actions typically begin a transaction, create/modify a few database rows, and possibly redirect to a different page.  Any ActiveRecord errors that occur are due to a programming error, not something the user did wrong (the user just clicked on a button with no other form elements).  In those cases, which are common in our code, if the programmer called “save” instead of “save!”, then any errors go undetected.  The user has no idea that the operation did not work.  We’ve been bit by this one many times.

    • I used to think this way, but I’ve revised my opinion. The common case for ActiveRecord is updating based on user input. User input is, by nature, prone to error. Failure to save is (usually) because of a validation failure.

      Exceptions are for *unexpected* events. A record failing to save because a user fat-fingered the keyboard or misread the instructions is very far from unexpected. So for that (common) case, an exception is uncalled-for.

      That said, I use the bang versions of AR calls a *lot* anywhere I’m not dealing with user input. If I’m sure of the input, then a failure to save genuinely *is* an unexpected circumstance, and I want to know right away.