First working version of new GraphQL::TypeCreator after major refactoring
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
ConsulSchema = GraphQL::Schema.define do
|
||||
query QueryRoot
|
||||
|
||||
# Reject deeply-nested queries
|
||||
max_depth 7
|
||||
|
||||
resolve_type -> (object, ctx) {
|
||||
# look up types by class name
|
||||
type_name = object.class.name
|
||||
ConsulSchema.types[type_name]
|
||||
}
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
class ExposableAssociation
|
||||
attr_reader :name, :type
|
||||
|
||||
def initialize(association)
|
||||
@name = association.name
|
||||
@type = association.macro # :has_one, :belongs_to or :has_many
|
||||
end
|
||||
end
|
||||
@@ -1,26 +0,0 @@
|
||||
class ExposableField
|
||||
attr_reader :name, :graphql_type
|
||||
|
||||
def initialize(column, options = {})
|
||||
@name = column.name
|
||||
@graphql_type = ExposableField.convert_type(column.type)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return a GraphQL type for 'database_type'
|
||||
def self.convert_type(database_type)
|
||||
case database_type
|
||||
when :integer
|
||||
GraphQL::INT_TYPE
|
||||
when :boolean
|
||||
GraphQL::BOOLEAN_TYPE
|
||||
when :float
|
||||
GraphQL::FLOAT_TYPE
|
||||
when :double
|
||||
GraphQL::FLOAT_TYPE
|
||||
else
|
||||
GraphQL::STRING_TYPE
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,36 +0,0 @@
|
||||
class ExposableModel
|
||||
attr_reader :name, :description, :exposed_fields, :exposed_associations
|
||||
|
||||
def initialize(model_class, options = {})
|
||||
@name = model_class.name
|
||||
@description = model_class.model_name.human
|
||||
@field_filter_list = options[:field_filter_list] || []
|
||||
@assoc_filter_list = options[:assoc_filter_list] || []
|
||||
@filter_strategy = options[:filter_strategy]
|
||||
set_exposed_items(model_class)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# determine which model fields and associations are exposed to the API
|
||||
def set_exposed_items(model_class)
|
||||
@exposed_fields = check_against_safety_list(model_class.columns, @field_filter_list)
|
||||
@exposed_associations = check_against_safety_list(model_class.reflect_on_all_associations, @assoc_filter_list)
|
||||
end
|
||||
|
||||
def check_against_safety_list(all_items, safety_list)
|
||||
case @filter_strategy
|
||||
when :whitelist
|
||||
exposed_items = all_items.select do |column|
|
||||
safety_list.include? column.name.to_s # works for both symbols and strings
|
||||
end
|
||||
when :blacklist
|
||||
exposed_items = all_items.select do |column|
|
||||
!(safety_list.include? column.name.to_s)
|
||||
end
|
||||
else
|
||||
exposed_items = []
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,65 +0,0 @@
|
||||
QueryRoot = GraphQL::ObjectType.define do
|
||||
name "Query"
|
||||
description "The query root for this schema"
|
||||
|
||||
field :proposal do
|
||||
type TYPE_BUILDER.types[Proposal]
|
||||
description "Find a Proposal by id"
|
||||
argument :id, !types.ID
|
||||
resolve -> (object, arguments, context) {
|
||||
Proposal.find(arguments["id"])
|
||||
}
|
||||
end
|
||||
|
||||
field :proposals do
|
||||
type types[TYPE_BUILDER.types[Proposal]]
|
||||
description "Find all Proposals"
|
||||
resolve -> (object, arguments, context) {
|
||||
Proposal.all
|
||||
}
|
||||
end
|
||||
|
||||
field :debate do
|
||||
type TYPE_BUILDER.types[Debate]
|
||||
description "Find a Debate by id"
|
||||
argument :id, !types.ID
|
||||
resolve -> (object, arguments, context) {
|
||||
Debate.find(arguments["id"])
|
||||
}
|
||||
end
|
||||
|
||||
field :debates do
|
||||
type types[TYPE_BUILDER.types[Debate]]
|
||||
description "Find all Debates"
|
||||
resolve -> (object, arguments, context) {
|
||||
Debate.all
|
||||
}
|
||||
end
|
||||
|
||||
field :comment do
|
||||
type TYPE_BUILDER.types[Comment]
|
||||
description "Find a Comment by id"
|
||||
argument :id, !types.ID
|
||||
resolve -> (object, arguments, context) {
|
||||
Comment.find(arguments["id"])
|
||||
}
|
||||
end
|
||||
|
||||
field :comments do
|
||||
type types[TYPE_BUILDER.types[Comment]]
|
||||
description "Find all Comments"
|
||||
resolve -> (object, arguments, context) {
|
||||
Comment.all
|
||||
}
|
||||
end
|
||||
|
||||
field :user do
|
||||
type TYPE_BUILDER.types[User]
|
||||
description "Find a User by id"
|
||||
argument :id, !types.ID
|
||||
resolve -> (object, arguments, context) {
|
||||
User.find(arguments["id"])
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,89 +0,0 @@
|
||||
class TypeBuilder
|
||||
attr_reader :filter_strategy, :graphql_models
|
||||
attr_accessor :graphql_types # contains all generated GraphQL types
|
||||
|
||||
def initialize(graphql_models, options = {})
|
||||
@graphql_models = graphql_models
|
||||
@graphql_types = {}
|
||||
|
||||
# determine filter strategy for this field
|
||||
if (options[:filter_strategy] == :blacklist)
|
||||
@filter_strategy = :blacklist
|
||||
else
|
||||
@filter_strategy = :whitelist
|
||||
end
|
||||
|
||||
create_all_types
|
||||
end
|
||||
|
||||
def types
|
||||
@graphql_types
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_all_types
|
||||
@graphql_models.keys.each do |model_class|
|
||||
@graphql_types[model_class] = create_type(model_class)
|
||||
end
|
||||
end
|
||||
|
||||
def create_type(model_class)
|
||||
type_builder = self
|
||||
|
||||
graphql_type = GraphQL::ObjectType.define do
|
||||
em = ExposableModel.new(
|
||||
model_class,
|
||||
filter_strategy: type_builder.filter_strategy,
|
||||
field_filter_list: type_builder.graphql_models[model_class][:fields],
|
||||
assoc_filter_list: type_builder.graphql_models[model_class][:associations]
|
||||
)
|
||||
|
||||
name(em.name)
|
||||
description(em.description)
|
||||
|
||||
em.exposed_fields.each do |column|
|
||||
ef = ExposableField.new(column)
|
||||
field(ef.name, ef.graphql_type) # returns a GraphQL::Field
|
||||
end
|
||||
|
||||
em.exposed_associations.each do |association|
|
||||
ea = ExposableAssociation.new(association)
|
||||
if ea.type.in? [:has_one, :belongs_to]
|
||||
field(ea.name, -> { type_builder.graphql_types[association.klass] })
|
||||
elsif ea.type.in? [:has_many]
|
||||
field(ea.name, -> {
|
||||
types[type_builder.graphql_types[association.klass]]
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return graphql_type
|
||||
end
|
||||
end
|
||||
|
||||
graphql_models = {}
|
||||
|
||||
graphql_models.store(User, {
|
||||
fields: ['id', 'username'],
|
||||
associations: ['proposals', 'debates']
|
||||
})
|
||||
graphql_models.store(Proposal, {
|
||||
fields: ['id', 'title', 'description', 'author_id', 'created_at'],
|
||||
associations: ['author']
|
||||
})
|
||||
graphql_models.store(Debate, {
|
||||
fields: ['id', 'title', 'description', 'author_id', 'created_at'],
|
||||
associations: ['author']
|
||||
})
|
||||
graphql_models.store(Comment, {
|
||||
fields: ['id', 'commentable_id', 'commentable_type', 'body'],
|
||||
associations: ['author']
|
||||
})
|
||||
graphql_models.store(Geozone, {
|
||||
fields: ['id', 'name', 'html_map_coordinates'],
|
||||
associations: []
|
||||
})
|
||||
|
||||
TYPE_BUILDER = TypeBuilder.new(graphql_models, filter_strategy: :whitelist)
|
||||
@@ -44,9 +44,6 @@ module Consul
|
||||
config.autoload_paths << "#{Rails.root}/app/controllers/custom"
|
||||
config.autoload_paths << "#{Rails.root}/app/models/custom"
|
||||
config.paths['app/views'].unshift(Rails.root.join('app', 'views', 'custom'))
|
||||
|
||||
# Add GraphQL directories to the autoload path
|
||||
config.autoload_paths << Rails.root.join('app', 'graph')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
46
config/initializers/graphql.rb
Normal file
46
config/initializers/graphql.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
API_TYPE_DEFINITIONS = {
|
||||
User => %I[ id username ],
|
||||
Proposal => %I[ id title description author_id author created_at ]
|
||||
}
|
||||
|
||||
api_types = {}
|
||||
|
||||
API_TYPE_DEFINITIONS.each do |model, fields|
|
||||
api_types[model] = GraphQL::TypeCreator.create(model, fields, api_types)
|
||||
end
|
||||
|
||||
ConsulSchema = GraphQL::Schema.define do
|
||||
query QueryRoot
|
||||
|
||||
# Reject deeply-nested queries
|
||||
max_depth 7
|
||||
|
||||
resolve_type -> (object, ctx) {
|
||||
# look up types by class name
|
||||
type_name = object.class.name
|
||||
ConsulSchema.types[type_name]
|
||||
}
|
||||
end
|
||||
|
||||
QueryRoot = GraphQL::ObjectType.define do
|
||||
name "Query"
|
||||
description "The query root for this schema"
|
||||
|
||||
field :proposal do
|
||||
type api_types[Proposal]
|
||||
description "Find a Proposal by id"
|
||||
argument :id, !types.ID
|
||||
resolve -> (object, arguments, context) {
|
||||
Proposal.find(arguments["id"])
|
||||
}
|
||||
end
|
||||
|
||||
field :proposals do
|
||||
type types[api_types[Proposal]]
|
||||
description "Find all Proposals"
|
||||
resolve -> (object, arguments, context) {
|
||||
Proposal.all
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
42
lib/graph_ql/type_creator.rb
Normal file
42
lib/graph_ql/type_creator.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
module GraphQL
|
||||
class TypeCreator
|
||||
|
||||
# Return a GraphQL type for a 'database_type'
|
||||
TYPES_CONVERSION = Hash.new(GraphQL::STRING_TYPE).merge(
|
||||
integer: GraphQL::INT_TYPE,
|
||||
boolean: GraphQL::BOOLEAN_TYPE,
|
||||
float: GraphQL::FLOAT_TYPE,
|
||||
double: GraphQL::FLOAT_TYPE
|
||||
)
|
||||
|
||||
def self.create(model, field_names, api_types)
|
||||
|
||||
new_graphql_type = GraphQL::ObjectType.define do
|
||||
|
||||
name(model.name)
|
||||
description("Generated programmatically from model: #{model.name}")
|
||||
|
||||
# Make a field for each column
|
||||
field_names.each do |field_name|
|
||||
if model.column_names.include?(field_name.to_s)
|
||||
field(field_name.to_s, TYPES_CONVERSION[field_name])
|
||||
else
|
||||
association = model.reflect_on_all_associations.find { |a| a.name == field_name }
|
||||
case association.macro
|
||||
when :has_one
|
||||
field(association.name, -> { api_types[association.klass] })
|
||||
when :belongs_to
|
||||
field(association.name, -> { api_types[association.klass] })
|
||||
when :has_many
|
||||
connection(association.name, api_types[association.klass].connection_type {
|
||||
description "Description for a field with pagination"
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return new_graphql_type
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user