hq-graphql 2.1.12 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/README.md +25 -9
 - data/lib/hq/graphql.rb +17 -8
 - data/lib/hq/graphql/association_loader.rb +1 -0
 - data/lib/hq/graphql/comparator.rb +22 -21
 - data/lib/hq/graphql/enum/filter_operation.rb +15 -0
 - data/lib/hq/graphql/enum/sort_by.rb +8 -4
 - data/lib/hq/graphql/enum/sort_order.rb +8 -4
 - data/lib/hq/graphql/ext.rb +8 -0
 - data/lib/hq/graphql/ext/active_record_extensions.rb +148 -0
 - data/lib/hq/graphql/ext/enum_extensions.rb +81 -0
 - data/lib/hq/graphql/ext/input_object_extensions.rb +110 -0
 - data/lib/hq/graphql/ext/mutation_extensions.rb +24 -0
 - data/lib/hq/graphql/ext/object_extensions.rb +122 -0
 - data/lib/hq/graphql/ext/schema_extensions.rb +50 -0
 - data/lib/hq/graphql/field.rb +1 -1
 - data/lib/hq/graphql/filter_operations.rb +59 -0
 - data/lib/hq/graphql/filters.rb +150 -0
 - data/lib/hq/graphql/paginated_association_loader.rb +1 -0
 - data/lib/hq/graphql/record_loader.rb +1 -0
 - data/lib/hq/graphql/resource.rb +86 -15
 - data/lib/hq/graphql/resource/auto_mutation.rb +8 -5
 - data/lib/hq/graphql/root_mutation.rb +1 -1
 - data/lib/hq/graphql/root_query.rb +3 -3
 - data/lib/hq/graphql/scalars.rb +0 -2
 - data/lib/hq/graphql/types.rb +1 -2
 - data/lib/hq/graphql/types/uuid.rb +3 -5
 - data/lib/hq/graphql/util.rb +13 -0
 - data/lib/hq/graphql/version.rb +1 -1
 - metadata +28 -38
 - data/lib/hq/graphql/active_record_extensions.rb +0 -143
 - data/lib/hq/graphql/enum.rb +0 -78
 - data/lib/hq/graphql/input_object.rb +0 -104
 - data/lib/hq/graphql/mutation.rb +0 -15
 - data/lib/hq/graphql/object.rb +0 -120
 - data/lib/hq/graphql/schema.rb +0 -22
 - data/lib/hq/graphql/types/object.rb +0 -35
 
| 
         @@ -0,0 +1,150 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "hq/graphql/filter_operations"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "hq/graphql/util"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module HQ
         
     | 
| 
      
 7 
     | 
    
         
            +
              module GraphQL
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Filters
         
     | 
| 
      
 9 
     | 
    
         
            +
                  BOOLEAN_VALUES = ["t", "f", "true", "false"]
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def self.supported?(column)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    !!Filter.class_from_column(column)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_reader :filters, :model
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def initialize(filters, model)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @filters = Array(filters).map { |filter| Filter.for(filter, table: model.arel_table) }
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @model = model
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def validate!
         
     | 
| 
      
 23 
     | 
    
         
            +
                    filters.each(&:validate)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    errors = filters.map do |filter|
         
     | 
| 
      
 25 
     | 
    
         
            +
                      filter.display_error_message
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end.flatten.uniq
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    if errors.any?
         
     | 
| 
      
 29 
     | 
    
         
            +
                      raise ::GraphQL::ExecutionError, errors.join(", ")
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def to_scope
         
     | 
| 
      
 34 
     | 
    
         
            +
                    filters.reduce(model.all) do |s, filter|
         
     | 
| 
      
 35 
     | 
    
         
            +
                      s.where(filter.to_arel)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  class Filter
         
     | 
| 
      
 40 
     | 
    
         
            +
                    include ActiveModel::Validations
         
     | 
| 
      
 41 
     | 
    
         
            +
                    include FilterOperations
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    def self.for(filter, **options)
         
     | 
| 
      
 44 
     | 
    
         
            +
                      class_from_column(filter.field).new(filter, **options)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    def self.class_from_column(column)
         
     | 
| 
      
 48 
     | 
    
         
            +
                      case column.type
         
     | 
| 
      
 49 
     | 
    
         
            +
                      when :boolean
         
     | 
| 
      
 50 
     | 
    
         
            +
                        BooleanFilter
         
     | 
| 
      
 51 
     | 
    
         
            +
                      when :date, :datetime
         
     | 
| 
      
 52 
     | 
    
         
            +
                        DateFilter
         
     | 
| 
      
 53 
     | 
    
         
            +
                      when :decimal, :integer
         
     | 
| 
      
 54 
     | 
    
         
            +
                        NumericFilter
         
     | 
| 
      
 55 
     | 
    
         
            +
                      when :string, :text
         
     | 
| 
      
 56 
     | 
    
         
            +
                        StringFilter
         
     | 
| 
      
 57 
     | 
    
         
            +
                      when :uuid
         
     | 
| 
      
 58 
     | 
    
         
            +
                        UuidFilter
         
     | 
| 
      
 59 
     | 
    
         
            +
                      end
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    def self.validate_operations(*operations)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      valid_operations = operations + [WITH]
         
     | 
| 
      
 64 
     | 
    
         
            +
                      validates :operation, inclusion: {
         
     | 
| 
      
 65 
     | 
    
         
            +
                        in: valid_operations,
         
     | 
| 
      
 66 
     | 
    
         
            +
                        message: "only supports the following operations: #{valid_operations.map(&:name).join(", ")}"
         
     | 
| 
      
 67 
     | 
    
         
            +
                      }
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                    def self.validate_value(**options)
         
     | 
| 
      
 71 
     | 
    
         
            +
                      validates :value, **options, unless: ->(filter) { filter.operation == WITH }
         
     | 
| 
      
 72 
     | 
    
         
            +
                    end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                    validate :validate_boolean_values, if: ->(filter) { filter.operation == WITH }
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    attr_reader :table, :column, :operation, :value
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    def initialize(filter, table:)
         
     | 
| 
      
 79 
     | 
    
         
            +
                      @table = table
         
     | 
| 
      
 80 
     | 
    
         
            +
                      @column = filter.field
         
     | 
| 
      
 81 
     | 
    
         
            +
                      @operation = filter.operation
         
     | 
| 
      
 82 
     | 
    
         
            +
                      @value = filter.value
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                    def display_error_message
         
     | 
| 
      
 86 
     | 
    
         
            +
                      return unless errors.any?
         
     | 
| 
      
 87 
     | 
    
         
            +
                      messages = errors.messages.values.join(", ")
         
     | 
| 
      
 88 
     | 
    
         
            +
                      "#{column.name.camelize(:lower)} (type: #{column.type}, operation: #{operation.name}, value: \"#{value}\"): #{messages}"
         
     | 
| 
      
 89 
     | 
    
         
            +
                    end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                    def to_arel
         
     | 
| 
      
 92 
     | 
    
         
            +
                      operation.to_arel(table: table, column_name: column.name, value: value)
         
     | 
| 
      
 93 
     | 
    
         
            +
                    end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                    def validate_boolean_values
         
     | 
| 
      
 96 
     | 
    
         
            +
                      is_valid = BOOLEAN_VALUES.any? { |v| value.casecmp(v) == 0 }
         
     | 
| 
      
 97 
     | 
    
         
            +
                      return if is_valid
         
     | 
| 
      
 98 
     | 
    
         
            +
                      errors.add(:value, "WITH operation only supports boolean values (#{BOOLEAN_VALUES.join(", ")})")
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  class BooleanFilter < Filter
         
     | 
| 
      
 103 
     | 
    
         
            +
                    validate_operations
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                    def to_arel
         
     | 
| 
      
 106 
     | 
    
         
            +
                      arel = super
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                      if value.casecmp("f") == 0 || value.casecmp("false") == 0
         
     | 
| 
      
 109 
     | 
    
         
            +
                        arel = arel.or(table[column.name].eq(false))
         
     | 
| 
      
 110 
     | 
    
         
            +
                      end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                      arel
         
     | 
| 
      
 113 
     | 
    
         
            +
                    end
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                  class DateFilter < Filter
         
     | 
| 
      
 117 
     | 
    
         
            +
                    validate_operations GREATER_THAN, LESS_THAN
         
     | 
| 
      
 118 
     | 
    
         
            +
                    validate :validate_iso8601
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    def validate_iso8601
         
     | 
| 
      
 121 
     | 
    
         
            +
                      is_valid = begin
         
     | 
| 
      
 122 
     | 
    
         
            +
                        DateTime.iso8601(value)
         
     | 
| 
      
 123 
     | 
    
         
            +
                        true
         
     | 
| 
      
 124 
     | 
    
         
            +
                      rescue ArgumentError
         
     | 
| 
      
 125 
     | 
    
         
            +
                        false
         
     | 
| 
      
 126 
     | 
    
         
            +
                      end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                      return if is_valid
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                      today = Date.today
         
     | 
| 
      
 131 
     | 
    
         
            +
                      errors.add(:value, "only supports ISO8601 values (\"#{today.iso8601}\", \"#{today.to_datetime.iso8601}\")")
         
     | 
| 
      
 132 
     | 
    
         
            +
                    end
         
     | 
| 
      
 133 
     | 
    
         
            +
                  end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                  class NumericFilter < Filter
         
     | 
| 
      
 136 
     | 
    
         
            +
                    validate_operations GREATER_THAN, LESS_THAN, EQUAL, NOT_EQUAL
         
     | 
| 
      
 137 
     | 
    
         
            +
                    validate_value numericality: { message: "only supports numerical values" }
         
     | 
| 
      
 138 
     | 
    
         
            +
                  end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                  class StringFilter < Filter
         
     | 
| 
      
 141 
     | 
    
         
            +
                    validate_operations EQUAL, NOT_EQUAL, LIKE, NOT_LIKE
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                  class UuidFilter < Filter
         
     | 
| 
      
 145 
     | 
    
         
            +
                    validate_operations EQUAL, NOT_EQUAL
         
     | 
| 
      
 146 
     | 
    
         
            +
                    validate_value format: { with: HQ::GraphQL::Util::UUID_FORMAT, message: "only supports UUID values (e.g. 00000000-0000-0000-0000-000000000000)" }
         
     | 
| 
      
 147 
     | 
    
         
            +
                  end
         
     | 
| 
      
 148 
     | 
    
         
            +
                end
         
     | 
| 
      
 149 
     | 
    
         
            +
              end
         
     | 
| 
      
 150 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -17,6 +17,7 @@ module HQ 
     | 
|
| 
       17 
17 
     | 
    
         
             
                  end
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                  def initialize(model, association_name, internal_association: false, limit: nil, offset: nil, scope: nil, sort_by: nil, sort_order: nil)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    super()
         
     | 
| 
       20 
21 
     | 
    
         
             
                    @model                = model
         
     | 
| 
       21 
22 
     | 
    
         
             
                    @association_name     = association_name
         
     | 
| 
       22 
23 
     | 
    
         
             
                    @internal_association = internal_association
         
     | 
    
        data/lib/hq/graphql/resource.rb
    CHANGED
    
    | 
         @@ -1,9 +1,12 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require "hq/graphql/ext/enum_extensions"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "hq/graphql/ext/input_object_extensions"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "hq/graphql/ext/object_extensions"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require "hq/graphql/enum/filter_operation"
         
     | 
| 
       3 
7 
     | 
    
         
             
            require "hq/graphql/enum/sort_by"
         
     | 
| 
       4 
8 
     | 
    
         
             
            require "hq/graphql/field_extension/paginated_arguments"
         
     | 
| 
       5 
     | 
    
         
            -
            require "hq/graphql/ 
     | 
| 
       6 
     | 
    
         
            -
            require "hq/graphql/object"
         
     | 
| 
      
 9 
     | 
    
         
            +
            require "hq/graphql/filters"
         
     | 
| 
       7 
10 
     | 
    
         
             
            require "hq/graphql/resource/auto_mutation"
         
     | 
| 
       8 
11 
     | 
    
         
             
            require "hq/graphql/scalars"
         
     | 
| 
       9 
12 
     | 
    
         | 
| 
         @@ -60,18 +63,21 @@ module HQ 
     | 
|
| 
       60 
63 
     | 
    
         
             
                    end
         
     | 
| 
       61 
64 
     | 
    
         | 
| 
       62 
65 
     | 
    
         
             
                    def nil_query_object
         
     | 
| 
       63 
     | 
    
         
            -
                      @nil_query_object ||= build_graphql_object(name: "#{graphql_name}Copy", auto_nil: false)
         
     | 
| 
      
 66 
     | 
    
         
            +
                      @nil_query_object ||= const_set(:NilQuery, build_graphql_object(name: "#{graphql_name}Copy", auto_nil: false))
         
     | 
| 
       64 
67 
     | 
    
         
             
                    end
         
     | 
| 
       65 
68 
     | 
    
         | 
| 
       66 
69 
     | 
    
         
             
                    def query_object
         
     | 
| 
       67 
70 
     | 
    
         
             
                      @query_object ||= begin
         
     | 
| 
       68 
     | 
    
         
            -
                         
     | 
| 
       69 
     | 
    
         
            -
                           
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
                           
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
      
 71 
     | 
    
         
            +
                        qo =
         
     | 
| 
      
 72 
     | 
    
         
            +
                          if @query_object_options
         
     | 
| 
      
 73 
     | 
    
         
            +
                            options, block = @query_object_options
         
     | 
| 
      
 74 
     | 
    
         
            +
                            @query_object_options = nil
         
     | 
| 
      
 75 
     | 
    
         
            +
                            build_graphql_object(**options, &block)
         
     | 
| 
      
 76 
     | 
    
         
            +
                          else
         
     | 
| 
      
 77 
     | 
    
         
            +
                            build_graphql_object
         
     | 
| 
      
 78 
     | 
    
         
            +
                          end
         
     | 
| 
      
 79 
     | 
    
         
            +
                        remove_const(:Query) if const_defined?(:Query, false)
         
     | 
| 
      
 80 
     | 
    
         
            +
                        const_set(:Query, qo)
         
     | 
| 
       75 
81 
     | 
    
         
             
                      end
         
     | 
| 
       76 
82 
     | 
    
         
             
                    end
         
     | 
| 
       77 
83 
     | 
    
         | 
| 
         @@ -79,6 +85,59 @@ module HQ 
     | 
|
| 
       79 
85 
     | 
    
         
             
                      @sort_fields_enum || ::HQ::GraphQL::Enum::SortBy
         
     | 
| 
       80 
86 
     | 
    
         
             
                    end
         
     | 
| 
       81 
87 
     | 
    
         | 
| 
      
 88 
     | 
    
         
            +
                    def const_missing(constant_name)
         
     | 
| 
      
 89 
     | 
    
         
            +
                      constant_name = constant_name.to_sym
         
     | 
| 
      
 90 
     | 
    
         
            +
                      case constant_name
         
     | 
| 
      
 91 
     | 
    
         
            +
                      when :Query
         
     | 
| 
      
 92 
     | 
    
         
            +
                        query_object
         
     | 
| 
      
 93 
     | 
    
         
            +
                      when :NilQuery
         
     | 
| 
      
 94 
     | 
    
         
            +
                        nil_query_object
         
     | 
| 
      
 95 
     | 
    
         
            +
                      when :Input
         
     | 
| 
      
 96 
     | 
    
         
            +
                        input_klass
         
     | 
| 
      
 97 
     | 
    
         
            +
                      when :FilterInput
         
     | 
| 
      
 98 
     | 
    
         
            +
                        filter_input
         
     | 
| 
      
 99 
     | 
    
         
            +
                      when :FilterFields
         
     | 
| 
      
 100 
     | 
    
         
            +
                        filter_fields_enum
         
     | 
| 
      
 101 
     | 
    
         
            +
                      else
         
     | 
| 
      
 102 
     | 
    
         
            +
                        super
         
     | 
| 
      
 103 
     | 
    
         
            +
                      end
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                    def filter_input
         
     | 
| 
      
 107 
     | 
    
         
            +
                      @filter_input ||= begin
         
     | 
| 
      
 108 
     | 
    
         
            +
                        scoped_self = self
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                        input_class = Class.new(::GraphQL::Schema::InputObject) do
         
     | 
| 
      
 111 
     | 
    
         
            +
                          graphql_name "#{scoped_self.graphql_name}FilterInput"
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                          argument :field, scoped_self.filter_fields_enum, required: true
         
     | 
| 
      
 114 
     | 
    
         
            +
                          argument :operation, Enum::FilterOperation, required: true
         
     | 
| 
      
 115 
     | 
    
         
            +
                          argument :value, String, required: true
         
     | 
| 
      
 116 
     | 
    
         
            +
                        end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                        const_set(:FilterInput, input_class)
         
     | 
| 
      
 119 
     | 
    
         
            +
                      end
         
     | 
| 
      
 120 
     | 
    
         
            +
                    end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                    def filter_fields_enum
         
     | 
| 
      
 123 
     | 
    
         
            +
                      @filter_fields_enum ||= begin
         
     | 
| 
      
 124 
     | 
    
         
            +
                        scoped_self = self
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                        enum_class = Class.new(::GraphQL::Schema::Enum) do
         
     | 
| 
      
 127 
     | 
    
         
            +
                          graphql_name "#{scoped_self.graphql_name}FilterFields"
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                          lazy_load do
         
     | 
| 
      
 130 
     | 
    
         
            +
                            scoped_self.model_klass.columns.sort_by(&:name).each do |column|
         
     | 
| 
      
 131 
     | 
    
         
            +
                              next unless HQ::GraphQL::Filters.supported?(column)
         
     | 
| 
      
 132 
     | 
    
         
            +
                              value column.name.camelize(:lower), value: column
         
     | 
| 
      
 133 
     | 
    
         
            +
                            end
         
     | 
| 
      
 134 
     | 
    
         
            +
                          end
         
     | 
| 
      
 135 
     | 
    
         
            +
                        end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                        const_set(:FilterFields, enum_class)
         
     | 
| 
      
 138 
     | 
    
         
            +
                      end
         
     | 
| 
      
 139 
     | 
    
         
            +
                    end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
       82 
141 
     | 
    
         
             
                    protected
         
     | 
| 
       83 
142 
     | 
    
         | 
| 
       84 
143 
     | 
    
         
             
                    def default_scope(&block)
         
     | 
| 
         @@ -115,11 +174,15 @@ module HQ 
     | 
|
| 
       115 
174 
     | 
    
         
             
                    def def_root(field_name, is_array: false, null: true, &block)
         
     | 
| 
       116 
175 
     | 
    
         
             
                      resource = self
         
     | 
| 
       117 
176 
     | 
    
         
             
                      resolver = -> {
         
     | 
| 
       118 
     | 
    
         
            -
                        Class.new(::GraphQL::Schema::Resolver) do
         
     | 
| 
      
 177 
     | 
    
         
            +
                        klass = Class.new(::GraphQL::Schema::Resolver) do
         
     | 
| 
       119 
178 
     | 
    
         
             
                          type = is_array ? [resource.query_object] : resource.query_object
         
     | 
| 
       120 
179 
     | 
    
         
             
                          type type, null: null
         
     | 
| 
       121 
180 
     | 
    
         
             
                          class_eval(&block) if block
         
     | 
| 
       122 
181 
     | 
    
         
             
                        end
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                        constant_name = "#{field_name.to_s.classify}Resolver"
         
     | 
| 
      
 184 
     | 
    
         
            +
                        resource.send(:remove_const, constant_name) if resource.const_defined?(constant_name, false)
         
     | 
| 
      
 185 
     | 
    
         
            +
                        resource.const_set(constant_name, klass)
         
     | 
| 
       123 
186 
     | 
    
         
             
                      }
         
     | 
| 
       124 
187 
     | 
    
         
             
                      ::HQ::GraphQL.root_queries << {
         
     | 
| 
       125 
188 
     | 
    
         
             
                        field_name: field_name, resolver: resolver, model_name: model_name
         
     | 
| 
         @@ -146,9 +209,13 @@ module HQ 
     | 
|
| 
       146 
209 
     | 
    
         
             
                      if find_all
         
     | 
| 
       147 
210 
     | 
    
         
             
                        def_root field_name.pluralize, is_array: true, null: false do
         
     | 
| 
       148 
211 
     | 
    
         
             
                          extension FieldExtension::PaginatedArguments, klass: scoped_self.model_klass if pagination
         
     | 
| 
      
 212 
     | 
    
         
            +
                          argument :filters, [scoped_self.filter_input], required: false
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                          define_method(:resolve) do |filters: nil, limit: nil, offset: nil, sort_by: nil, sort_order: nil, **_attrs|
         
     | 
| 
      
 215 
     | 
    
         
            +
                            filters_scope = ::HQ::GraphQL::Filters.new(filters, scoped_self.model_klass)
         
     | 
| 
      
 216 
     | 
    
         
            +
                            filters_scope.validate!
         
     | 
| 
       149 
217 
     | 
    
         | 
| 
       150 
     | 
    
         
            -
             
     | 
| 
       151 
     | 
    
         
            -
                            scope = scoped_self.scope(context).all
         
     | 
| 
      
 218 
     | 
    
         
            +
                            scope = scoped_self.scope(context).all.merge(filters_scope.to_scope)
         
     | 
| 
       152 
219 
     | 
    
         | 
| 
       153 
220 
     | 
    
         
             
                            if pagination || page || limit
         
     | 
| 
       154 
221 
     | 
    
         
             
                              offset = [0, *offset].max
         
     | 
| 
         @@ -172,7 +239,7 @@ module HQ 
     | 
|
| 
       172 
239 
     | 
    
         
             
                    def build_graphql_object(name: graphql_name, **options, &block)
         
     | 
| 
       173 
240 
     | 
    
         
             
                      scoped_graphql_name = name
         
     | 
| 
       174 
241 
     | 
    
         
             
                      scoped_model_name = model_name
         
     | 
| 
       175 
     | 
    
         
            -
                      object_class = @query_class || ::HQ::GraphQL.default_object_class || :: 
     | 
| 
      
 242 
     | 
    
         
            +
                      object_class = @query_class || ::HQ::GraphQL.default_object_class || ::GraphQL::Schema::Object
         
     | 
| 
       176 
243 
     | 
    
         
             
                      Class.new(object_class) do
         
     | 
| 
       177 
244 
     | 
    
         
             
                        graphql_name scoped_graphql_name
         
     | 
| 
       178 
245 
     | 
    
         | 
| 
         @@ -187,18 +254,22 @@ module HQ 
     | 
|
| 
       187 
254 
     | 
    
         
             
                      scoped_model_name = model_name
         
     | 
| 
       188 
255 
     | 
    
         
             
                      scoped_excluded_inputs = @excluded_inputs || []
         
     | 
| 
       189 
256 
     | 
    
         | 
| 
       190 
     | 
    
         
            -
                      Class.new(:: 
     | 
| 
      
 257 
     | 
    
         
            +
                      input_klass = Class.new(::GraphQL::Schema::InputObject) do
         
     | 
| 
       191 
258 
     | 
    
         
             
                        graphql_name "#{scoped_graphql_name}Input"
         
     | 
| 
       192 
259 
     | 
    
         | 
| 
       193 
260 
     | 
    
         
             
                        with_model scoped_model_name, excluded_inputs: scoped_excluded_inputs, **options
         
     | 
| 
       194 
261 
     | 
    
         | 
| 
       195 
262 
     | 
    
         
             
                        class_eval(&block) if block
         
     | 
| 
       196 
263 
     | 
    
         
             
                      end
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
                      remove_const(:Input) if const_defined?(:Input, false)
         
     | 
| 
      
 266 
     | 
    
         
            +
                      const_set(:Input, input_klass)
         
     | 
| 
       197 
267 
     | 
    
         
             
                    end
         
     | 
| 
       198 
268 
     | 
    
         | 
| 
       199 
269 
     | 
    
         
             
                    def sort_fields_enum=(fields)
         
     | 
| 
       200 
270 
     | 
    
         
             
                      @sort_fields_enum ||= Class.new(::HQ::GraphQL::Enum::SortBy).tap do |c|
         
     | 
| 
       201 
271 
     | 
    
         
             
                        c.graphql_name "#{graphql_name}Sort"
         
     | 
| 
      
 272 
     | 
    
         
            +
                        const_set(:Sort, c)
         
     | 
| 
       202 
273 
     | 
    
         
             
                      end
         
     | 
| 
       203 
274 
     | 
    
         | 
| 
       204 
275 
     | 
    
         
             
                      Array(fields).each do |field|
         
     | 
| 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require "hq/graphql/ext/mutation_extensions"
         
     | 
| 
       3 
4 
     | 
    
         
             
            require "hq/graphql/inputs"
         
     | 
| 
       4 
     | 
    
         
            -
            require "hq/graphql/mutation"
         
     | 
| 
       5 
5 
     | 
    
         
             
            require "hq/graphql/types"
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
            module HQ
         
     | 
| 
         @@ -130,15 +130,16 @@ module HQ 
     | 
|
| 
       130 
130 
     | 
    
         
             
                    def build_mutation(action:, require_primary_key: false, nil_klass: false, &block)
         
     | 
| 
       131 
131 
     | 
    
         
             
                      gql_name = "#{graphql_name}#{action.to_s.titleize}"
         
     | 
| 
       132 
132 
     | 
    
         
             
                      scoped_model_name = model_name
         
     | 
| 
       133 
     | 
    
         
            -
             
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                      klass = Class.new(::GraphQL::Schema::Mutation) do
         
     | 
| 
       134 
135 
     | 
    
         
             
                        graphql_name gql_name
         
     | 
| 
       135 
136 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
                        define_method(:ready?) do  
     | 
| 
       137 
     | 
    
         
            -
                          super( 
     | 
| 
      
 137 
     | 
    
         
            +
                        define_method(:ready?) do |**args|
         
     | 
| 
      
 138 
     | 
    
         
            +
                          super(**args) && ::HQ::GraphQL.authorized?(action, scoped_model_name, context)
         
     | 
| 
       138 
139 
     | 
    
         
             
                        end
         
     | 
| 
       139 
140 
     | 
    
         | 
| 
       140 
141 
     | 
    
         
             
                        lazy_load do
         
     | 
| 
       141 
     | 
    
         
            -
                          field :errors, :: 
     | 
| 
      
 142 
     | 
    
         
            +
                          field :errors, ::GraphQL::Types::JSON, null: false
         
     | 
| 
       142 
143 
     | 
    
         
             
                          field :resource, ::HQ::GraphQL::Types[scoped_model_name, nil_klass], null: true
         
     | 
| 
       143 
144 
     | 
    
         
             
                        end
         
     | 
| 
       144 
145 
     | 
    
         | 
| 
         @@ -156,6 +157,8 @@ module HQ 
     | 
|
| 
       156 
157 
     | 
    
         
             
                          resource.errors.to_h.deep_transform_keys { |k| k.to_s.camelize(:lower) }
         
     | 
| 
       157 
158 
     | 
    
         
             
                        end
         
     | 
| 
       158 
159 
     | 
    
         
             
                      end
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                      const_set(gql_name, klass)
         
     | 
| 
       159 
162 
     | 
    
         
             
                    end
         
     | 
| 
       160 
163 
     | 
    
         
             
                  end
         
     | 
| 
       161 
164 
     | 
    
         
             
                end
         
     | 
| 
         @@ -2,13 +2,13 @@ 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            module HQ
         
     | 
| 
       4 
4 
     | 
    
         
             
              module GraphQL
         
     | 
| 
       5 
     | 
    
         
            -
                class RootQuery < :: 
     | 
| 
      
 5 
     | 
    
         
            +
                class RootQuery < ::GraphQL::Schema::Object
         
     | 
| 
       6 
6 
     | 
    
         
             
                  def self.inherited(base)
         
     | 
| 
       7 
7 
     | 
    
         
             
                    super
         
     | 
| 
       8 
8 
     | 
    
         
             
                    base.class_eval do
         
     | 
| 
       9 
9 
     | 
    
         
             
                      lazy_load do
         
     | 
| 
       10 
     | 
    
         
            -
                        ::HQ::GraphQL.root_queries.each do | 
     | 
| 
       11 
     | 
    
         
            -
                          field field_name, resolver: resolver.call, klass: model_name
         
     | 
| 
      
 10 
     | 
    
         
            +
                        ::HQ::GraphQL.root_queries.each do |query|
         
     | 
| 
      
 11 
     | 
    
         
            +
                          field query[:field_name], resolver: query[:resolver].call, klass: query[:model_name]
         
     | 
| 
       12 
12 
     | 
    
         
             
                        end
         
     | 
| 
       13 
13 
     | 
    
         
             
                      end
         
     | 
| 
       14 
14 
     | 
    
         
             
                    end
         
     | 
    
        data/lib/hq/graphql/scalars.rb
    CHANGED
    
    
    
        data/lib/hq/graphql/types.rb
    CHANGED
    
    | 
         @@ -1,6 +1,5 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require "hq/graphql/types/object"
         
     | 
| 
       4 
3 
     | 
    
         
             
            require "hq/graphql/types/uuid"
         
     | 
| 
       5 
4 
     | 
    
         | 
| 
       6 
5 
     | 
    
         
             
            module HQ
         
     | 
| 
         @@ -35,7 +34,7 @@ module HQ 
     | 
|
| 
       35 
34 
     | 
    
         
             
                      when :uuid
         
     | 
| 
       36 
35 
     | 
    
         
             
                        ::HQ::GraphQL::Types::UUID
         
     | 
| 
       37 
36 
     | 
    
         
             
                      when :json, :jsonb
         
     | 
| 
       38 
     | 
    
         
            -
                        :: 
     | 
| 
      
 37 
     | 
    
         
            +
                        ::GraphQL::Types::JSON
         
     | 
| 
       39 
38 
     | 
    
         
             
                      when :integer
         
     | 
| 
       40 
39 
     | 
    
         
             
                        ::GraphQL::Types::Int
         
     | 
| 
       41 
40 
     | 
    
         
             
                      when :decimal
         
     | 
| 
         @@ -1,5 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require "hq/graphql/util"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            module HQ
         
     | 
| 
       4 
6 
     | 
    
         
             
              module GraphQL
         
     | 
| 
       5 
7 
     | 
    
         
             
                module Types
         
     | 
| 
         @@ -18,16 +20,12 @@ module HQ 
     | 
|
| 
       18 
20 
     | 
    
         
             
                      private
         
     | 
| 
       19 
21 
     | 
    
         | 
| 
       20 
22 
     | 
    
         
             
                      def validate_and_return_uuid(value)
         
     | 
| 
       21 
     | 
    
         
            -
                        if validate_uuid(value)
         
     | 
| 
      
 23 
     | 
    
         
            +
                        if ::HQ::GraphQL::Util.validate_uuid(value)
         
     | 
| 
       22 
24 
     | 
    
         
             
                          value
         
     | 
| 
       23 
25 
     | 
    
         
             
                        else
         
     | 
| 
       24 
26 
     | 
    
         
             
                          raise ::GraphQL::CoercionError, "#{value.inspect} is not a valid UUID"
         
     | 
| 
       25 
27 
     | 
    
         
             
                        end
         
     | 
| 
       26 
28 
     | 
    
         
             
                      end
         
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
                      def validate_uuid(value)
         
     | 
| 
       29 
     | 
    
         
            -
                        !!value.to_s.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
         
     | 
| 
       30 
     | 
    
         
            -
                      end
         
     | 
| 
       31 
29 
     | 
    
         
             
                    end
         
     | 
| 
       32 
30 
     | 
    
         
             
                  end
         
     | 
| 
       33 
31 
     | 
    
         
             
                end
         
     |