I was following an absolutely stellar tutorial on building a GraphQL API on https://www.howtographql.com/graphql-ruby/0-introduction/
My API framework is Grape, and Mongoid is my Database framework but works very similar to rails because it uses ActiveRecord
After getting my API endpoint working and defining some models on an existing REST API I realized I am not able to write methods and filters on child records (array). My sense is that if I add a function
argument to the cost_codes
field in the JOBDATA::Types::JobType
class that it will resolve arguments, however because of the load order this is impossible. In other frameworks to resolve this sort of issue, you can wrap your class in a string so it loads on demand after the application has been loaded, but thats not supported by graphql.
updated : I can resolve the load issue with require relative, but then my search class no longer filters the child elements of the parent, instead it calls JOBDATA::CostCode.all
and tries to load the entire collection
## this works on the parent, I have a custom method "orderBy"
{
allJobs(orderBy: ["jobNumber:asc"]) {
id,
createdAt,
updatedAt,
jobNumber,
description,
status,
costCodes { ## <-- by default returns the order of cost codes on the database
id,
createdAt,
updatedAt,
costCode,
description
}
}
}
what I need to do
{
allJobs(orderBy: ["jobNumber:asc"]) {
id,
createdAt,
updatedAt,
jobNumber,
description,
status,
costCodes(orderBy: ["costCode:asc"]) {
id,
createdAt,
updatedAt,
costCode,
description
}
}
}
Code:
Files are loaded in the following order
module JOBDATA
## . . .
## GraphQL Files
Dir.glob('./graphql/types/*.rb') { |file| load file }
Dir.glob('./graphql/search_filters/*.rb') { |file| load file }
Dir.glob('./graphql/resolvers/*.rb') { |file| load file }
Dir.glob('./graphql/schemas/*.rb') { |file| load file }
end
First to load is './graphql/types/cost_code_type.rb'
module JOBDATA
module Types
class CostCodeType < GraphQL::Introspection::BaseObject
field :id, GraphQL::Types::ID, null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
field :cost_code, GraphQL::Types::String, null: false
field :description, GraphQL::Types::String, null: true
field :status, GraphQL::Types::String, null: false
end
end
end
Second './graphql/types/job_type.rb'
module JOBDATA
module Types
class JobType < GraphQL::Introspection::BaseObject
field :id, GraphQL::Types::ID, null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
field :job_number, GraphQL::Types::String, null: false
field :description, GraphQL::Types::String, null: true
field :status, GraphQL::Types::String, null: false
field :cost_codes, [JOBDATA::Types::CostCodeType], null: false
## this seems like it should work - but because of the required load order I
## I can force it using require relative, but rather than returning only the
## parents children it returns everything for that collection agnostic to the
## parent (its connected via has_many relationship on the parent)
# field :cost_codes, [JOBDATA::Types::CostCodeType], null: false, function: JOBDATA::SearchFilters::CostCode
end
end
end
Third './graphql/search_filters/cost_code_search_filter.rb'
module JOBDATA
module SearchFilters
class CostCode
include SearchObject.module(:graphql)
scope { JOBDATA::CostCode.all }
type types[JOBDATA::Types::CostCodeType]
option :orderBy, type: types[types.String], with: :apply_order_by, default: ['cost_code:asc']
def apply_order_by(scope, value)
order_by = []
value.each do |field|
pair = field.split(':')
order_by.push([pair[0].underscore, pair[1].downcase])
end
scope.order_by(order_by)
end
end
end
end
Fourth './graphql/search_filters/job_search_filter.rb'
module JOBDATA
module SearchFilters
class Job
include SearchObject.module(:graphql)
scope { JOBDATA::Job.all }
type types[JOBDATA::Types::JobType]
option :orderBy, type: types[types.String], with: :apply_order_by, default: ['job_number:asc']
def apply_order_by(scope, value)
order_by = []
value.each do |field|
pair = field.split(':')
order_by.push([pair[0].underscore, pair[1].downcase])
end
scope.order_by(order_by)
end
end
end
end