Global ID

repository·main·Indexed 23 days ago

https://github.com/rails/globalid

A library providing an application-wide URI scheme to uniquely identify model instances. It enables developers to reference different classes of objects using a single universal identifier, facilitating job scheduling and polymorphic associations. Features include Signed Global IDs (SGIDs) for security with expiration and purpose-based restrictions, efficient batch loading via GlobalID::Locator.locate_many, and support for custom app locators for cross-app references.

Tokens
5.6K
Snippets
12
Records
38
Agent score
79%

What's inside rails-globalid

  1. Configure expiration for Signed Global IDs

    main

    SGIDs can be created with an expiration to limit access (e.g., for share links). You can specify a relative time with expires_in or an absolute time with expires_at.

    Note: Expiring SGIDs are not idempotent because they encode the current timestamp; repeated calls to to_sgid will produce different strings.

    Configuration

    • Rails default: 1 month.
    • Global default: Set via SignedGlobalID.expires_in = 1.month.
    • Rails app-specific default: Set via Rails.application.config.global_id.expires_in = 3.months in an initializer.

    To create a permanent SGID that never expires, pass expires_in: nil.

  2. Implement a custom App Locator

    main

    You can define custom locators for specific app names in a URI (e.g., gid://foo/Model/1) using GlobalID::Locator.use. This is useful for cross-app references.

    It is recommended to inherit from GlobalID::Locator::BaseLocator to get default implementations for model_class and locate_many.

    Custom Model Class Derivation

    By overriding the model_class(gid) method in your locator, you can map a GID's model name to a different local class. This allows you to work with Global IDs that reference models that don't exist locally by redirecting them to appropriate local models.

    ```ruby
    class RemoteLocator < GlobalID::Locator::BaseLocator
      def model_class(gid)
        case gid.model_name
        when 'User'
          RemoteUser
        when 'Profile'
          RemoteProfile
        else
          super
        end
      end
    
      def locate(gid, options = {})
        model_class(gid).find_by(remote_id: gid.model_id)
      end
    end
    
    GlobalID::Locator.use :remote, RemoteLocator.new
    # URIs like "gid://remote/User/1" will now use this locator.
    ```埋
  3. Enable Global ID support in models

    main

    To allow a model to be represented by a Global ID, mix GlobalID::Identification into the class. The model must implement a .find(id) class method (returning an instance) and a .where(id:) class method (returning an enumerable). Active Record models include this support automatically.

    Once mixed in, you can convert an instance to a Global ID using .to_global_id and retrieve its URI string.

    ```ruby
    person_gid = Person.find(1).to_global_id
    # => #<GlobalID ...
    
    person_gid.uri
    # => #<URI ...
    
    person_gid.to_s
    # => "gid://app/Person/1"
    ```埋
  4. What is a URI::GID and how is it structured?

    main

    A URI::GID is a specialized URI that encodes a unique reference to a specific model. It is used to represent an application-specific model instance as a URI string.

    Format: gid://app/model_name/model_id?key=value

    Components:

    • app: The application name (acts as the URI host). Must be a valid hostname (alphanumeric, hyphens, or underscores).
    • model_name: The class name of the model.
    • model_id: The unique identifier of the model instance. This can be a single value or a composite ID separated by /.
    • params: Optional metadata stored as query parameters.

    Important Notes on Params:

    • Params are always returned as strings (no typecasting).
    • Params can be accessed using both strings and symbol keys (indifferent access).
    • Multi-value params are not supported; if a key appears multiple times, only the last value is retained.
  5. Enable Global ID support in a model

    main

    To allow a model to generate its own Global IDs, mix in GlobalID::Identification. The model must implement a class method find(id) that can retrieve the record by its ID. Support for this is automatically included in Active Record models.

    Example implementation for a non-Active Record model:

    class Person
      include ActiveModel::Model
      include GlobalID::Identification
    
      attr_accessor :id
    
      def self.find(id)
        new id: id
      end
    
      def ==(other)
        id == other.try(:id)
      end
    end
    class Person
      include ActiveModel::Model
      include GlobalID::Identification
    
      attr_accessor :id
    
      def self.find(id)
        new id: id
      end
    
      def ==(other)
        id == other.try(:id)
      end
    end
    
    person_gid = Person.find(1).to_global_id
    # => #<GlobalID ...
    person_gid.uri
    # => #<URI ...
    person_gid.to_s
    # => "gid://app/Person/1"
    GlobalID::Locator.locate person_gid
    # => #<Person:0x007fae94bf6298 @id="1">
  6. Locate a model instance from a Global ID

    main

    Use GlobalID::Locator.locate to retrieve a model instance from a Global ID. It returns nil if the ID is blank or unparseable, and allows backend exceptions to bubble up if a record cannot be found.

    If you need to distinguish between a record that no longer exists and a transient backend failure (like a database timeout), use GlobalID::Locator.fetch. This method raises specific errors:

    • GlobalID::Locator::RecordNotFound: The record no longer exists.
    • GlobalID::Locator::RecordUnavailable: The backend failed (retry may succeed).

    Both errors extend GlobalID::Locator::Error.

    ```ruby
    GlobalID::Locator.locate person_gid
    # => #<Person:0x007fae94bf6298 @id="1">
    
    GlobalID::Locator.fetch person_gid
    # => #<Person:0x007fae94bf6298 @id="1">           # found
    # => raises GlobalID::Locator::RecordNotFound     # the record no longer exists
    # => raises GlobalID::Locator::RecordUnavailable  # the backend failed; retry may succeed
    ```埋
  7. Use the 'for' option to restrict Signed Global ID usage

    main
    You can add a purpose to an SGID using the for: option. This prevents an SGID generated for one purpose (e.g., a sign-up form) from being reused for another (e.g., a login page). When locating the ID, you must provide the same purpose string.
  8. Use Signed Global IDs (SGIDs) for security

    main

    Signed Global IDs (SGIDs) ensure that the data hasn't been tampered with. You can generate them using .to_signed_global_id or the alias .to_sgid. To retrieve the instance, use GlobalID::Locator.locate_signed.

    ```ruby
    person_sgid = Person.find(1).to_signed_global_id
    # => #<SignedGlobalID:0x007fea1944b410>
    
    person_sgid.to_s
    # => "BAhJIh5naWQ6Ly9pZGluYWlkaS9Vc2VyLzM5NTk5BjoGRVQ=--81d7358dd5ee2ca33189bb404592df5e8d11420e"
    
    GlobalID::Locator.locate_signed person_sgid
    # => #<Person:0x007fae94bf6298 @id="1">
    ```埋
  9. Locate multiple Global IDs efficiently

    main
    When you have a collection of Global IDs, use GlobalID::Locator.locate_many (or locate_many_signed for SGIDs) to load them efficiently. This method groups IDs by their model name to perform fewer queries (e.g., using WHERE id IN (...) instead of individual find calls). The order of the input GIDs is preserved in the returned array.
  10. Set a custom default locator

    main

    To change the default locator used by the entire application, assign a locator instance to GlobalID::Locator.default_locator=.

    ```ruby
    class MyCustomLocator < UnscopedLocator
      def locate(gid, options = {})
        ActiveRecord::Base.connected_to(role: :reading) do
          super(gid, options)
        end
      end
    end
    
    GlobalID::Locator.default_locator = MyCustomLocator.new
    ```埋
  11. Configure SignedGlobalID verifier and expiration

    main

    You can set global defaults for all SignedGlobalID operations by configuring the class attributes:

    • SignedGlobalID.verifier: An instance of ActiveSupport::MessageVerifier used to sign and verify IDs.
    • SignedGlobalID.expires_in: A duration (e.g., 5.minutes) used as the default expiration time for new signed IDs if no specific expiration is provided during initialization.