module Sequel::Plugins::AssociationFiltering::DatasetMethods

Constants

COUNT_STAR

Public Instance Methods

association_exclude(association_name, &block) click to toggle source
# File lib/sequel/plugins/association_filtering.rb, line 73
def association_exclude(association_name, &block)
  association_filter(association_name, invert: true, &block)
end
association_filter( association_name, invert: false, **extra ) { |ds| ... } click to toggle source
# File lib/sequel/plugins/association_filtering.rb, line 24
def association_filter(
  association_name,
  invert: false,
  **extra
)
  having_args = extra.slice(:at_least, :at_most, :exactly)

  case having_args.length
  when 0
    # No-op
  when 1
    having_arg   = having_args.keys[0]
    having_value = having_args.values[0]
    raise Error, ":#{having_arg} must be an Integer if present" unless having_value.is_a?(Integer)
  else
    raise Error, "cannot pass more than one of :at_least, :at_most, and :exactly"
  end

  reflection =
    model.association_reflections.fetch(association_name) do
      raise Error, "association #{association_name} not found on model #{model}"
    end

  ds = _association_filter_dataset(reflection, group_by_remote: !!having_arg)
  ds = yield(ds) if block_given?

  cache_key =
    _association_filter_cache_key(
      reflection: reflection,
      extra: :"#{invert}_#{having_arg}_#{having_value}",
    )

  ds.send :cached_dataset, cache_key do
    having_condition =
      case having_arg
      when :at_least then COUNT_STAR >= having_value
      when :at_most  then COUNT_STAR <= having_value
      when :exactly  then COUNT_STAR =~ having_value
      when nil       then nil
      else raise Error, "Unexpected argument: #{having_arg.inspect}"
      end

    ds = ds.having(having_condition) if having_condition
    cond = ds.exists
    cond = Sequel.~(cond) if invert
    where(cond)
  end
end

Private Instance Methods

_association_filter_cache_key(reflection:, extra: nil) click to toggle source
# File lib/sequel/plugins/association_filtering.rb, line 119
def _association_filter_cache_key(reflection:, extra: nil)
  :"_association_filter_#{reflection[:model]}_#{reflection[:name]}_#{extra}"
end
_association_filter_dataset(reflection, group_by_remote:) click to toggle source
# File lib/sequel/plugins/association_filtering.rb, line 79
def _association_filter_dataset(reflection, group_by_remote:)
  cache_key =
    _association_filter_cache_key(
      reflection: reflection,
      extra: :"association_#{group_by_remote}"
    )

  ds = reflection.associated_dataset

  ds.send(:cached_dataset, cache_key) do
    case t = reflection[:type]
    when :one_to_many
      local_keys  = reflection.qualified_primary_key
      remote_keys = reflection.predicate_key
    when :many_to_one
      local_keys  = reflection[:qualified_key]
      remote_keys = reflection.qualified_primary_key
    when :many_to_many
      local_keys  = reflection.qualify_cur(reflection[:left_primary_key])
      remote_keys = reflection.qualified_left_key
    else
      raise Error, "Unsupported reflection type: #{t}"
    end

    local_keys  = Array(local_keys)
    remote_keys = Array(remote_keys)

    result =
      ds.where(
        remote_keys.
        zip(local_keys).
        map{|r,l| {r => l}}.
        inject{|a,b| Sequel.&(a, b)}
      ).select(1)

    result = result.group_by(*remote_keys) if group_by_remote
    result
  end
end