| name | ActiveRecord Query Patterns |
| description | Complete guide to ActiveRecord query optimization, associations, scopes, and PostgreSQL-specific patterns. Use this skill when writing database queries, designing model associations, creating migrations, optimizing query performance, or debugging N+1 queries and grouping errors. |
ActiveRecord Query Patterns Skill
This skill provides comprehensive guidance for writing efficient, correct ActiveRecord queries in Rails applications with PostgreSQL.
When to Use This Skill
- Writing complex ActiveRecord queries
- Designing model associations
- Creating database migrations
- Optimizing query performance
- Debugging N+1 queries
- Working with GROUP BY operations
- Implementing scopes and query objects
Model Structure
Standard Model Template
# app/models/task.rb
class Task < ApplicationRecord
# == Constants ============================================================
STATUSES = %w[pending in_progress completed failed cancelled].freeze
# == Associations =========================================================
belongs_to :account
belongs_to :merchant
belongs_to :carrier, optional: true
belongs_to :recipient
belongs_to :zone, optional: true
has_many :timelines, dependent: :destroy
has_many :task_actions, dependent: :destroy
has_many :photos, dependent: :destroy
# == Validations ==========================================================
validates :status, presence: true, inclusion: { in: STATUSES }
validates :tracking_number, presence: true, uniqueness: { scope: :account_id }
# == Scopes ===============================================================
scope :active, -> { where.not(status: %w[completed failed cancelled]) }
scope :completed, -> { where(status: 'completed') }
scope :for_carrier, ->(carrier) { where(carrier: carrier) }
scope :created_between, ->(start_date, end_date) { where(created_at: start_date..end_date) }
scope :by_status, ->(status) { where(status: status) if status.present? }
# == Callbacks ============================================================
before_validation :generate_tracking_number, on: :create
after_commit :notify_recipient, on: :create
# == Class Methods ========================================================
def self.search(query)
where("tracking_number ILIKE :q OR description ILIKE :q", q: "%#{query}%")
end
# == Instance Methods =====================================================
def completable?
%w[pending in_progress].include?(status)
end
def complete!
update!(status: 'completed', completed_at: Time.current)
end
private
def generate_tracking_number
self.tracking_number ||= SecureRandom.hex(8).upcase
end
def notify_recipient
TaskNotificationJob.perform_later(id)
end
end
Association Patterns
Basic Associations
# One-to-Many
class Account < ApplicationRecord
has_many :users, dependent: :destroy
has_many :tasks, dependent: :destroy
end
class User < ApplicationRecord
belongs_to :account
end
# Many-to-Many (with join table)
class Task < ApplicationRecord
has_many :task_tags, dependent: :destroy
has_many :tags, through: :task_tags
end
class Tag < ApplicationRecord
has_many :task_tags, dependent: :destroy
has_many :tasks, through: :task_tags
end
class TaskTag < ApplicationRecord
belongs_to :task
belongs_to :tag
end
# Polymorphic
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Task < ApplicationRecord
has_many :comments, as: :commentable
end
class Invoice < ApplicationRecord
has_many :comments, as: :commentable
end
Association Options
class Task < ApplicationRecord
# Foreign key specification
belongs_to :creator, class_name: 'User', foreign_key: 'created_by_id'
# Optional association
belongs_to :carrier, optional: true
# Counter cache
belongs_to :merchant, counter_cache: true
# Dependent options
has_many :photos, dependent: :destroy # Delete associated records
has_many :logs, dependent: :nullify # Set foreign key to NULL
has_many :exports, dependent: :restrict_with_error # Prevent deletion
# Scoped association
has_many :active_timelines, -> { where(active: true) }, class_name: 'Timeline'
# Touch parent on update
belongs_to :bundle, touch: true
end
Query Patterns
Basic Queries
# Find
Task.find(1) # Raises RecordNotFound
Task.find_by(id: 1) # Returns nil if not found
Task.find_by!(id: 1) # Raises RecordNotFound
# Where
Task.where(status: 'pending')
Task.where(status: %w[pending in_progress]) # IN query
Task.where.not(status: 'completed')
Task.where(created_at: 1.week.ago..) # Range (>= date)
Task.where(created_at: ..1.week.ago) # Range (<= date)
Task.where(created_at: 1.month.ago..1.week.ago) # Between
# Order
Task.order(created_at: :desc)
Task.order(:status, created_at: :desc)
# Limit & Offset
Task.limit(10).offset(20)
# Distinct
Task.distinct.pluck(:status)
Avoiding N+1 Queries
# WRONG - N+1 query
tasks = Task.all
tasks.each { |t| puts t.carrier.name } # Query per task!
# CORRECT - Eager loading
tasks = Task.includes(:carrier)
tasks.each { |t| puts t.carrier.name } # Single query
# Multiple associations
Task.includes(:carrier, :merchant, :recipient)
# Nested associations
Task.includes(merchant: :branches)
# With conditions on association (use joins or references)
Task.includes(:carrier).where(carriers: { active: true }).references(:carriers)
# OR
Task.joins(:carrier).where(carriers: { active: true })
Choosing Loading Strategy
# includes - Smart loading (preload or eager_load based on usage)
Task.includes(:carrier)
# preload - Separate queries (can't filter on association)
Task.preload(:carrier)
# SELECT * FROM tasks
# SELECT * FROM carriers WHERE id IN (...)
# eager_load - Single LEFT JOIN query
Task.eager_load(:carrier)
# SELECT tasks.*, carriers.* FROM tasks LEFT JOIN carriers...
# joins - INNER JOIN (no loading, just filtering)
Task.joins(:carrier).where(carriers: { active: true })
GROUP BY Queries (Critical for PostgreSQL)
Rule: Every non-aggregated column in SELECT must appear in GROUP BY.
# CORRECT - Only grouped columns and aggregates
Task.group(:status).count
# => { "pending" => 10, "completed" => 25 }
Task.group(:status).sum(:amount)
# => { "pending" => 1000, "completed" => 5000 }
# CORRECT - Multiple GROUP BY columns
Task
.group(:status, :task_type)
.count
# => { ["pending", "express"] => 5, ["completed", "standard"] => 10 }
# CORRECT - Explicit select with aggregates
Task
.select(:status, 'COUNT(*) as task_count', 'AVG(amount) as avg_amount')
.group(:status)
# CORRECT - Date grouping
Task
.group("DATE(created_at)")
.count
# WRONG - includes with group
Task.includes(:carrier).group(:status).count # ERROR!
# CORRECT - Separate queries if you need associated data
status_counts = Task.group(:status).count
tasks_by_status = status_counts.keys.each_with_object({}) do |status, hash|
hash[status] = Task.where(status: status).includes(:carrier).limit(5)
end
Subqueries
# Subquery in WHERE
active_carrier_ids = Carrier.where(active: true).select(:id)
Task.where(carrier_id: active_carrier_ids)
# SELECT * FROM tasks WHERE carrier_id IN (SELECT id FROM carriers WHERE active = true)
# Subquery with join
Task.where(carrier_id: Carrier.active.select(:id))
.where(merchant_id: Merchant.premium.select(:id))
Raw SQL (When Needed)
# Safe with sanitization
Task.where("created_at > ?", 1.week.ago)
Task.where("description ILIKE ?", "%#{query}%")
# Named bindings
Task.where("status = :status AND amount > :min", status: 'pending', min: 100)
# Select with raw SQL
Task.select("*, amount * 0.1 as commission")
# Find by SQL
Task.find_by_sql(["SELECT * FROM tasks WHERE status = ?", 'pending'])
Scope Patterns
Simple Scopes
class Task < ApplicationRecord
scope :active, -> { where.not(status: %w[completed cancelled]) }
scope :completed, -> { where(status: 'completed') }
scope :recent, -> { order(created_at: :desc) }
scope :today, -> { where(created_at: Time.current.all_day) }
end
Parameterized Scopes
class Task < ApplicationRecord
scope :by_status, ->(status) { where(status: status) }
scope :created_after, ->(date) { where('created_at >= ?', date) }
scope :for_carrier, ->(carrier_id) { where(carrier_id: carrier_id) }
# With default
scope :recent, ->(limit = 10) { order(created_at: :desc).limit(limit) }
# Conditional scope
scope :by_status_if_present, ->(status) { where(status: status) if status.present? }
end
Chainable Scopes
# All scopes are chainable
Task.active.recent.by_status('pending').for_carrier(123)
# Combine with where
Task.active.where(merchant_id: 456)
Query Objects
# app/queries/tasks/pending_delivery_query.rb
module Tasks
class PendingDeliveryQuery
def initialize(relation = Task.all)
@relation = relation
end
def call(zone_id: nil, since: 24.hours.ago)
result = @relation
.where(status: 'pending')
.where('created_at >= ?', since)
.includes(:carrier, :recipient)
result = result.where(zone_id: zone_id) if zone_id.present?
result.order(created_at: :asc)
end
end
end
# Usage
Tasks::PendingDeliveryQuery.new.call(zone_id: 123)
Tasks::PendingDeliveryQuery.new(account.tasks).call(since: 1.hour.ago)
Migration Patterns
Create Table
class CreateTasks < ActiveRecord::Migration[7.1]
def change
create_table :tasks do |t|
t.references :account, null: false, foreign_key: true
t.references :merchant, null: false, foreign_key: true
t.references :carrier, foreign_key: true # nullable
t.string :tracking_number, null: false
t.string :status, null: false, default: 'pending'
t.decimal :amount, precision: 10, scale: 2
t.jsonb :metadata, default: {}
t.datetime :completed_at
t.timestamps
t.index :tracking_number, unique: true
t.index :status
t.index [:account_id, :status]
t.index [:merchant_id, :created_at]
t.index :metadata, using: :gin # For JSONB queries
end
end
end
Safe Migrations
# Add column with default (safe in PostgreSQL 11+)
class AddPriorityToTasks < ActiveRecord::Migration[7.1]
def change
add_column :tasks, :priority, :integer, default: 0, null: false
end
end
# Add index concurrently (for large tables)
class AddIndexToTasksStatus < ActiveRecord::Migration[7.1]
disable_ddl_transaction!
def change
add_index :tasks, :status, algorithm: :concurrently
end
end
# Remove column safely
class RemoveOldColumnFromTasks < ActiveRecord::Migration[7.1]
def change
safety_assured { remove_column :tasks, :old_column, :string }
end
end
JSONB Columns
# Migration
add_column :tasks, :metadata, :jsonb, default: {}
add_index :tasks, :metadata, using: :gin
# Model
class Task < ApplicationRecord
# Using jsonb_accessor gem
jsonb_accessor :metadata,
priority: :integer,
tags: [:string, array: true],
notes: :string
end
# Queries
Task.where("metadata @> ?", { priority: 1 }.to_json)
Task.where("metadata->>'priority' = ?", '1')
Task.where("metadata ? 'special_flag'")
Performance Optimization
Batch Processing
# WRONG - Loads all records into memory
Task.all.each { |task| process(task) }
# CORRECT - Batches of 1000
Task.find_each(batch_size: 1000) { |task| process(task) }
# With specific order
Task.order(:id).find_each { |task| process(task) }
# In batches (for batch operations)
Task.in_batches(of: 1000) do |batch|
batch.update_all(processed: true)
end
Select Only Needed Columns
# WRONG - Loads all columns
users = User.all
users.each { |u| puts u.email }
# CORRECT - Only needed columns
users = User.select(:id, :email)
users.each { |u| puts u.email }
# With pluck (returns arrays, not AR objects)
emails = User.pluck(:email)
Counter Caches
# Migration
add_column :merchants, :tasks_count, :integer, default: 0
# Model
class Task < ApplicationRecord
belongs_to :merchant, counter_cache: true
end
# Now merchant.tasks_count doesn't query
merchant.tasks_count # Uses cached count
Exists? vs Any? vs Present?
# EFFICIENT - Stops at first match
Task.where(status: 'pending').exists?
# SELECT 1 FROM tasks WHERE status = 'pending' LIMIT 1
# LESS EFFICIENT - Loads records
Task.where(status: 'pending').any?
# May load records depending on implementation
# INEFFICIENT - Loads all records
Task.where(status: 'pending').present?
# SELECT * FROM tasks WHERE status = 'pending'
Explain & Analyze
# In Rails console
Task.where(status: 'pending').explain
Task.where(status: 'pending').explain(:analyze)
# Check for sequential scans on large tables
# Look for "Seq Scan" - may need index
Debugging Queries
# In Rails console, enable query logging
ActiveRecord::Base.logger = Logger.new(STDOUT)
# Or in development.rb
config.active_record.verbose_query_logs = true
# Using bullet gem for N+1 detection
# Gemfile: gem 'bullet', group: :development
Rails 7.x/8.x Modern Features
Composite Primary Keys (Rails 7.1+)
# Migration
class CreateBookOrders < ActiveRecord::Migration[7.1]
def change
create_table :book_orders, primary_key: [:shop_id, :id] do |t|
t.integer :shop_id
t.integer :id
t.string :status
t.timestamps
end
end
end
# Model
class BookOrder < ApplicationRecord
self.primary_key = [:shop_id, :id]
belongs_to :shop
has_many :line_items, foreign_key: [:shop_id, :order_id]
end
# Usage
order = BookOrder.find([shop_id: 1, id: 100])
order.id # => { shop_id: 1, id: 100 }
ActiveRecord::Encryption (Rails 7+)
For encrypting sensitive data at rest:
# config/credentials.yml.enc
active_record_encryption:
primary_key: <%= ENV['AR_ENCRYPTION_PRIMARY_KEY'] %>
deterministic_key: <%= ENV['AR_ENCRYPTION_DETERMINISTIC_KEY'] %>
key_derivation_salt: <%= ENV['AR_ENCRYPTION_KEY_DERIVATION_SALT'] %>
# Model
class User < ApplicationRecord
encrypts :email # Non-deterministic (can't query)
encrypts :ssn, deterministic: true # Deterministic (can query equality)
encrypts :credit_card, ignore_case: true
end
# Queries with deterministic encryption
User.where(ssn: '123-45-6789') # Works with deterministic: true
User.where(email: 'user@example.com') # Doesn't work without deterministic
# Unencrypted reads (for migration)
class User < ApplicationRecord
encrypts :email, ignore_case: true, previous: { ignore_case: false }
end
Multi-Database Configuration (Rails 6.1+)
# config/database.yml
production:
primary:
<<: *default
database: my_primary_database
analytics:
<<: *default
database: my_analytics_database
replica: true
migrations_paths: db/analytics_migrate
# Models
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary, reading: :primary }
end
class AnalyticsRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :analytics, reading: :analytics }
end
class Event < AnalyticsRecord
end
# Switching databases
ActiveRecord::Base.connected_to(role: :reading) do
# Read from replica
end
ActiveRecord::Base.connected_to(role: :writing) do
# Write to primary
end
# Prevent writes
ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true) do
# Raises error on write
end
Horizontal Sharding (Rails 7.1+)
# config/database.yml
production:
primary:
database: my_primary_database
shard_one:
database: my_shard_one_database
shard_two:
database: my_shard_two_database
# Model
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to shards: {
shard_one: { writing: :shard_one },
shard_two: { writing: :shard_two }
}
end
# Usage
ActiveRecord::Base.connected_to(shard: :shard_one) do
User.create!(name: "User in shard one")
end
ActiveRecord::Base.connected_to(shard: :shard_two) do
User.create!(name: "User in shard two")
end
# Switching shards based on data
def with_user_shard(user_id)
shard = user_id.even? ? :shard_one : :shard_two
ActiveRecord::Base.connected_to(shard: shard) do
yield
end
end
Enum Patterns with i18n
# Model
class Task < ApplicationRecord
enum status: {
pending: 0,
in_progress: 1,
completed: 2,
failed: 3,
cancelled: 4
}, _prefix: true # status_pending?, status_completed?
enum priority: {
low: 0,
medium: 1,
high: 2,
urgent: 3
}, _suffix: true # low_priority?, high_priority?
# Auto-generated methods:
# task.status => "pending"
# task.pending? => true
# task.status_pending? => true (with prefix)
# task.completed! => Changes to completed
# Task.statuses => {"pending" => 0, "completed" => 2, ...}
# Task.pending => Scope for pending tasks
# Task.not_pending => Scope for non-pending tasks
end
# i18n
# config/locales/en.yml
en:
activerecord:
attributes:
task:
status:
pending: "Pending"
in_progress: "In Progress"
completed: "Completed"
failed: "Failed"
cancelled: "Cancelled"
# Usage in views
<%= t("activerecord.attributes.task.status.#{task.status}") %>
# Or with enum_help gem
gem 'enum_help'
Task.human_attribute_name("status.#{task.status}")
# Scopes with enums
Task.pending # SELECT * FROM tasks WHERE status = 0
Task.not_pending # SELECT * FROM tasks WHERE status != 0
Task.where.not(status: :completed)
Database Views
# Migration
class CreateActiveTasksView < ActiveRecord::Migration[7.1]
def up
execute <<-SQL
CREATE VIEW active_tasks AS
SELECT
tasks.*,
merchants.name AS merchant_name,
carriers.name AS carrier_name
FROM tasks
INNER JOIN merchants ON merchants.id = tasks.merchant_id
LEFT JOIN carriers ON carriers.id = tasks.carrier_id
WHERE tasks.status IN ('pending', 'in_progress')
SQL
end
def down
execute "DROP VIEW IF EXISTS active_tasks"
end
end
# Model
class ActiveTask < ApplicationRecord
# Read-only model backed by view
self.primary_key = :id
def readonly?
true
end
end
# Usage
ActiveTask.all
ActiveTask.where(merchant_name: "ACME Corp")
# Materialized Views (faster, but need refresh)
class CreateTaskSummaryView < ActiveRecord::Migration[7.1]
def up
execute <<-SQL
CREATE MATERIALIZED VIEW task_summaries AS
SELECT
DATE(created_at) as date,
status,
COUNT(*) as count,
AVG(amount) as average_amount
FROM tasks
GROUP BY DATE(created_at), status
SQL
add_index :task_summaries, :date
end
def down
execute "DROP MATERIALIZED VIEW IF EXISTS task_summaries"
end
end
# Refresh materialized view
ActiveRecord::Base.connection.execute("REFRESH MATERIALIZED VIEW task_summaries")
# Concurrent refresh (non-blocking)
ActiveRecord::Base.connection.execute("REFRESH MATERIALIZED VIEW CONCURRENTLY task_summaries")
Common Table Expressions (CTEs)
# Simple CTE
Task.with(
active_merchants: Merchant.where(active: true).select(:id)
).joins("INNER JOIN active_merchants ON tasks.merchant_id = active_merchants.id")
# Complex CTE example
Task.with(
recent_tasks: Task.where('created_at > ?', 30.days.ago).select(:id, :merchant_id),
active_merchants: Merchant.where(active: true).select(:id)
).from("recent_tasks")
.joins("INNER JOIN active_merchants ON recent_tasks.merchant_id = active_merchants.id")
# Recursive CTE for hierarchical data
Category.with_recursive(
category_tree: [
Category.where(id: 1), # Base case
Category.joins("INNER JOIN category_tree ON categories.parent_id = category_tree.id") # Recursive
]
).from(:category_tree)
# Using raw SQL for complex CTEs
sql = <<-SQL
WITH RECURSIVE subordinates AS (
SELECT id, name, manager_id, 1 as level
FROM employees
WHERE id = ?
UNION ALL
SELECT e.id, e.name, e.manager_id, s.level + 1
FROM employees e
INNER JOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates
SQL
Employee.find_by_sql([sql, manager_id])
Single Table Inheritance (STI) Patterns
# Base model
class Vehicle < ApplicationRecord
# Columns: id, type, name, ...
validates :name, presence: true
# Shared behavior
def describe
"#{self.class.name}: #{name}"
end
end
class Car < Vehicle
# Car-specific behavior
def drive
"Driving #{name}"
end
# Car-specific validations
validates :num_doors, presence: true
end
class Motorcycle < Vehicle
def ride
"Riding #{name}"
end
end
# Usage
car = Car.create!(name: "Tesla", num_doors: 4)
motorcycle = Motorcycle.create!(name: "Harley")
Vehicle.all # Returns mix of cars and motorcycles
Car.all # Returns only cars
car.type # => "Car"
# Scopes work with STI
class Vehicle < ApplicationRecord
scope :recent, -> { where('created_at > ?', 1.week.ago) }
end
Car.recent # Only recent cars
Vehicle.recent # All recent vehicles
# Custom type column name
class Vehicle < ApplicationRecord
self.inheritance_column = 'vehicle_type'
end
# Disable STI (use 'type' column for something else)
class Vehicle < ApplicationRecord
self.inheritance_column = nil
end
STI Best Practices:
- Use when subclasses share 80%+ of attributes
- Avoid if types have very different attributes (use polymorphic instead)
- Watch for sparse tables (lots of nulls) - consider delegated_type
- Add database constraint on type column
STI Anti-patterns:
# BAD - Too many type-specific columns
create_table :vehicles do |t|
t.string :type
t.string :name
# Car-specific
t.integer :num_doors
t.string :trunk_type
# Boat-specific
t.integer :hull_length
t.string :sail_type
# Plane-specific
t.integer :max_altitude
t.string :engine_type
end
# GOOD - Use polymorphic or separate tables instead
Generated Columns (PostgreSQL/MySQL)
# PostgreSQL generated columns (Rails 7.0+)
class AddFullNameToUsers < ActiveRecord::Migration[7.1]
def change
add_column :users, :full_name, :virtual,
type: :string,
as: "first_name || ' ' || last_name",
stored: true # Or false for computed on-the-fly
add_index :users, :full_name
end
end
# Model (read-only)
class User < ApplicationRecord
# full_name is automatically calculated
end
user = User.create!(first_name: "Alice", last_name: "Smith")
user.full_name # => "Alice Smith"
# Can query generated columns
User.where("full_name ILIKE ?", "%smith%")
Full-Text Search with pg_search
# Gemfile
gem 'pg_search'
# Model
class Article < ApplicationRecord
include PgSearch::Model
pg_search_scope :search_full_text,
against: {
title: 'A', # Higher weight
body: 'B',
author: 'C'
},
using: {
tsearch: {
prefix: true,
dictionary: 'english'
}
}
# Multi-table search
pg_search_scope :search_with_comments,
against: [:title, :body],
associated_against: {
comments: [:body]
}
# Trigram similarity search
pg_search_scope :fuzzy_search,
against: [:title, :body],
using: {
trigram: { threshold: 0.3 }
}
end
# Migration for indexes
class AddPgSearchIndexes < ActiveRecord::Migration[7.1]
def up
# tsvector column for better performance
add_column :articles, :tsv, :tsvector
add_index :articles, :tsv, using: :gin
execute <<-SQL
UPDATE articles
SET tsv = to_tsvector('english', coalesce(title, '') || ' ' || coalesce(body, ''))
SQL
# Trigger to keep it updated
execute <<-SQL
CREATE TRIGGER articles_tsv_update BEFORE INSERT OR UPDATE
ON articles FOR EACH ROW EXECUTE FUNCTION
tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);
SQL
# For trigram search
enable_extension 'pg_trgm'
add_index :articles, :title, using: :gin, opclass: :gin_trgm_ops
end
def down
execute "DROP TRIGGER IF EXISTS articles_tsv_update ON articles"
remove_column :articles, :tsv
end
end
# Usage
Article.search_full_text("rails tutorial")
Article.fuzzy_search("raails") # Finds "rails"
Article.search_with_comments("ruby")
# With rankings
Article.search_full_text("rails")
.with_pg_search_rank
.order('pg_search_rank DESC')
Advanced JSONB Queries
# Model with JSONB
class Product < ApplicationRecord
# Column: specifications (jsonb)
# Using jsonb_accessor for typed access
jsonb_accessor :specifications,
color: :string,
weight: :float,
dimensions: [:string, array: true],
features: [:string, array: true]
end
# Query patterns
# Contains
Product.where("specifications @> ?", { color: 'red' }.to_json)
# Has key
Product.where("specifications ? 'warranty'")
# Array contains element
Product.where("specifications -> 'features' ? 'wireless'")
# Extract and compare
Product.where("specifications ->> 'color' = ?", 'red')
Product.where("(specifications ->> 'weight')::float > ?", 5.0)
# With indexes
add_index :products, :specifications, using: :gin
add_index :products, "(specifications -> 'color')", using: :btree
# Array queries
Product.where("specifications -> 'features' @> ?", ['wireless'].to_json)
Pre-Query Checklist
Before writing any complex query:
[ ] What columns am I selecting?
[ ] Am I using GROUP BY? If so, is every SELECT column grouped or aggregated?
[ ] Am I using includes/preload with GROUP BY? (DON'T!)
[ ] Will this query run on a large table? Do indexes exist?
[ ] Am I iterating and accessing associations? Use includes.
[ ] Am I loading more data than needed? Use select/pluck.
[ ] Is this sensitive data? Consider ActiveRecord::Encryption.
[ ] Should this be in a separate database? (multi-database)
[ ] Is this a hierarchical query? Consider CTEs.
[ ] Need full-text search? Use pg_search.