ActiveRecord Except

Ghent,

August 19th was Whyday1, and to commemorate it, I decided to write a gem called activerecord-except.

What is it?

activerecord-except is a Ruby gem that extends the functionality of ActiveRecord, allowing you to select all the fields from a table, except the ones that you specify. For example, if you have a table users.

development_db=# \d users
      Table "public.users"
            Column             | 
-------------------------------+-
 id                            | 
 username                      | 
 password                      | 
 email                         | 
 first_name                    | 
 last_name                     | 
 phone                         | 
 ...
 created_at                    | 
 updated_at                    | 

And you want to get all the fields except for the password, you’d have to pass each of them in your select clause like so

User.all.select(:id,
                :username,
                :email,
                :first_name,
                :last_name,
                :phone,
                ...
                :created_at,
                :updated_at)

Instead, using activerecord-except, can simplify your statement by saying only the field you don’t want, in this case, the password one

User.all.except(:password)

How does it work?

Under the hood, the except clause makes use of the traditional select clause. So our previous example will produce the following query

SELECT "users"."id",
       "users"."username",
       "users"."first_name",
       "users"."last_name",
       "users"."phone",
       "users"."created_at",
       ...
       "users"."updated_at"
  FROM "users"

This is because the SQL language doesn’t provide such functionality out of the box.

I don’t know what is the reason for this. I can only speculate that it’s to be more explicit and not be caught by surprise if a field in a table gets added/deleted/changed. However, * is also wildly used. In Rails for example, it is what you get, when you don’t specify a select clause in your query.

The way I managed to make it work is by adding a method to ActiveRecord::Relation which asks the model for all its attributes and rejecting those that match with the ones passed as arguments.

klass._default_attributes
     .keys.map(&:to_sym)
     .reject { |attr| fields.include?(attr) }

Note: As you can see, I’m using _default_attributes which starts with an underscore. This can mean that the method is not intended to be relied upon.

Whether or not you might want to use in production, I leave up to you, where I really see the benefit of activerecord-except is for writing one-off scripts to extract data, because it makes them much easier to read.

Want to give it a try?

You can install it from rubygems or you can check the source code either on Sourcehut or GitHub.


  1. A day to hack just for the fun and joy of it in memory of Why The Lucky Stiff https://www.whyday.org/ ↩︎


Related posts

The contents for this page are licensed CC-BY-SA.