TalentBox / sequel-rails

A gem for using Sequel with Rails 5.x, 6.x, 7.x, 8.x
http://talentbox.github.io/sequel-rails/
MIT License
326 stars 81 forks source link

GlobalID support #111

Open halostatue opened 8 years ago

halostatue commented 8 years ago

Sequel doesn’t follow the same protocol as ActiveRecord, even when ActiveModel compliant (or so it seems). I’ve come up with what looks like the right level of monkey-patching for this, and can turn this into a real patch (with tests) for GlobalID if there is interest. The changes are only necessary in GlobalID::Locator::BaseLocator for #locate and #find_records.

The only other change is including GlobalID::Identification into Sequel::Model, but in my basic testing, these changes create the same result as AR-backed GlobalID, mod the appropriate exceptions. If we want to make a real patch out of this, then we should probably make a plugin so that we can do Sequel::Model.plugin :globalid for explicit opt-in.

This has also been opened as rails/globalid#87.

module SequelBaseLocator
  def locate(gid)
    if defined?(::Sequel::Model) && gid.model_class < Sequel::Model
      gid.model_class.with_pk!(gid.model_id)
    else
      super
    end
  end

  private

  def find_records(model_class, ids, options)
    if defined?(::Sequel::Model) && model_class < Sequel::Model
      model_class.where(model_class.primary_key => ids).tap do |result|
        if !options[:ignore_missing] && result.count < ids.size
          fail Sequel::NoMatchingRow
        end
      end.all
    else
      super
    end
  end
end

GlobalID::Locator::BaseLocator.prepend SequelBaseLocator
Sequel::Model.send(:include, ::GlobalID::Identification)
Aryk commented 4 years ago

Nice work!

I packaged it into a plugin for anyone interested:

module Sequel
  module Plugins
    module GlobalId

      # Add Global ID support for Sequel::Models
      # Code comes @halostatue from https://github.com/TalentBox/sequel-rails/issues/111
      def self.apply(base, *)
        base.send(:include, ::GlobalID::Identification)
        GlobalID::Locator::BaseLocator.prepend SequelBaseLocator
      end

      module SequelBaseLocator
        def locate(gid)
          if defined?(::Sequel::Model) && gid.model_class < Sequel::Model
            gid.model_class.with_pk!(gid.model_id)
          else
            super
          end
        end

        private

        def find_records(model_class, ids, options)
          if defined?(::Sequel::Model) && model_class < Sequel::Model
            model_class.where(model_class.primary_key => ids).tap do |result|
              if !options[:ignore_missing] && result.count < ids.size
                fail Sequel::NoMatchingRow
              end
            end.all
          else
            super
          end
        end
      end

    end
  end
end
qnm commented 1 year ago

For anyone else finding this, the API for locate has changed a little: https://github.com/rails/globalid#custom-app-locator

The new plugin would be something like:

module Sequel
  module Plugins
    module GlobalId

      # Add Global ID support for Sequel::Models
      # Code comes @halostatue from https://github.com/TalentBox/sequel-rails/issues/111
      def self.apply(base, *)
        base.send(:include, ::GlobalID::Identification)
        GlobalID::Locator::BaseLocator.prepend SequelBaseLocator
      end

      module SequelBaseLocator
        def locate(gid, options = {})
          if defined?(::Sequel::Model) && gid.model_class < Sequel::Model
            gid.model_class.with_pk!(gid.model_id)
          else
            super
          end
        end

        private

        def find_records(model_class, ids, options)
          if defined?(::Sequel::Model) && model_class < Sequel::Model
            model_class.where(model_class.primary_key => ids).tap do |result|
              if !options[:ignore_missing] && result.count < ids.size
                fail Sequel::NoMatchingRow
              end
            end.all
          else
            super
          end
        end
      end

    end
  end
end