Welcome to mirror list, hosted at ThFree Co, Russian Federation.

where_composite.rb « concerns « models « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3b66efc1c779ca5857b45a444b16356b08c3a490 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# 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