module HQ::GraphQL::Resource::ClassMethods

Attributes

graphql_name[W]
model_name[W]

Public Instance Methods

const_missing(constant_name) click to toggle source
Calls superclass method
# File lib/hq/graphql/resource.rb, line 88
def const_missing(constant_name)
  constant_name = constant_name.to_sym
  case constant_name
  when :Query
    query_object
  when :NilQuery
    nil_query_object
  when :Input
    input_klass
  when :FilterInput
    filter_input
  when :FilterFields
    filter_fields_enum
  else
    super
  end
end
filter_fields_enum() click to toggle source
# File lib/hq/graphql/resource.rb, line 122
def filter_fields_enum
  @filter_fields_enum ||= begin
    scoped_self = self

    enum_class = Class.new(::GraphQL::Schema::Enum) do
      graphql_name "#{scoped_self.graphql_name}FilterFields"

      lazy_load do
        scoped_self.model_klass.columns.sort_by(&:name).each do |column|
          next unless HQ::GraphQL::Filters.supported?(column)
          value column.name.camelize(:lower), value: column
        end
      end
    end

    const_set(:FilterFields, enum_class)
  end
end
filter_input() click to toggle source
# File lib/hq/graphql/resource.rb, line 106
def filter_input
  @filter_input ||= begin
    scoped_self = self

    input_class = Class.new(::GraphQL::Schema::InputObject) do
      graphql_name "#{scoped_self.graphql_name}FilterInput"

      argument :field, scoped_self.filter_fields_enum, required: true
      argument :operation, Enum::FilterOperation, required: true
      argument :value, String, required: true
    end

    const_set(:FilterInput, input_class)
  end
end
find_record(attrs, context) click to toggle source
# File lib/hq/graphql/resource.rb, line 35
def find_record(attrs, context)
  primary_key = model_klass.primary_key.to_sym
  primary_key_value = attrs[primary_key]
  scope(context).find_by(primary_key => primary_key_value)
end
graphql_name() click to toggle source
# File lib/hq/graphql/resource.rb, line 45
def graphql_name
  @graphql_name || model_name.demodulize
end
input_klass() click to toggle source
# File lib/hq/graphql/resource.rb, line 61
def input_klass
  @input_klass ||= build_input_object
end
model_klass() click to toggle source
# File lib/hq/graphql/resource.rb, line 53
def model_klass
  @model_klass ||= model_name&.safe_constantize
end
model_name() click to toggle source
# File lib/hq/graphql/resource.rb, line 49
def model_name
  @model_name || ::HQ::GraphQL.extract_class(self)
end
mutation_klasses() click to toggle source
# File lib/hq/graphql/resource.rb, line 57
def mutation_klasses
  @mutation_klasses ||= {}.with_indifferent_access
end
new_record(context) click to toggle source
# File lib/hq/graphql/resource.rb, line 41
def new_record(context)
  scope(context).new
end
nil_query_object() click to toggle source
# File lib/hq/graphql/resource.rb, line 65
def nil_query_object
  @nil_query_object ||= const_set(:NilQuery, build_graphql_object(name: "#{graphql_name}Copy", auto_nil: false))
end
query_object() click to toggle source
# File lib/hq/graphql/resource.rb, line 69
def query_object
  @query_object ||= begin
    qo =
      if @query_object_options
        options, block = @query_object_options
        @query_object_options = nil
        build_graphql_object(**options, &block)
      else
        build_graphql_object
      end
    remove_const(:Query) if const_defined?(:Query, false)
    const_set(:Query, qo)
  end
end
scope(context) click to toggle source
# File lib/hq/graphql/resource.rb, line 29
def scope(context)
  scope = model_klass
  scope = ::HQ::GraphQL.default_scope(scope, context)
  @default_scope&.call(scope, context) || scope
end
sort_fields_enum() click to toggle source
# File lib/hq/graphql/resource.rb, line 84
def sort_fields_enum
  @sort_fields_enum || ::HQ::GraphQL::Enum::SortBy
end

Protected Instance Methods

def_root(field_name, is_array: false, null: true, &block) click to toggle source
# File lib/hq/graphql/resource.rb, line 174
def def_root(field_name, is_array: false, null: true, &block)
  resource = self
  resolver = -> {
    klass = Class.new(::GraphQL::Schema::Resolver) do
      type = is_array ? [resource.query_object] : resource.query_object
      type type, null: null
      class_eval(&block) if block
    end

    constant_name = "#{field_name.to_s.classify}Resolver"
    resource.send(:remove_const, constant_name) if resource.const_defined?(constant_name, false)
    resource.const_set(constant_name, klass)
  }
  ::HQ::GraphQL.root_queries << {
    field_name: field_name, resolver: resolver, model_name: model_name
  }
end
default_scope(&block) click to toggle source
# File lib/hq/graphql/resource.rb, line 143
def default_scope(&block)
  @default_scope = block
end
excluded_inputs(*fields) click to toggle source
# File lib/hq/graphql/resource.rb, line 170
def excluded_inputs(*fields)
  @excluded_inputs = fields
end
input(**options, &block) click to toggle source
# File lib/hq/graphql/resource.rb, line 147
def input(**options, &block)
  @input_klass = build_input_object(**options, &block)
end
mutations(create: true, copy: true, update: true, destroy: true) click to toggle source
# File lib/hq/graphql/resource.rb, line 151
def mutations(create: true, copy: true, update: true, destroy: true)
  mutation_klasses["create_#{graphql_name.underscore}"] = build_create if create
  mutation_klasses["copy_#{graphql_name.underscore}"] = build_copy if copy
  mutation_klasses["update_#{graphql_name.underscore}"] = build_update if update
  mutation_klasses["destroy_#{graphql_name.underscore}"] = build_destroy if destroy
end
query(**options, &block) click to toggle source
# File lib/hq/graphql/resource.rb, line 158
def query(**options, &block)
  @query_object_options = [options, block]
end
query_class(klass) click to toggle source
# File lib/hq/graphql/resource.rb, line 162
def query_class(klass)
  @query_class = klass
end
root_query(find_one: true, find_all: true, pagination: true, limit_max: 250) click to toggle source
# File lib/hq/graphql/resource.rb, line 192
def root_query(find_one: true, find_all: true, pagination: true, limit_max: 250)
  field_name = graphql_name.underscore
  scoped_self = self

  if find_one
    def_root field_name, is_array: false, null: true do
      klass = scoped_self.model_klass
      primary_key = klass.primary_key

      argument primary_key, ::GraphQL::Types::ID, required: true

      define_method(:resolve) do |**attrs|
        scoped_self.find_record(attrs, context)
      end
    end
  end

  if find_all
    def_root field_name.pluralize, is_array: true, null: false do
      extension FieldExtension::PaginatedArguments, klass: scoped_self.model_klass if pagination
      argument :filters, [scoped_self.filter_input], required: false

      define_method(:resolve) do |filters: nil, limit: nil, offset: nil, sort_by: nil, sort_order: nil, **_attrs|
        filters_scope = ::HQ::GraphQL::Filters.new(filters, scoped_self.model_klass)
        filters_scope.validate!

        scope = scoped_self.scope(context).all.merge(filters_scope.to_scope)

        if pagination || page || limit
          offset = [0, *offset].max
          limit = [[limit_max, *limit].min, 0].max
          scope = scope.limit(limit).offset(offset)
        end

        sort_by ||= :updated_at
        sort_order ||= :desc
        # There should be no risk for SQL injection since an enum is being used for both sort_by and sort_order
        scope = scope.reorder(sort_by => sort_order)

        scope
      end
    end
  end
end
sort_fields(*fields) click to toggle source
# File lib/hq/graphql/resource.rb, line 166
def sort_fields(*fields)
  self.sort_fields_enum = fields
end

Private Instance Methods

build_graphql_object(name: graphql_name, **options, &block) click to toggle source
# File lib/hq/graphql/resource.rb, line 239
def build_graphql_object(name: graphql_name, **options, &block)
  scoped_graphql_name = name
  scoped_model_name = model_name
  object_class = @query_class || ::HQ::GraphQL.default_object_class || ::GraphQL::Schema::Object
  Class.new(object_class) do
    graphql_name scoped_graphql_name

    with_model scoped_model_name, **options

    class_eval(&block) if block
  end
end
build_input_object(**options, &block) click to toggle source
# File lib/hq/graphql/resource.rb, line 252
def build_input_object(**options, &block)
  scoped_graphql_name = graphql_name
  scoped_model_name = model_name
  scoped_excluded_inputs = @excluded_inputs || []

  input_klass = Class.new(::GraphQL::Schema::InputObject) do
    graphql_name "#{scoped_graphql_name}Input"

    with_model scoped_model_name, excluded_inputs: scoped_excluded_inputs, **options

    class_eval(&block) if block
  end

  remove_const(:Input) if const_defined?(:Input, false)
  const_set(:Input, input_klass)
end
sort_fields_enum=(fields) click to toggle source
# File lib/hq/graphql/resource.rb, line 269
def sort_fields_enum=(fields)
  @sort_fields_enum ||= Class.new(::HQ::GraphQL::Enum::SortBy).tap do |c|
    c.graphql_name "#{graphql_name}Sort"
    const_set(:Sort, c)
  end

  Array(fields).each do |field|
    @sort_fields_enum.value field.to_s.classify, value: field
  end
end