Unlocking the Power of ActiveRecord: 10 Odd and Tricky Things You Can Do in Ruby on Rails

December 3, 2024

ActiveRecord is the heart and soul of Rails, providing a powerful and intuitive interface for interacting with databases. While Rails abstracts away much of the SQL complexity, ActiveRecord also provides developers with some neat tricks, features, and capabilities that can make your code more efficient, flexible, and elegant. In this article, we’ll dive into 10 interesting, odd, or tricky things you can do with ActiveRecord to improve your workflow and boost your Rails projects.


1. Use select for Specific Columns to Avoid Unnecessary Data

One of the simplest but most effective optimizations in ActiveRecord is selecting only the columns you need. By default, ActiveRecord returns entire rows from the database, but using select allows you to choose just the necessary columns, reducing memory usage and database load.

# Retrieve specific columns from a model
User.select(:name, :email).where(age: 30)

This is particularly useful when you only need a small subset of columns from a table, such as when displaying a list of users or filtering large datasets.


🚀 Need Expert Ruby on Rails Developers to Elevate Your Project?

Fill out our form! >>


2. Create Dynamic Scopes for Cleaner Queries

ActiveRecord scopes are a great way to make your queries more readable. But did you know you can define dynamic scopes that change based on parameters?

# Defining dynamic scopes
class Product < ApplicationRecord
  scope :recent, -> { where('created_at > ?', 1.month.ago) }
  scope :priced_above, ->(price) { where('price > ?', price) }
end

# Using scopes dynamically
Product.recent.priced_above(100)

Dynamic scopes allow you to chain conditions in a flexible way, making your queries more composable and easier to maintain.

3. Polymorphic Associations for Flexible Relationships

Polymorphic associations are one of Rails’ most powerful features. They allow you to set up a relationship where a model can belong to more than one other model, making your code flexible and reusable.

# Example of a comment model that can belong to either a Post or a Photo
class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end

class Post < ApplicationRecord
  has_many :comments, as: :commentable
end

class Photo < ApplicationRecord
  has_many :comments, as: :commentable
end

With polymorphic associations, you can have a Comment model that can be attached to either a Post or a Photo, providing significant flexibility in your application’s relationships.

4. Execute Raw SQL with find_by_sql

While ActiveRecord provides an elegant ORM, sometimes you may need to bypass the ActiveRecord abstraction and write raw SQL. For that, find_by_sql lets you run custom queries directly and still return model instances.

# Writing a custom SQL query
users = User.find_by_sql('SELECT * FROM users WHERE age > 30 AND active = true')

This is a great option when you need to run complex SQL queries that ActiveRecord doesn’t easily support or when performance is critical.

5. Leverage find_or_create_by for Simplified Record Management

Rails makes it easy to check if a record exists and create it if it doesn’t, using find_or_create_by. However, this method also allows you to add custom logic before the record is saved, which can be useful in many situations.

# Customizing behavior before creation
user = User.find_or_create_by(email: 'newuser@example.com') do |user|
  user.name = 'New User'
  user.password = 'securepassword'
end

This is handy for cases where you want to perform some logic or modification before saving the record to the database.

6. Using includes and references Together for Performance Optimization

When working with associations, you can use includes to eager load associated records and avoid N+1 query problems. But you can also use references to perform conditions on the associated tables within a single query.

# Optimizing joins with `references`
users = User.includes(:posts).where(posts: { published: true }).references(:posts)

This helps you optimize your queries by loading the associated records upfront and filtering based on conditions on those associated tables, all in a single query.

7. Implement Counter Caching for Faster Counts

If you need to maintain a count of associated records (e.g., the number of comments on a post), Rails has a built-in feature called counter cache that automatically updates the count whenever the associated records are created or destroyed.

# Adding a counter cache to an association
class AddPostsCountToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :posts_count, :integer, default: 0
  end
end

# In the User model
class User < ApplicationRecord
  has_many :posts, counter_cache: true
end

With this, you don’t have to manually count the records in the database, saving on performance and simplifying your code.

8. Use Database Functions with ActiveRecord Queries

ActiveRecord gives you the ability to perform SQL functions like LENGTH or LOWER directly in your queries, allowing you to create more complex filtering conditions without leaving Rails.

# Using database functions like LENGTH or LOWER
users = User.where('LENGTH(name) > ?', 10)

You can use any of the database functions available in your SQL dialect directly within ActiveRecord queries, giving you full access to the power of SQL while maintaining the convenience of ActiveRecord.

9. Self-Referential Associations for Hierarchical Data

ActiveRecord allows you to set up models that refer to themselves. This is useful for hierarchical data structures like categories or organizational charts.

# Self-referential association for categories
class Category < ApplicationRecord
  belongs_to :parent, class_name: 'Category', optional: true
  has_many :children, class_name: 'Category', foreign_key: :parent_id
end

This lets you create complex relationships in a natural, recursive way, ideal for representing data with parent-child hierarchies.

10. Use touch to Update updated_at Without Modifying Other Attributes

Rails provides a touch method that lets you update a record’s updated_at timestamp without changing any other attributes. This can be useful for keeping track of when records were last accessed or interacted with.

# Manually updating the updated_at timestamp
user = User.find(1)
user.touch

This method can be useful for auditing purposes, where you need to record the last time a record was touched without actually modifying its content.


Conclusion

ActiveRecord is a powerful and flexible tool in Ruby on Rails, and it offers many lesser-known features that can make your code more efficient, elegant, and readable. From polymorphic associations to counter caches, these features can simplify your code, improve performance, and make your database interactions more intuitive. By mastering these odd and tricky features, you’ll be well-equipped to tackle even the most complex database requirements in your Rails applications.

Leave a comment