ruby 50 lines · 3 tabs

Database view-backed models for complex queries

Alex Kumar Jan 2026
3 tabs
class CreateUserStatsView < ActiveRecord::Migration[6.1]
  def up
    execute <<-SQL
      CREATE VIEW user_stats AS
      SELECT
        users.id AS user_id,
        users.name,
        users.email,
        COUNT(DISTINCT posts.id) AS posts_count,
        COUNT(DISTINCT comments.id) AS comments_count,
        SUM(posts.views) AS total_views,
        SUM(posts.likes_count) AS total_likes,
        MAX(posts.created_at) AS last_post_at
      FROM users
      LEFT JOIN posts ON posts.author_id = users.id
      LEFT JOIN comments ON comments.author_id = users.id
      GROUP BY users.id, users.name, users.email;
    SQL
  end

  def down
    execute 'DROP VIEW user_stats;'
  end
end
3 files · ruby Explain with highlit

Complex reporting queries with multiple joins and aggregations can become unmaintainable in ActiveRecord. PostgreSQL views encapsulate query complexity in the database layer and appear as regular tables to Rails. I create views for common reporting needs like materialized user statistics, denormalized search indexes, or aggregated analytics. Views update automatically as underlying data changes (or on schedule for materialized views). ActiveRecord models backed by views work exactly like regular models—I can query them with where, includes, and other ActiveRecord methods. The key limitation is that views are read-only by default. For complex analytics, materialized views with scheduled refreshes balance freshness and query performance.


Related snips

Share this code

Here's the card — post it anywhere.

Database view-backed models for complex queries — share card
Link copied