Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
# frozen_string_literal: true

require_relative 'admin_connection'

Sequel.extension :pg_json

module Faculty
  module DatabaseHelper
    # Macros for use in defining table helpers
    module TableMacros
      def define_table(table, helper_class, **)
        schema = module_schema_name
        helper = helper_class.new(table, schema:, **)
        define_method(:"#{table}_record") do
          warn "[DEPRECATION] `#{table}_record` is deprecated.  Please use `dbt.#{schema}.#{table}` instead."
          helper
        end
        SCHEMAS[schema][table] = helper
      end

      def module_schema_name
        name.split('::').last.downcase.to_sym
      end
    end

    # Generic spec helper for dealing with test records
    class GenericTableHelper
      attr_reader :qualified_table

      def initialize(table, schema: nil, default_record: {}, pg_jsonb_fields: {})
        @qualified_table = schema ? Sequel[schema][table] : table
        @default_record = default_record
        @pg_jsonb_fields = pg_jsonb_fields
      end

      def connection
        raise "#{self.class} (for #{table_name}) is an instance of GenericTableHelper and has no connection"
      end

      def table
        connection[qualified_table]
      end

      def table_name
        return qualified_table unless qualified_table.is_a? Sequel::SQL::QualifiedIdentifier

        "#{qualified_table.table}.#{qualified_table.column}"
      end

      def default_record_fields
        @default_record
      end

      def include?(...)
        !table.where(...).empty?
      end

      def insert(**fields)
        table.insert_select(**fields_to_insert(**fields))
             .then { |row| row.extend(Row).set(table, primary_keys) }
      rescue Sequel::Error => e
        raise e.class, "#{self.class} failed to insert to #{table_name}: #{e}"
      end

      def fields_to_insert(**fields)
        wrap_jsonb_fields(deep_merge(default_record_fields, fields))
      end

      def wrap_jsonb_fields(fields)
        fields.to_h do |k, v|
          @pg_jsonb_fields.include?(k) ? [k, Sequel.pg_jsonb_wrap(v)] : [k, v]
        end
      end

      # Shamelessly lifted from rails
      # (https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/hash/deep_merge.rb)
      def deep_merge(from, to, &block)
        from.merge(to) do |key, this_val, other_val|
          if this_val.is_a?(Hash) && other_val.is_a?(Hash)
            deep_merge(this_val, other_val, &block)
          elsif block
            yield key, this_val, other_val
          else
            other_val
          end
        end
      end

      def truncate
        table.truncate(cascade: true)
      end

      def to_s
        return @qualified_table unless @qualified_table.is_a? Sequel::SQL::QualifiedIdentifier

        "#{@qualified_table.table}.#{@qualified_table.column}"
      end

      def inspect
        "#<#{self.class} #{self}>"
      end

      private

      def primary_keys
        @primary_keys ||= connection.schema(@qualified_table).filter_map { |k, v| k if v[:primary_key] }
      end
    end

    # Tables that are in the app's schema
    class LocalTableHelper < GenericTableHelper
      def connection
        Faculty::SpecHelpers.connection
      end
    end

    # Tables which are outside the app's schema
    class ExternalTableHelper < GenericTableHelper
      include AdminConnection

      class << self
        attr_accessor :db
      end

      def connection
        ExternalTableHelper.db || super
      end
    end

    # Extend the returned row with a cleanup method
    module Row
      def set(table, primary_keys)
        @table = table
        @primary_keys = primary_keys
        self
      end

      def primary_keys
        slice(*@primary_keys)
      end

      def primary_key
        raise 'More than one primary key' if @primary_keys.length > 1
        raise 'No primary keys' if @primary_keys.empty?

        fetch @primary_keys.first
      end

      def cleanup
        @table.where(**(@primary_keys.empty? ? self : primary_keys)).delete
      end
    end
  end
end