gitlab-org--gitlab-foss/app/models/concerns/where_composite.rb

82 lines
2.3 KiB
Ruby

# frozen_string_literal: true
module WhereComposite
extend ActiveSupport::Concern
class TooManyIds < ArgumentError
LIMIT = 100
def initialize(no_of_ids)
super(<<~MSG)
At most #{LIMIT} identifier sets at a time please! Got #{no_of_ids}.
Have you considered splitting your request into batches?
MSG
end
def self.guard(collection)
n = collection.size
return collection if n <= LIMIT
raise self, n
end
end
class_methods do
# Apply a set of constraints that function as composite IDs.
#
# This is the plural form of the standard ActiveRecord idiom:
# `where(foo: x, bar: y)`, except it allows multiple pairs of `x` and
# `y` to be specified, with the semantics that translate to:
#
# ```sql
# WHERE
# (foo = x_0 AND bar = y_0)
# OR (foo = x_1 AND bar = y_1)
# OR ...
# ```
#
# or the equivalent:
#
# ```sql
# WHERE
# (foo, bar) IN ((x_0, y_0), (x_1, y_1), ...)
# ```
#
# @param permitted_keys [Array<Symbol>] The keys each hash must have. There
# must be at least one key (but really,
# it ought to be at least two)
# @param hashes [Array<#to_h>|#to_h] The constraints. Each parameter must have a
# value for the keys named in `permitted_keys`
#
# e.g.:
# ```
# where_composite(%i[foo bar], [{foo: 1, bar: 2}, {foo: 1, bar: 3}])
# ```
#
def where_composite(permitted_keys, hashes)
raise ArgumentError, 'no permitted_keys' unless permitted_keys.present?
# accept any hash-like thing, such as Structs
hashes = TooManyIds.guard(Array.wrap(hashes)).map(&:to_h)
return none if hashes.empty?
case permitted_keys.size
when 1
key = permitted_keys.first
where(key => hashes.map { |hash| hash.fetch(key) })
else
clauses = hashes.map do |hash|
permitted_keys.map do |key|
arel_table[key].eq(hash.fetch(key))
end.reduce(:and)
end
where(clauses.reduce(:or))
end
rescue KeyError
raise ArgumentError, "all arguments must contain #{permitted_keys}"
end
end
end