Added support for associations (WITHOUT PAGINATION)
This commit is contained in:
8
app/graph/exposable_association.rb
Normal file
8
app/graph/exposable_association.rb
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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,30 +1,35 @@
|
|||||||
class ExposableModel
|
class ExposableModel
|
||||||
attr_reader :exposed_fields, :name, :description
|
attr_reader :name, :description, :exposed_fields, :exposed_associations
|
||||||
|
|
||||||
def initialize(model_class, options = {})
|
def initialize(model_class, options = {})
|
||||||
@model_class = model_class
|
|
||||||
@filter_list = options[:filter_list] || []
|
|
||||||
@name = model_class.name
|
@name = model_class.name
|
||||||
@description = model_class.model_name.human
|
@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]
|
@filter_strategy = options[:filter_strategy]
|
||||||
set_exposed_fields
|
set_exposed_items(model_class)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# determine which model fields are exposed to the API
|
# determine which model fields and associations are exposed to the API
|
||||||
def set_exposed_fields
|
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
|
case @filter_strategy
|
||||||
when :whitelist
|
when :whitelist
|
||||||
@exposed_fields = @model_class.columns.select do |column|
|
exposed_items = all_items.select do |column|
|
||||||
@filter_list.include? column.name
|
safety_list.include? column.name.to_s # works for both symbols and strings
|
||||||
end
|
end
|
||||||
when :blacklist
|
when :blacklist
|
||||||
@exposed_fields = @model_class.columns.select do |column|
|
exposed_items = all_items.select do |column|
|
||||||
!(@filter_list.include? column.name)
|
!(safety_list.include? column.name.to_s)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@exposed_fields = []
|
exposed_items = []
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
class TypeBuilder
|
class TypeBuilder
|
||||||
attr_reader :filter_strategy, :graphql_models
|
attr_reader :filter_strategy, :graphql_models
|
||||||
|
attr_accessor :graphql_types # contains all generated GraphQL types
|
||||||
|
|
||||||
def initialize(graphql_models, options = {})
|
def initialize(graphql_models, options = {})
|
||||||
@graphql_models = graphql_models
|
@graphql_models = graphql_models
|
||||||
@@ -31,14 +32,30 @@ private
|
|||||||
type_builder = self
|
type_builder = self
|
||||||
|
|
||||||
graphql_type = GraphQL::ObjectType.define do
|
graphql_type = GraphQL::ObjectType.define do
|
||||||
em = ExposableModel.new(model_class, filter_strategy: type_builder.filter_strategy, filter_list: type_builder.graphql_models[model_class])
|
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)
|
name(em.name)
|
||||||
description(em.description)
|
description(em.description)
|
||||||
|
|
||||||
em.exposed_fields.each do |column|
|
em.exposed_fields.each do |column|
|
||||||
ef = ExposableField.new(column)
|
ef = ExposableField.new(column)
|
||||||
field(ef.name, ef.graphql_type)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -48,10 +65,25 @@ end
|
|||||||
|
|
||||||
graphql_models = {}
|
graphql_models = {}
|
||||||
|
|
||||||
graphql_models.store(User, ['id', 'username'])
|
graphql_models.store(User, {
|
||||||
graphql_models.store(Proposal, ['id', 'title', 'description', 'author_id', 'created_at'])
|
fields: ['id', 'username'],
|
||||||
graphql_models.store(Debate, ['id', 'title', 'description', 'author_id', 'created_at'])
|
associations: ['proposals', 'debates']
|
||||||
graphql_models.store(Comment, ['id', 'commentable_id', 'commentable_type', 'body'])
|
})
|
||||||
graphql_models.store(Geozone, ['id', 'name', 'html_map_coordinates'])
|
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)
|
TYPE_BUILDER = TypeBuilder.new(graphql_models, filter_strategy: :whitelist)
|
||||||
|
|||||||
Reference in New Issue
Block a user