October 20, 2022; in Ruby on Rails

Simple column aliases in ActiveRecord

In SherlockDNA I have a few models that are mostly represented by a JSON-backed column. I don’t want to deserialize the entire JSON payload every time that row is called — there are tens of millions of rows accessed every day on this little side project, and that’s a lot of CPU cycles. I make use of SQL aliases to pull the data that I went.

In one controller action, I have this:

@matches = @leed.leed_matches.select("data->'test_guid' as test_guid, data->'public_display_name' as name, last_synced_at")

Fine, whatever. I have data->'test_guid' as test_guid a lot though and heaven forbid something changes in the data attribute I am left scrambling!

What I wanted was this:

@matches = @leed.leed_matches.with_test_guid.with_name.and_select("last_synced_at")

Or something.

Slight issue with ActiveRecord::QueryMethods

This:

Post.select(:id).select(:name)

Only gets name and id is left in the dark.

So we need and_select, or something.

module ActiveRecord
  module QueryMethods
    def and_select(*fields)
      if(!select_values.any?)
        fields.unshift(arel_table[Arel.star])
      end
      select(*fields)
    end
  end
end

The ActiveModel DSL

module ColumnAliases
  extend ActiveSupport::Concern
  
  included do 
    class_attribute :column_aliases, default: {}
  end
  
  class_methods do 
    def column_alias(name, sql)
      self.column_aliases[name] = Arel.sql(sql).as(name.to_s)
      scope "with_#{name}".to_sym, -> { and_select(column_aliases[name]) }
    end
  end
end

Usage

class Leed::Match < ApplicationRecord
  include ColumnAliases
  
  column_alias :test_guid, "data->'test_guid'"
  column_alias :name, "data->'public_display_name'"
end

Alas,

Leed::Match.with_test_guid.with_name.and_select(:last_synced_at)

Improvement

My colleague and I were talking about how this could be improved. They mentioned we probably really want something like:

Leed::Match.select(:test_guid, :name, :last_synced_at)

Which, we kind of can get, but not for things backed by SQL (at this time):

class Post < ApplicationRecord
  alias_attribute :subject, :title 
end

Post.where(subject: "bob") and Post.last.subject are both respectable.

I made a post on the mailing list: https://discuss.rubyonrails.org/t/support-for-alias-attribute-with-sql-aliasing/81592