Ruby and Ruby on Rails: Tips and Tricks #2

Erim Icel
14 min readMay 5, 2023

Welcome back to the second part of Ruby and Ruby on Rails: Tips and Tricks, where we’ll continue exploring some essential tips for working with Ruby on Rails. As we mentioned in the previous post, Ruby on Rails is a popular web framework used to quickly develop web applications. However, as with any programming language, developers can face challenges while working with Ruby on Rails.

Ruby and Ruby on Rails: Tips and Tricks Series

Ruby and Ruby on Rails: Tips and Tricks #1
Ruby and Ruby on Rails: Tips and Tricks #2
Ruby and Ruby on Rails: Tips and Tricks #3
Ruby and Ruby on Rails: Tips and Tricks #4
Ruby and Ruby on Rails: Tips and Tricks #5

Table of Contents

  1. Optimizing Ruby on Rails Performance: How to Eliminate N+1 Queries with .includes()
  2. Efficient Data Management in Ruby on Rails with Batch Inserts and Updates
  3. Know the Differences of Common Ruby Methods (present?, exists?, any?, blank?, empty?, count, size, length)
  4. Exploring the where.missing and where.associated Methods in Ruby on Rails
  5. Exploring Ruby’s alias_attribute Method: Creating Attribute Aliases in Rails
  6. Object#presence in Ruby: Simplify Your Code and Handle Empty Objects with Ease
  7. Convert Any Value Into a boolean With !! Operator

1. Optimizing Ruby on Rails Performance: How to Eliminate N+1 Queries with .includes()

What is the “n + 1” query problem?

The “n + 1” query problem occurs when you have a query that retrieves a set of records, and then you make another query for each of those records to retrieve additional related data. This can result in an excessive number of database queries and significantly slow down your application.

Let’s consider an example. Suppose you have two models, User and Post, with a one-to-many relationship. If you want to retrieve all posts and the user who created them, you might start with the following code:

@posts = Post.all

Then, in your view, you might loop through each post and display its associated user:

<% @posts.each do |post| %>
<h2><%= post.title %></h2>
<p><%= post.content %></p>
<p>Created by <%= post.user.name %></p>
<% end %>

This code will work, but it will result in a separate query for each post’s associated user. So if you have 10 posts, it will make 11 queries (one for all posts and 10 for the associated users). If you have 100 posts, it will make 101 queries.

Post Load (0.5ms)  SELECT "posts".* FROM "posts"
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 4], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 6], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 7], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 8], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 9], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 10], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 11], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 12], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 13], ["LIMIT", 1]]
Rendered posts/index.html.erb within layouts/application (12.4ms)
Completed 200 OK in 35ms (Views: 28.1ms | ActiveRecord: 3.6ms)

How to use includes to eliminate the “n + 1” query problem?

Rails provides includes method, that can help solve the “n + 1” query problem.

The includes method eagerly loads associated data, so you can avoid making additional queries. To use includes, you simply add it to your query, like this:

@posts = Post.includes(:user).all

This code tells Rails to fetch all posts and their associated users in a single query. Then, in your view, you can access the associated user without making additional queries:

<% @posts.each do |post| %>
<h2><%= post.title %></h2>
<p><%= post.content %></p>
<p>Created by <%= post.user.name %></p>
<% end %>

With this code, if you have 10 posts, it will only make two queries (one for all posts and one for their associated users). If you have 100 posts, it will only make two queries.

Post Load (0.5ms)  SELECT "posts".* FROM "posts"
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
Rendered posts/index.html.erb within layouts/application (13.0ms)
Completed 200 OK in 41ms (Views: 29.8ms | ActiveRecord: 1.0ms)

2. Efficient Data Management in Ruby on Rails with Batch Inserts and Updates

Ruby on Rails offers a variety of powerful methods for interacting with the database, including the ability to insert and update data efficiently using the insert_all and upsert_all methods.

insert_all

The insert_all method allows you to insert multiple records into a table with a single query, which can be much more efficient than inserting each record individually. Here's an example of how you might use insert_all:

users = [
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 }
]
User.insert_all(users)

In this example, we’re inserting two records into the users table with a single query. The insert_all method takes an array of hashes as its argument, where each hash represents a record to be inserted.

One important thing to note about insert_all is that it does not instantiate ActiveRecord objects for the inserted records. This means that callbacks, validations, and other ActiveRecord features will not be triggered. If you need these features, you should use the create or new methods instead.

Another thing to keep in mind is that insert_all is not available for all databases. In particular, it is not available for SQLite, which is the default database for Rails applications.

upsert_all

The upsert_all method is similar to insert_all, but it also provides the ability to update existing records if they already exist. This can be useful when you need to insert new records or update existing ones depending on whether they already exist.

Here’s an example of how you might use upsert_all:

users = [
{ name: 'John', age: 32 },
{ name: 'Jane', age: 27 }
]
User.upsert_all(users, unique_by: :name)

In this example, we’re inserting two records into the users table, but we're also specifying that the name column should be used as a unique constraint. This means that if a record already exists with the same name, it will be updated rather than inserted.

One thing to note about upsert_all is that it requires a unique constraint to be specified, which can be a bit more complicated to set up than a regular table. You'll need to create a unique index on the column or columns you want to use as a constraint.

Another thing to keep in mind is that upsert_all is not available for all databases. In particular, it is not available for MySQL, which is a popular database for Rails applications.

Pros and Cons

The main advantage of both insert_all and upsert_all is that they can be much more efficient than inserting or updating records individually. This is because they reduce the overhead of sending multiple queries to the database.

However, there are some trade-offs to consider. One disadvantage of insert_all is that it does not instantiate ActiveRecord objects, which means that you lose some of the functionality provided by ActiveRecord. Another disadvantage of upsert_all is that it requires a unique constraint, which can be difficult to set up in some cases.

Additionally, both methods are not available for all databases, which can limit their usefulness in some situations.

3. Know the differences of common Ruby methods (present?, exists?, any?, blank?, empty?, count, size, length)

.present? vs .exists? vs .any?

These methods check if at least one record is present in the database. Here’s a quick summary of their differences:

  • .present? loads all the records (if not already loaded) and checks if there is at least one record present. This method is generally slow if the record is not yet loaded and produces a ‘SELECT *’ query.
  • .exists? will always query the database to check if at least one record is present. This method is the quickest if you only need to check for existence but can be slow if the record is already loaded. It produces a ‘SELECT 1 LIMIT 1’ query.
  • .any? produces a ‘SELECT COUNT(*)’ query in Rails 4.2 and ‘SELECT 1 LIMIT 1’ in Rails 5.1 and up. It behaves like .present? if records are already loaded and behaves like .exists? if the records aren’t loaded. In Rails 5.1 and up, it generates the same query as .exists?. It is generally recommended to use .any? for newer versions of Rails.

Conclusion:

  • When only checking for existence, always use .exists?.
  • If the record is already loaded or when you would be using the result, use .present?.
  • In Rails 4.2 and below, there is not much of a use case for .any?

.blank? vs .empty?

These methods check if a variable is .blank? or .empty?. Here’s a quick summary of their differences:

  • .blank? is the negation of .present?. The same rule applies as .present?. Use .blank? only if the record is already loaded.
  • .empty? behaves like .any?. It produces a ‘SELECT COUNT(*)’ query in Rails 4.2 and ‘SELECT 1 LIMIT 1’ in Rails 5.1 and up.

Conclusion:

  • In Rails 4.2 and below, negate the .exists? if you want to check for existence. For newer versions, you can use .empty?.
  • As with .present?, use .blank? only if the record is already loaded.

.count vs .size vs .length

These methods are used to get the count, size, or length of an array. Here’s a quick summary of their differences:

  • .length loads the association into memory if not yet loaded. It behaves much like .present?.
  • .count will always generate a COUNT SQL query. It has a similar behavior to .exists?.
  • .size behaves like .any?. It behaves like .length if the array is already loaded. Otherwise, it defers to using a COUNT query.

Conclusion:

  • It is generally recommended to use .size.

By understanding the differences between common Ruby methods, you can write more efficient code and avoid unnecessary database queries.

4. Exploring the where.missing and where.associated Methods in Ruby on Rails

In Ruby on Rails, ActiveRecord is an ORM (Object-Relational Mapping) library that allows developers to interact with a database using Ruby code instead of SQL. One of the most commonly used methods in ActiveRecord is where, which is used to query a specific set of records from a database table. In this article, we'll be discussing two useful variations of the where method in Rails: where.missing and where.associated.

where.missing

where.missing is a method that allows you to find records where a specific association is missing. For example, if you have a User model that has_many Post models, and you want to find all users who haven't posted anything yet, you can use the where.missing method like so:

users_without_posts = User.where.missing(:posts)

The above code will generate the following SQL query:

SELECT "users".* FROM "users"
WHERE (NOT (EXISTS (SELECT 1 AS one FROM "posts" WHERE "posts"."user_id" = "users"."id")))

The SQL query essentially finds all users where there isn't a record in the posts table with a matching user_id.

where.associated

where.associated is a method that allows you to find records based on a condition on their associated models. For example, if you have a User model that has_many Post models, and you want to find all posts that have a title containing the word "Rails" and are associated with a user who lives in New York, you can use the where.associated method like so:

Post.where('title LIKE ?', '%Rails%').where.associated(:user, city: 'New York')

The above code will generate the following SQL query:

SELECT "posts".* FROM "posts"
INNER JOIN "users" ON "users"."id" = "posts"."user_id"
WHERE (title LIKE '%Rails%')
AND ("users"."city" = 'New York')

The SQL query joins the posts table with the users table and filters the result set based on the conditions specified in the where clause.

In conclusion, the where.missing and where.associated methods are powerful variations of the where method in Ruby on Rails that allow developers to write complex queries in a more intuitive and readable way. Understanding these methods can help you write more efficient and maintainable code.

5. Exploring Ruby’s alias_attribute Method: Creating Attribute Aliases in Rails

In Ruby, we often use instance variables to store and access data within a class. However, sometimes we want to provide alternative names or aliases for those instance variables, without having to write additional methods to access them. This is where the alias_attribute method comes in handy.

What is alias_attribute?

alias_attribute is a method in Ruby that allows you to create aliases for attribute accessors in your models. It provides an easy way to define alternative names for instance variables, without having to write additional methods or modify the underlying database schema.

The syntax for using alias_attribute is:

alias_attribute :new_name, :old_name

Here, new_name is the new attribute name you want to create, and old_name is the existing attribute name that you want to alias. When you call new_name on an instance of the model, it will return the value of old_name.

Let’s see some examples of how alias_attribute can be used.

Example 1: Aliasing database columns

Suppose you have a User model with a first_name attribute, but you want to provide an alternative name name that also returns the value of first_name. You can achieve this using alias_attribute:

class User < ApplicationRecord
alias_attribute :name, :first_name
end

Now, you can call user.name instead of user.first_name:

user = User.first
puts user.name
# Outputs the value of user.first_name

Example 2: Aliasing instance variables

You can also use alias_attribute to create aliases for instance variables that are not backed by database columns. For example, suppose you have a Person class with an instance variable full_name, but you want to provide an alternative name name that also returns the value of full_name. You can achieve this using alias_attribute:

class Person
attr_accessor :full_name
alias_attribute :name, :full_name
end

Now, you can call person.name instead of person.full_name:

person = Person.new
person.full_name = "John Doe"
puts person.name
# Outputs the value of person.full_name

Example 3: Aliasing namespaced attributes

If you have namespaced attributes in your model, you can use alias_attribute to create aliases for them too. For example, suppose you have a User model with an attribute profile_image_url, but you want to provide an alternative name image_url that also returns the value of profile_image_url. You can achieve this using alias_attribute:

class User < ApplicationRecord
alias_attribute :image_url, :profile_image_url
end

Now, you can call user.image_url instead of user.profile_image_url:

user = User.first
puts user.image_url
# Outputs the value of user.profile_image_url

6. Object#presence in Ruby: Simplify Your Code and Handle Empty Objects with Ease

Object#presence is a Ruby method that can be called on any object, and is commonly used in Rails applications. The purpose of this method is to determine whether an object is considered "present" or "non-empty", and return the object itself if it is, or nil otherwise.

What is Object#presence

The Object#presence method is defined as follows:

def presence
self if present?
end

Here, self refers to the object that is calling the method. The present? method is defined on the object's class, and returns true if the object is not empty or nil.

Why use object.presence instead of ‘object.present? ? object : nil’

One reason to use object.presence instead of the ternary expression object.present? ? object : nil is that it is more concise and easier to read. The ternary expression can become cumbersome when used repeatedly, and can make code more difficult to understand.

Another reason to use object.presence is that it is a part of the Rails framework, and is therefore already available for use. It also has the added benefit of being defined on all objects that respond to present?, which includes strings, arrays, hashes, and custom objects. This allows for a consistent approach to checking for the presence of an object, regardless of its type.

Example 1: Checking for an empty string

Suppose you have a string that may be empty, and you want to handle it in a concise way. You can use presence to return the string if it is not empty, or nil if it is:

string = ""
result = string.presence
puts result
# Outputs nil

If the string is not empty, presence will return the string:

string = "hello"
result = string.presence
puts result
# Outputs "hello"

Example 2: Checking for an empty array

Suppose you have an array that may be empty, and you want to handle it in a concise way. You can use presence to return the array if it is not empty, or nil if it is:

array = []
result = array.presence
puts result
# Outputs nil

If the array is not empty, presence will return the array:

array = [1, 2, 3]
result = array.presence
puts result
# Outputs [1, 2, 3]

Example 3: Checking for a nil value

Suppose you have a value that may be nil, and you want to handle it in a concise way. You can use presence to return the value if it is not nil, or nil if it is:

value = nil
result = value.presence
puts result
# Outputs nil

If the value is not nil, presence will return the value:

value = "hello"
result = value.presence
puts result
# Outputs "hello"

By using Object#presence, you can simplify your code, make it more readable, and handle empty objects with ease.

7. Convert any value into a boolean with !! operator

In Ruby, the boolean data type is a fundamental part of the language. A boolean can have one of two possible values, either true or false. In some cases, you may need to convert a value into a boolean, particularly when working with user input or data from external sources. Fortunately, Ruby provides a simple and effective way to convert any value into a boolean.

The !! operator

The most common way to convert a value into a boolean in Ruby is to use the double negation operator !!. This operator takes any value and returns its boolean equivalent. The !! operator is essentially shorthand for converting a value to a boolean using an if statement.

Here’s an example of using the !! operator to convert a string to a boolean:

value = "true"
boolean_value = !!value
puts boolean_value
# Outputs true

In this example, the string “true” is converted into a boolean value of true.

Here’s another example of using the !! operator to convert an integer to a boolean:

value = 0
boolean_value = !!value
puts boolean_value
# Outputs true

In this example, the integer value of 0 is converted into a boolean value of false, and then negated twice using !!, resulting in a boolean value of true.

Truthiness and Falsiness

In Ruby, the boolean value of false is the only "falsey" value. All other values are considered "truthy", meaning they are treated as true in boolean expressions. This means that the !! operator can be used to convert any truthy value into true, and any falsey value into false.

Here are some examples of truthy and falsey values in Ruby:

puts !!0     # true
puts !!1 # true
puts !!nil # false
puts !!"" # true
puts !!"abc" # true
puts !![] # true
puts !!{} # true

Customizing Truthiness and Falsiness

In some cases, you may need to customize the truthiness or falsiness of a value. For example, you may have an object that represents a database record, and you want to treat it as “falsey” if it has no associated data.

To do this, you can define the to_bool method on the object, which returns true or false depending on the state of the object. Here's an example:

class Record
def initialize(data)
@data = data
end

def to_bool
!@data.nil?
end
end

record1 = Record.new("data")
record2 = Record.new(nil)
puts !!record1 # true
puts !!record2 # false

In this example, the Record class defines a to_bool method that returns true if the @data instance variable is not nil, and false otherwise. When the !! operator is applied to a Record object, it calls the to_bool method to determine its boolean value.

Previous: Ruby and Ruby on Rails: Tips and Tricks #1
Next: Ruby and Ruby on Rails: Tips and Tricks #3

Erim Icel
LinkedInTwitterGithub

--

--