Rails ActiveRecord contains: A Powerful Tool for Efficient Queries

Rails ActiveRecord contains

In the Rails world, ActiveRecord is the ORM (Object-Relational Mapping) library that connects Ruby objects to database tables. Among its many features, the contains method (though not an official ActiveRecord method but often used as part of conditions) allows developers to retrieve data by specifying if a value is included in an array or a string in a database column. Let’s dive into how contains can simplify querying, make your Rails app more efficient, and keep your code clean and readable.

What is ActiveRecord contains?

ActiveRecord provides an interface for interacting with databases. While contains isn’t an official method, it represents a pattern used in querying arrays and string data. By using contains, developers can perform a search on whether a certain value is part of an array or substring. This can be particularly useful when dealing with data types that store multiple values, like PostgreSQL’s array or JSON fields.

Why Use contains with ActiveRecord?

Using contains in your ActiveRecord queries brings several benefits:

  • Efficient Data Retrieval: Instead of looping through records in Ruby, contains lets the database perform the work, reducing data retrieval time.
  • Readable Code: Writing queries with contains can make your code more understandable, as it reflects exactly what you’re searching for in your data.
  • Optimized Performance: Offloading work to the database can improve app performance, especially with large datasets.

How to Use contains with Different Database Types

In Rails, contains querying may differ depending on the database you’re using. Let’s look at some examples for PostgreSQL and MySQL, two popular databases.

PostgreSQL Array Contains Query

If you’re using PostgreSQL, you can work with arrays directly in the database. The @> operator in PostgreSQL allows you to check if an array contains a specific value.

Example: Imagine you have a User model with a column roles, which stores an array of roles for each user.

# Migration for PostgreSQL to add an array column
def change
  add_column :users, :roles, :string, array: true, default: []
end

To find users with a specific role, such as admin, you can use:

User.where("roles @> ?", "{admin}")

This query tells PostgreSQL to check if the roles array contains the string "admin". PostgreSQL will handle the heavy lifting, making this a highly efficient way to filter users by roles.

Check out our article on ruby multiline strings for a better understanding of how to play with strings.

JSON and JSONB Contains Query in PostgreSQL

For applications where data is stored as JSON or JSONB, the @> operator also works. JSONB fields are useful for handling flexible data, especially for storing key-value pairs without strict schema definitions.

Example: Let’s say you’re storing user preferences in a JSONB column called preferences in the users table. If you want to find users who prefer email notifications, you might structure it like this:

# Sample JSONB data in `preferences`:
# { "notifications": { "email": true, "sms": false } }

User.where("preferences @> ?", { notifications: { email: true } }.to_json)

This query filters users by checking if their preferences contain an email notification setting. JSONB queries are a powerful way to work with semi-structured data directly in Rails.

MySQL JSON Contains Query

In MySQL, working with JSON fields differs from PostgreSQL. You can use JSON functions to perform similar searches. MySQL doesn’t have array support like PostgreSQL, but JSON can be a workaround.

Example: Consider you’re storing preferences as JSON. Here’s how you’d query users who want email notifications:

User.where("JSON_CONTAINS(preferences, 'true', '$.notifications.email')")

This tells MySQL to check if the JSON field preferences has an email setting set to true. Although less flexible than PostgreSQL arrays, JSON functions provide powerful tools for querying structured data in MySQL.

Using contains with ActiveRecord Enums

Rails Enums are a great way to handle attributes that have a limited set of values. Enums allow you to use a more readable, symbolic approach, while the database stores them as integers.

Example: Suppose you have a status enum in your Order model:

class Order < ApplicationRecord
  enum status: { pending: 0, shipped: 1, completed: 2 }
end

If you want to find orders with specific statuses, where with contains can come in handy:

Order.where(status: [:shipped, :completed])

This query translates to checking if the status contains shipped or completed. Enums simplify querying by allowing symbolic representation of status values.

Common Scenarios for Using contains in Rails Applications

Understanding how contains can be useful requires practical examples. Here are a few scenarios where contains shines in Rails applications:

1. Filtering Users by Roles

Imagine you’re building a content management system with multiple user roles. Instead of defining a column for each role, you can store roles as an array in a PostgreSQL column:

User.where("roles @> ?", "{editor}")

This query checks if the roles array contains editor, making it easy to filter users by their roles.

2. Checking Tags in JSON Arrays

For an e-commerce app, you may have a JSONB field in your Product model to store tags. To find products tagged with eco-friendly, you can use:

Product.where("tags @> ?", '["eco-friendly"]')

3. Querying Preferences in JSON Fields

Applications with customizable user settings often store them as JSON. If your app has a User model with a JSON field settings, you can find users with specific preferences.

User.where("settings @> ?", { theme: "dark" }.to_json)

This is useful for targeting users with specific preferences, such as those who use a dark theme.

Performance Tips for Using contains in ActiveRecord

Using contains is efficient, but there are best practices to follow for optimal performance.

1. Use Indexes on Array and JSON Columns

If you’re frequently using contains on an array or JSON column, indexing can improve performance. PostgreSQL supports indexes on both array and JSONB columns.

add_index :users, :roles, using: 'gin'
add_index :products, :tags, using: 'gin'

2. Avoid Overusing JSONB in Place of Columns

While JSONB is flexible, overusing it can make your schema complex and harder to maintain. Stick to structured columns when possible, reserving JSONB for truly flexible data.

3. Limit JSONB Depth for Faster Queries

When working with nested JSON data, remember that deeper nesting can slow down queries. Design your JSON structure carefully to avoid performance hits.

Real-World Example of ActiveRecord contains in Action

Consider an application managing tasks with an array of labels. Here’s a setup that uses contains in a practical way.

# Migration to add labels as an array to the tasks table
def change
  add_column :tasks, :labels, :string, array: true, default: []
end

With this setup, you can easily filter tasks by label:

Task.where("labels @> ?", "{urgent}")

This query returns tasks labeled as urgent. By offloading filtering to the database, you reduce application overhead and improve data retrieval speeds.

Best Practices for Writing ActiveRecord Queries with contains

When using contains, keep these tips in mind to write clean, maintainable code.

Use Symbolic Queries: Write queries in a way that reflects the intention of your code. Use symbols and enums wherever possible.

Simplify with Scopes: Define scopes to keep your code DRY (Don’t Repeat Yourself). For example:

class Task < ApplicationRecord
  scope :with_label, ->(label) { where("labels @> ?", "{#{label}}") }
end

Now you can call Task.with_label('urgent') for cleaner and more readable code.

Test Query Performance: When working with large datasets, test your queries to make sure they don’t slow down your app. Use tools like EXPLAIN in SQL to understand query execution plans.

Wrapping Up

Using contains with ActiveRecord can be a powerful tool for efficient data retrieval and clean, maintainable code. While Rails doesn’t offer a built-in contains method, SQL operators like @> for PostgreSQL or JSON_CONTAINS for MySQL enable this functionality. With proper indexing and thoughtful schema design, contains can make your Rails app faster and easier to work with. Happy querying!

Scroll to Top