SpeciesFileGroup/taxonworks

View on GitHub
lib/queries/concerns/tags.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# Helpers and facets for queries that reference Tags.
#
# Test coverage is currently in spec/lib/queries/source/filter_spec.rb.
#
module Queries::Concerns::Tags

  extend ActiveSupport::Concern

  def self.params
    [
      :keyword_id_and,
      :keyword_id_or,
      :tags,
      keyword_id_and: [],
      keyword_id_or: []
    ]
  end

  included do
    # @return [Array]
    # @params keyword_id_and
    #   match all objects tagged with all of the keywords referenced in this array
    attr_accessor :keyword_id_and

    # @return [Array]
    # @params keyword_id_or
    #   match all objects tagged with any of the keywords referenced in this array
    attr_accessor :keyword_id_or

    # @return [Boolean, nil]
    # @params tags ['true', 'false', nil]
    attr_accessor :tags

    def keyword_id_and
      [@keyword_id_and].flatten.compact.uniq
    end

    def keyword_id_or
      [@keyword_id_or].flatten.compact.uniq
    end
  end

  def self.merge_clauses
    [
      :keyword_id_facet,
      :tags_facet
    ]
  end

  def set_tags_params(params)
    @keyword_id_and = params[:keyword_id_and]
    @keyword_id_or = params[:keyword_id_or]

    @tags = boolean_param(params, :tags) # (params[:tags]&.to_s&.downcase == 'true' ? true : false) if !params[:tags].nil?
  end

  def keyword_id_and
    [@keyword_id_and].flatten.compact.uniq
  end

  def keyword_id_or
    [@keyword_id_or].flatten.compact.uniq
  end

  # @return [Arel::Table]
  def tag_table
    ::Tag.arel_table
  end

  # TODO: why here?
  def keyword_ids=(value = [])
    @keyword_ids = value
  end

  # @return
  #   all sources that match all _and ids OR any OR id
  def keyword_id_facet
    return nil if keyword_id_or.empty? && keyword_id_and.empty?
    k = table.name.classify.safe_constantize

    a = matching_keyword_id_or
    b = matching_keyword_id_and

    if a.nil?
      b
    elsif b.nil?
      a
    else
      k.from("( (#{a.to_sql}) UNION (#{b.to_sql})) as #{table.name}")
    end
  end

  def tags_facet
    return nil if tags.nil?
    if tags
      referenced_klass.joins(:tags).distinct
    else
      referenced_klass.where.missing(:tags)
    end
  end

  def matching_keyword_id_or
    return nil if keyword_id_or.empty?
    k = table.name.classify.safe_constantize
    t = ::Tag.arel_table

    w = t[:tag_object_id].eq(table[:id]).and( t[:tag_object_type].eq(table.name.classify))
    w = w.and( t[:keyword_id].in(keyword_id_or) ) if keyword_id_or.any?

    k.where( ::Tag.where(w).arel.exists )
  end

  def matching_keyword_id_and
    return nil if keyword_id_and.empty?
    l = table.name
    k = l.classify.safe_constantize
    t = ::Tag.arel_table

    a = table.alias("k_#{l}")

    b = table.project(a[Arel.star]).from(a)
      .join(t)
      .on(
        t[:tag_object_id].eq(a[:id]),
        t[:tag_object_type].eq(k.name)
      )

    i = 0

    keyword_id_and.each do |j|
      t_a = t.alias("tk_#{l[0..5]}_#{i}")
      b = b.join(t_a).on(
        t_a['tag_object_id'].eq(a['id']),
        t_a[:tag_object_type].eq(k),
        t_a[:keyword_id].eq(j)
      )

      i += 1
    end

    b = b.group(a[:id]).having(t[:keyword_id].count.gteq(keyword_id_and.count))
    b = b.as("#{l}_ai")

    k.joins(Arel::Nodes::InnerJoin.new(b, Arel::Nodes::On.new(b[:id].eq(table[:id]))))
  end
end