class GraphQL::ActiveRecordExtensions::Field

Public Class Methods

generate(model:, resolve_type:, name: nil) click to toggle source
# File lib/graphql/active_record_extensions/field.rb, line 8
def self.generate(model:, resolve_type:, name: nil)
  n = name || model.name.underscore
  GraphQL::Field.define do
    name(n)
    type(resolve_type)
    description("Find a #{model.name} by ID/UUID")

    argument :id, !GraphQL::ID_TYPE
    argument :use_uuid, GraphQL::BOOLEAN_TYPE

    resolve -> (object, arguments, context) do
      GraphQL::ActiveRecordExtensions::Field.resolve_it(model, object, arguments, context)
    end
  end
end
resolve_it(model, object, arguments, ctx) click to toggle source

override of GraphQL::Field.resolve basically just provides the final object (in this case an AR model) can check RDoc for graphql-ruby gem

@param object not used @param arguments List List of Arguments for the field (i.e. id, uuid, etc.) @param ctx [GraphQL::Context] the context of the GraphQL query

@return eager-loaded ActiveRecord model

# File lib/graphql/active_record_extensions/field.rb, line 34
def self.resolve_it(model, object, arguments, ctx)
  includes = map_includes(model, ctx.ast_node.selections, ctx)

  model_with_includes = include_in_model(model, includes)

  if arguments['use_uuid']
    model_with_includes.find_by_uuid(arguments['id'])
  else
    model_with_includes.find(arguments['id'])
  end

  rescue ActiveRecord::ConfigurationError => e
    model.find(arguments['id'])
end

Private Class Methods

handle_fragments(node, ctx) click to toggle source

method to handle fragments in GQL Query works by finding the fragment reference in the context

@param node [GraphQL::Language::Nodes::*] current node @param ctx [GraphQL::Context] GQL Context used to map fragment to node

@return [String, GraphQL::Language::Nodes::*] tuple of node name and node

# File lib/graphql/active_record_extensions/field.rb, line 128
def self.handle_fragments(node, ctx)
  if node.is_a?(GraphQL::Language::Nodes::FragmentSpread)
    fragment = ctx.query.fragments[node.name]

    return [ fragment.type.underscore, fragment ] if fragment.type.is_a?(String)

    [fragment.type.name.underscore, fragment]
  elsif node.is_a?(GraphQL::Language::Nodes::InlineFragment)
    ##
    # avoid auto-including things in InlineFragments
    # If using an interface, not all fields are guaranteed to
    # exist in all of the implementing types, but at this point,
    # we can't tell if it will, because we don't know what type
    # we are dealing with.
    ##
    [nil, nil]
  else
    [node.name, node]
  end
end
include_in_model(model, includes) click to toggle source
# File lib/graphql/active_record_extensions/field.rb, line 51
def self.include_in_model(model, includes)
  includes.present? ? model.includes(*includes) : model
end
map_includes(model, selections, ctx) click to toggle source

generates an array for use in AR::QueryMethods.includes allows for nested includes as well supports fragments

@param model [ActiveRecord::Base] an ActiveRecord model @param selections [GraphQL::Selection] selections on the current model @param ctx [GraphQL::Context] the context of the GraphQL query. Used to map fragments to models

@return Array of tables to include in the query

# File lib/graphql/active_record_extensions/field.rb, line 65
def self.map_includes(model, selections, ctx)
  selections.map do |selection|

    table_name, node = handle_fragments(selection, ctx)

    if node.present? && node.selections.present?
      singular = table_name.singularize
      plural   = table_name.pluralize

      # make sure that the next model is an ActiveRecord model
      next_model = singular.to_s.classify.safe_constantize

      if next_model.blank? || !(next_model < ActiveRecord::Base)
        next_model = model
      end

      nested = map_includes(next_model, node.selections, ctx)

      singular_reflection = model.reflections[singular] || model.reflections[singular.to_sym]
      plural_reflection = model.reflections[plural] || model.reflections[plural.to_sym]

      final_type =  if valid_association(singular_reflection)
                      # this is for has_one relationships
                      singular

                    elsif valid_association(plural_reflection)
                      # this is for has_many relationships
                      plural

                    else
                      # this is for non-AR models (i.e. Product)
                      # so they use the correct parent
                      # TODO Try to get possible_types and include those
                      nil
                    end

      if nested.present?
        if final_type
          # flatten is used for cases like Interfaces
          # i.e. Customer -> Product -> Loan
          # Product isn't an AR type
          { final_type => nested.flatten }
        else
          nested.compact
        end

      else
        final_type
      end
    else
      nil
    end
  end.flatten.compact
end
valid_association(reflection) click to toggle source

Method to check whether a reflection is valid. Some things, like PowerEnum, have reflections but do not work properly because they do not have an association_class. So ignore them.

@param reflection [ActiveRecord::Associations::* | any | nil] Inputted Association @return [boolean] Whether or not the association is valid

# File lib/graphql/active_record_extensions/field.rb, line 156
def self.valid_association(reflection)
  return false if reflection.blank?
  return false unless reflection.respond_to?(:association_class)
  return (reflection.association_class < ActiveRecord::Associations::Association)
end