Software Development
Piotr Komorowski
Piotr Komorowski
Software Engineer
2021-05-28

Ruby software development. What are the bang methods and when to create them?

Before we start creating a bang method, let's learn what exactly this type of method is and what its characteristics are. Let's start with the fact that there is no unambiguous definition of these types of methods. Simply put, a bang method is a method with an exclamation mark at the end.

We may often come across a statement that the bang method is, in a sense, a dangerous method and the exclamation mark at the end of its definition tells us to be vigilant when using this method. Let us see: what is this threat exactly when it comes to these methods and what are their characteristics?

Characteristics of bang methods

1. The bang method changes the recipient

One of the most popular characteristics of these types of methods is that they usually change their audience. Let's take a look at the map! method as an example. According to the documentation, the map! method invokes the given block once for each element of self, replacing the element with the value returned by the block and if no block is given, an Enumerator is returned instead.

arry = [1, 2, 3, 4, 5]
arry.object_id                  => 280 
arry.map! {|num| num**num }     => [1, 4, 27, 256, 3125] 
arry.map!                       => #<Enumerator: [1, 2, 3, 4, 5]:map!> 
arry.map! {|n| n }              => [1, 4, 27, 256, 3125] 
arry.object_id                  => 280

As we can see, object_id remains unchanged. So, the danger of using such a method seems obvious. If in our code we used the variable arry anywhere else, then the map! will change it. This can lead to our program operating in an undesirable manner or even crashing.

There are objects in Ruby that cannot be changed, such as instances of the Integer, Float, and Symbol classes. While working on a project, you could also meet the so-called magic-comment which go like this:

# frozen_string_literal: true

Attempting to change the String recipient in the code where such a comment was used would result in an error like this:

'abc'.upcase!
FrozenError (can't modify frozen String)

Interestingly, there were plans to introduce unchangeable String in Ruby 3.0, but it was decided not to make this change. However, it should be remembered that not all bang methods change the recipient, so you should always check in the documentation what kind of danger you should expect in the case of a particular method.

Get free code review

2. The bang method raises an exception

Another distinctive feature of these methods is that, for many of them, an exception is raised. An example of such a method is the ActiveRecord::FinderMethods#first! According to the documentation, the first! method is same as the first but raises ActiveRecord::RecordNotFound if no record is found. Note that first! accepts no arguments.

# File activerecord/lib/active_record/relation/finder_methods.rb, line 128
def first!
  first || raise_record_not_found_exception!
end

However, the ActiveRecord::FinderMethods#first method that is used above looks like this:

# File activerecord/lib/active_record/relation/finder_methods.rb, line 116
def first(limit = nil)
  check_reorder_deprecation unless loaded?

  if limit
    find_nth_with_limit(0, limit)
  else
    find_nth 0
  end
end

Thank so the above examples, we see the danger of using the former. If we use ActiveRecord::FinderMethods#find! and it will not find a suitable record in the database, then it will return ActiveRecord::RecordNotFound, which may cause our program to stop working.

However, we must remember that this does not mean that if a method does not have an exclamation mark at the end, it is safe and will not raise the exception or change the recipient.

A new pair of methods was introduced in Ruby on Rails 6.0: ActiveRecord::Persistence::ClassMethods#insert_all and ActiveRecord::Persistence::ClassMethods#insert_all!

The first of these methods already shows a certain degree of danger. Well, according to the documentation, insert_all inserts multiple records into the database in a single SQL INSERT statement. It does not instantiate any models nor does it trigger ActiveRecord callbacks or validations. However, the insert_all! additionally raises ActiveRecord::RecordNotUnique if any rows violate a unique index on the table. In that case, no rows are inserted. This means that the first of these methods will save all records except those that violate the unique index, while the second (more dangerous) method will raise an exception and will not save any of the records to the database.

3. The bang method has an equivalent without an exclamation mark

Many methods, even though they do not have an exclamation mark at the end, are dangerous to use. For example, ActiveRecord::FinderMethods#find. According to the documentation If one or more records cannot be found for the requested ids, then ActiveRecord::RecordNotFound will be raised.

We can see that, although this method has the same degree of danger as ActiveRecord::FinderMethods#first!, it does not have an exclamation mark.

The same is true when it comes to modifying the recipient. For example, Array.delete (as documented) deletes all items from the self that are equal to object and returns the last deleted item, or nil if no matching items are found.

a = [1, 2, 3, 4, 5]
a.object_id                     #=> 320
a.delete(2)                     #=> 2
a                               #=> [1, 3, 4, 5]
a.delete(6)                     #=> nil
a.object_id                     #=> 320

You can clearly see that the object has been modified. What the two methods have in common is that they do not have an exclamation mark equivalent. Therefore, if the method we wish to create would have the kind of dangers I mentioned above, it does not need to immediately have an exclamation mark at the end. Moreover, it is advisable to create a bang method if there is already a method with the same name that is less hazardous than the method you want to create.

When to use bang methods

You can wonder why use these dangerous methods at all, especially since we usually have less dangerous counterparts. One of the advantages can be, for example, improved code performance thanks to reducing the number of objects created. Here you can read more about increasing Rails performance.

When it comes to raising exceptions, using the create method instead of the dangerous create! may lead to a situation where an error in our application is difficult to detect because the records will not be written into the database. So, if we are sure that the parameters we pass to this method are correct, we should use the create! method, which will raise an exception if, for some reason, the record is not saved in the database.

The conclusion is simple, and it goes as follows: prudent use of such methods can improve the performance and quality of our code. However, we must always remember that their inappropriate use can stop the code from running properly.

When to create your own bang method

If you want to build your own dangerous method, you must go through a certain decision-making process. First, the question is whether there already is a method with the same name as the one you want to create but less dangerous. You can also consider creating a pair of methods in which one of them is equivalent to the other, only more dangerous.

There is no point in creating a bang method if its counterpart without an exclamation mark does not exist. There are methods that are dangerous because they change the object or raise an exception, and yet they don't have an exclamation mark at the end of the name. Creating a bang method with no equivalent would confuse a programmer using your code. This person would not be able to say what the danger of this method is and why it deserves an exclamation mark at the end of its name.

Secondly, we should consider what kind of dangers we are talking about. From the examples above, we can conclude that the most common type of danger is the change of the object itself (instead of creating its copy) or raising an exception. Of course, this is not a rule but rather a result of analyzing the methods that have been defined in Ruby and Ruby on Rails. For example, we might consider the implementation of a method pair in which the method without the exclamation mark would save the record in the database, while the method with the exclamation mark would skip the validation of this record. In this case, it is clear where the potential danger of using such methods lies.

Summary

A summary of the knowledge of bang methods points to these conclusions: the 1st method with the exclamation mark has a less threatening counterpart without an exclamation mark, second, the method with an exclamation mark performs a dangerous action, e.g., modifies the recipient or raises an exception.

Product development consulting

Read more:

Pros and cons of Ruby software development

Rails API & CORS. A dash of consciousness

Ruby 3.0. Ruby and lesser known privacy control methods