I ran into something recently where I wanted to access a Postgres view from a Rails 6 application. Lots of ways to do this, but I found a pattern that’s really simple and feels right to me:

First, make an abstract class called ApplicationView. It’s helpful for semantics, and also because everything inheriting it will be read only.

app/models/application_view.rb:

class ApplicationView < ApplicationRecord
  self.abstract_class = true

  def readonly?
    true
  end
end

Then, create a model and have it inherit from ApplicationView :

app/models/search_result.rb:

class SearchResult < ApplicationView
  self.table_name = "search_results"
end

Create your view as part of a regular Rails migration:

class AddSearchIndexes < ActiveRecord::Migration[6.0]
  def up
    execute <<~SQL.chomp
      CREATE VIEW search_results AS
        SELECT
          ...
        FROM table
          ...
    SQL
  end

  def down
    execute <<~SQL.chomp
      DROP VIEW search_results
    SQL
  end

Finally, you can access it like a regular Rails model:

SearchResult.where(name: "foo bar")

Bonus: if you’re using this to build a gin or gist search index like I was, you can easily write a scope to execute the search. Assuming your gin index is on haystack_column:

class SearchResult < ApplicationView
  self.table_name = "search_results"

  scope :for_term, ->(needle) { where("plainto_tsquery('english', ?) @@ haystack_column", needle) }
end

Access like this:

SearchResult.for_term("foo bar")

The benefit of using a scope is that you can chain it to longer and more complex queries.

SearchResult
  .where(user: user)
  .for_term("foo bar")
  .order(score: :desc)
  .limit(10)

Boom.