5 examples of Ruby’s best usage
Have you ever wondered what we can do with Ruby? Well, the sky is probably the limit, but we are happy to talk about some more or less known cases...
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?
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:
frozenstringliteral: 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.
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/activerecord/relation/findermethods.rb, line 128
def first!
first || raiserecordnotfoundexception!
end
However, the ActiveRecord::FinderMethods#first method that is used above looks like this:
File activerecord/lib/activerecord/relation/findermethods.rb, line 116
def first(limit = nil)
checkreorderdeprecation unless loaded?
if limit
findnthwithlimit(0, limit)
else
findnth 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, insertall 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 insertall! 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.
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.
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.
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.
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.
In conclusion, understanding the concept of bang methods in Ruby software development can greatly enhance your coding skills and bring more clarity to your codebase. Bang methods, denoted by an exclamation point at the end of their names, indicate a destructive or potentially dangerous version of a method. By convention, the bang counterpart of a method should be used with caution as it can modify objects directly, without creating a separate copy. This can be particularly useful when dealing with mutable objects or when performance optimization is a concern. However, it’s important to exercise care when using bang methods, as they can have unintended consequences if not properly handled. It’s also worth noting that not all methods have a bang version, and their presence should be evaluated on a case-by-case basis. With the knowledge of when and how to create bang methods, you can wield this powerful tool effectively and write clean, efficient code that meets your specific requirements.