First working version of new GraphQL::TypeCreator after major refactoring

This commit is contained in:
Alberto Miedes Garcés
2016-11-10 20:53:15 +01:00
parent 49317e2c11
commit 59a355df1b
9 changed files with 88 additions and 239 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View 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

View 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