1# frozen_string_literal: true 2 3module WhereComposite 4 extend ActiveSupport::Concern 5 6 class TooManyIds < ArgumentError 7 LIMIT = 100 8 9 def initialize(no_of_ids) 10 super(<<~MSG) 11 At most #{LIMIT} identifier sets at a time please! Got #{no_of_ids}. 12 Have you considered splitting your request into batches? 13 MSG 14 end 15 16 def self.guard(collection) 17 n = collection.size 18 return collection if n <= LIMIT 19 20 raise self, n 21 end 22 end 23 24 class_methods do 25 # Apply a set of constraints that function as composite IDs. 26 # 27 # This is the plural form of the standard ActiveRecord idiom: 28 # `where(foo: x, bar: y)`, except it allows multiple pairs of `x` and 29 # `y` to be specified, with the semantics that translate to: 30 # 31 # ```sql 32 # WHERE 33 # (foo = x_0 AND bar = y_0) 34 # OR (foo = x_1 AND bar = y_1) 35 # OR ... 36 # ``` 37 # 38 # or the equivalent: 39 # 40 # ```sql 41 # WHERE 42 # (foo, bar) IN ((x_0, y_0), (x_1, y_1), ...) 43 # ``` 44 # 45 # @param permitted_keys [Array<Symbol>] The keys each hash must have. There 46 # must be at least one key (but really, 47 # it ought to be at least two) 48 # @param hashes [Array<#to_h>|#to_h] The constraints. Each parameter must have a 49 # value for the keys named in `permitted_keys` 50 # 51 # e.g.: 52 # ``` 53 # where_composite(%i[foo bar], [{foo: 1, bar: 2}, {foo: 1, bar: 3}]) 54 # ``` 55 # 56 def where_composite(permitted_keys, hashes) 57 raise ArgumentError, 'no permitted_keys' unless permitted_keys.present? 58 59 # accept any hash-like thing, such as Structs 60 hashes = TooManyIds.guard(Array.wrap(hashes)).map(&:to_h) 61 62 return none if hashes.empty? 63 64 case permitted_keys.size 65 when 1 66 key = permitted_keys.first 67 where(key => hashes.map { |hash| hash.fetch(key) }) 68 else 69 clauses = hashes.map do |hash| 70 permitted_keys.map do |key| 71 arel_table[key].eq(hash.fetch(key)) 72 end.reduce(:and) 73 end 74 75 where(clauses.reduce(:or)) 76 end 77 rescue KeyError 78 raise ArgumentError, "all arguments must contain #{permitted_keys}" 79 end 80 end 81end 82