Ruby is a powerful and elegant programming language, known for its readability and expressiveness. Optimised for developer happiness. One of the more unique aspects of Ruby is its "bang"(!) methods. As a newcomer to Ruby, you might find yourself wondering about the purpose of these methods and how to use them. In this article, we will explore Ruby's bang methods, the naming convention, and their use in a Ruby on Rails project.
What are Bang Methods?
In Ruby, bang methods are simply methods that have an exclamation mark (!) at the end of their names. The bang is a naming convention to signify that the method has some potentially surprising or dangerous behaviour compared to its non-bang counterpart.
Mutating bang methods
Generally, bang methods mutate/modify the object they are called upon, while their non-bang counterparts return a new object with the desired changes.
Here are a few examples of bang and non-bang method pairs in Ruby:
String#upcase
andString#upcase!
Array#sort
andArray#sort!
Array#uniq
andArray#uniq!
Let's take a closer look at these examples to understand the differences between bang and non-bang methods.
Example 1: String#upcase and String#upcase!
String#upcase
returns a new string with all characters in uppercase, leaving the original string unchanged. In contrast, String#upcase!
modifies the original string in-place, converting all characters to uppercase.
text = "hello, world!"
uppercase_text = text.upcase
puts uppercase_text # => "HELLO, WORLD!"
puts text # => "hello, world!"
text.upcase!
puts text # => "HELLO, WORLD!"
Example 2: Array#sort and Array#sort!
Array#sort
returns a new array with its elements sorted, while Array#sort!
sorts the original array in-place.
numbers = [5, 3, 1, 4, 2]
sorted_numbers = numbers.sort
puts sorted_numbers.inspect # => [1, 2, 3, 4, 5]
puts numbers.inspect # => [5, 3, 1, 4, 2]
numbers.sort!
puts numbers.inspect # => [1, 2, 3, 4, 5]
Example 3: Array#uniq and Array#uniq!
Array#uniq
returns a new array with duplicate elements removed, while Array#uniq!
removes duplicate elements from the original array in-place.
repeated_numbers = [1, 2, 2, 3, 3, 3]
unique_numbers = repeated_numbers.uniq
puts unique_numbers.inspect # => [1, 2, 3]
puts repeated_numbers.inspect # => [1, 2, 2, 3, 3, 3]
repeated_numbers.uniq!
puts repeated_numbers.inspect # => [1, 2, 3]
Error-raising bang methods
In a Ruby application, bang methods are also prevalent for denoting error-raising methods. You can find common use cases in a library such as ActiveRecord, the built-in Object-Relational Mapping (ORM) framework for Ruby on Rails that handles database operations. Some of the most common ActiveRecord bang methods include:
ActiveRecord::Base#save
andActiveRecord::Base#save!
ActiveRecord::Base#create
andActiveRecord::Base#create!
ActiveRecord::Base#update
andActiveRecord::Base#update!
ActiveRecord::Base#destroy
andActiveRecord::Base#destroy!
Let's examine these ActiveRecord examples to understand their usage and differences in a Ruby on Rails application.
Example 1: ActiveRecord::Base#save and ActiveRecord::Base#save!
save
and save!
are instance methods used to persist an ActiveRecord object to the database. The non-bang method, save
, returns a boolean value indicating whether the record was saved successfully or not. If there are any validation errors, it returns false.
On the other hand, save!
raises an ActiveRecord::RecordInvalid
exception if the record is invalid, halting the execution flow. This method is useful when you want to ensure the record is valid and saved; otherwise, an exception should be raised.
class User < ActiveRecord::Base
validates :email, presence: true
end
user = User.new(email: "")
# Using save
if user.save
puts "User saved!"
else
puts "User not saved. Errors: #{user.errors.full_messages.join(', ')}"
end
# Using save!
begin
user.save!
puts "User saved!"
rescue ActiveRecord::RecordInvalid => e
puts "User not saved. Errors: #{e.record.errors.full_messages.join(', ')}"
end
Example 2: ActiveRecord::Base#create and ActiveRecord::Base#create!
create
and create!
are class methods used to create and save a new ActiveRecord object to the database in a single step. Similar to save
, create
returns the newly created object, whether it's valid or not. If the object is invalid, it won't be persisted to the database.
In contrast, create!
raises an ActiveRecord::RecordInvalid
exception if the object is invalid, making it suitable for situations where you want to ensure the record is valid and created; otherwise, an exception should be raised.
# Using create
user = User.create(email: "")
if user.persisted?
puts "User created!"
else
puts "User not created. Errors: #{user.errors.full_messages.join(', ')}"
end
# Using create!
begin
user = User.create!(email: "")
puts "User created!"
rescue ActiveRecord::RecordInvalid => e
puts "User not created. Errors: #{e.record.errors.full_messages.join(', ')}"
end
Example 3: ActiveRecord::Base#update and ActiveRecord::Base#update!
update
and update!
are instance methods used to update the attributes of an ActiveRecord object and save it to the database. update
returns a boolean value, indicating whether the record was updated successfully or not. If there are any validation errors, it returns false.
update!
, like save!
and create!
, raises an ActiveRecord::RecordInvalid
exception if the record is invalid, making it suitable for situations where you want to ensure the record is valid and updated; otherwise, an exception should be raised.
Example 4: ActiveRecord::Base#destroy and ActiveRecord::Base#destroy!
destroy
and destroy!
are instance methods used to delete an ActiveRecord object from the database. The non-bang method, destroy
, returns the destroyed object if the deletion is successful. If the object has any before_destroy
callbacks that halt the destruction process, it returns false.
In contrast, destroy!
raises an ActiveRecord::RecordNotDestroyed
exception if the object cannot be destroyed due to any before_destroy
callbacks. This method is useful when you want to ensure the record is deleted; otherwise, an exception should be raised.
class User < ApplicationRecord
before_destroy :check_admin
private
def check_admin
throw :abort if email == "admin@example.com"
end
end
user = User.create(email: "admin@example.com")
# Using destroy
if user.destroy
puts "User destroyed!"
else
puts "User not destroyed."
end
# Using destroy!
begin
user.destroy!
puts "User destroyed!"
rescue ActiveRecord::RecordNotDestroyed => e
puts "User not destroyed."
end
In this example, the User
model has a before_destroy
callback that prevents the deletion of a user with the email "admin@example.com". When using destroy
, the method returns false if the user cannot be deleted. However, when using destroy!
, an ActiveRecord::RecordNotDestroyed
exception is raised if the user cannot be deleted.
Conclusion
Understanding the differences between bang and non-bang methods in Ruby can empower you to write more efficient and maintainable code. Bang methods are more aggressive, often modifying objects in place or raising exceptions when operations fail. Non-bang methods are more lenient, returning modified copies of objects or allowing for flexible error handling. Knowing when to use each type of method can be useful in creating robust and reliable applications.
So, the next time you come across a bang method in Ruby, don't be afraid to use it but also be aware of its potential side effects. Happy coding!
🧑🏾🚀🚀
Top comments (0)