Read more

How to generate GIDs from an ActiveRecord scope

Arne Hartherz
September 18, 2020Software engineer at makandra GmbH

ActiveRecord provides the ids method to pluck ids from a scope, but what if you need to pluck Global IDs Show archive.org snapshot ?

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

While you could just call map(&:to_global_id) on your scope, this approach would instantiate each record just to do that. When you have many records, this will at the very least be slow.

Here is a method that does it for you efficiently. It respects Single Table Inheritance (STI).
Put it in your project's ApplicationRecord to make it available on all models.

class ApplicationRecord
  def self.global_ids
    # This method creates GID values for the current scope, similar to
    # ActiveRecord's `ids` method. For performance, we read id and type
    # directly from the database and create GIDs from them.
    # Note that we do not return GlobalID objects, but GID URI strings.
    columns = [primary_key]
    columns << inheritance_column if column_names.include?(inheritance_column)

    # To avoid instantiating the model class itself (which is expensive)
    # we define temporary classes which hold just enough information
    # for the GID to be built. We do this only once per type.
    anonymous_classes_by_type = Hash.new do |hash, type|
      klass = Struct.new(:id)
      klass.define_singleton_method(:name) { type }
      hash[type] = klass
    end

    # We want to use `GlobalID.create` to avoid constructing a GID
    # ourselves, and because it includes some sanity checks as well.
    pluck(*columns).map do |(id, type)|
      klass = anonymous_classes_by_type[type || name]
      GlobalID.create(klass.new(id)).to_s
    end
  end
end

Example usage:

>> User.global_ids
=> ["gid://your-app/User/1", "gid://your-app/User/2", "gid://your-app/User/3"]
>> Page.global_ids
=> ["gid://your-app/Page::BlogPost/1", "gid://your-app/Page::PressRelease/2", "gid://your-app/Page::BlogPost/3"]
Posted by Arne Hartherz to makandra dev (2020-09-18 09:32)