Mauro Morales

software developer

Day: September 6, 2020

  • ActiveRecord Except

    August 19th was Whyday, 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 selectclause. 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 toActiveRecord::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.