back

When to use a bang (exclamation point) after Rails methods

-

Update:

A bang can used in the below ways, in order of my personal preference.

1) An active record method raises an error if the method does not do what it says it will. (e.g. create!)
2) An active record method saves the record or a method saves an object (e.g. strip!)
3) A method does something “extra”, like posts to someplace, or does some action.
4) other things

The point is: only use a bang when you’ve really thought about whether it’s necessary, to save other developers the annoyance of having to check why you are using a bang.

-

When writing methods for active record models, bangs should only come when a) something is being changed in the database or b) calling the method will raise an error if the record is not valid, or if no records are returned (e.g. find! / create!)

When to use a bang:

1
2
3
4
5
6
7
# obviously we would write this better in prod, but the point is to show the point
class AModel < ActiveRecord::Base
  def hit!
    hits += 1
    save
  end
end

When not to use a bang:

1
2
3
def modify_something
  self.hits += 1
end

In the first method, we are saving the object, in the second we will have to save it if we want the change to actually happen in the db.

The bang provides two cues to other developers.

1) that it’s not necessary to save the object after calling the method.

2) when you call the method, the db is going to be changed.

If bangs are used in other situations, the going gets rough, because it’s unclear what is being saved in what context.

Obviously, this is more a design decision than a hard rule, but I’ve found that it works well for me.

February 11, 2009

  1. Trevor Turk says:

    I also use the bang when the method is doing something "extra" - which is a pretty loose thing. For example, with Machinist, you might do this:

    def login!(options = {})
    user = User.make(options)
    login_as(user)
    user
    end

    That allows this kind of thing in your tests:

    user = login!

    ...or just:

    login!

    ...at the top of a test.

    You can also do:

    login!(:admin => true)

    ...to log in as an admin.

    So, that creates the user and logs them in. The bang just helps to keep the method short while also indicating that there's something extra going on.

    hehe - I should really do a blog post about this...

    P.S. your OpenID sign-in isn't working for me.

  2. Trevor Turk says:

    I did a little write-up about this:

    http://almosteffortless.com/2009/02/12/the-login-test-helper-for-restful-authentication-and-machinist/

    My coworker talked about it with me, and he made a good point. This bang seems appropriate in this case because it's changing the context and state.

  3. Piers Cawley says:

    I take a cue from the bang methods in the rails API and generally have them throw an exception if things go wrong as well. So hit! would be implemented as:

    1
    2
    3
    4
    
    def hit!
      hits += 1
      save!
    end
    

    The other guideline I work by is that finder methods with bangs will throw an exception if they don't find anything, but that's now in Rails core so I can get rid of a bunch of custom made finders. Hurrah.