From 49317e2c110736db802d5a5c513c3438d723f789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Miedes=20Garc=C3=A9s?= Date: Tue, 8 Nov 2016 22:19:16 +0100 Subject: [PATCH] Added support for associations (WITHOUT PAGINATION) --- app/graph/exposable_association.rb | 8 ++++++ app/graph/exposable_model.rb | 27 +++++++++++------- app/graph/type_builder.rb | 46 +++++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 app/graph/exposable_association.rb diff --git a/app/graph/exposable_association.rb b/app/graph/exposable_association.rb new file mode 100644 index 000000000..78e650b0a --- /dev/null +++ b/app/graph/exposable_association.rb @@ -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 diff --git a/app/graph/exposable_model.rb b/app/graph/exposable_model.rb index 9e4b228ad..22976c63d 100644 --- a/app/graph/exposable_model.rb +++ b/app/graph/exposable_model.rb @@ -1,30 +1,35 @@ class ExposableModel - attr_reader :exposed_fields, :name, :description + attr_reader :name, :description, :exposed_fields, :exposed_associations def initialize(model_class, options = {}) - @model_class = model_class - @filter_list = options[:filter_list] || [] @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_fields + set_exposed_items(model_class) end private - # determine which model fields are exposed to the API - def set_exposed_fields + # 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_fields = @model_class.columns.select do |column| - @filter_list.include? column.name + exposed_items = all_items.select do |column| + safety_list.include? column.name.to_s # works for both symbols and strings end when :blacklist - @exposed_fields = @model_class.columns.select do |column| - !(@filter_list.include? column.name) + exposed_items = all_items.select do |column| + !(safety_list.include? column.name.to_s) end else - @exposed_fields = [] + exposed_items = [] end end diff --git a/app/graph/type_builder.rb b/app/graph/type_builder.rb index 7de5bfd23..1277f8746 100644 --- a/app/graph/type_builder.rb +++ b/app/graph/type_builder.rb @@ -1,5 +1,6 @@ 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 @@ -31,14 +32,30 @@ private type_builder = self 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) description(em.description) em.exposed_fields.each do |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 @@ -48,10 +65,25 @@ end graphql_models = {} -graphql_models.store(User, ['id', 'username']) -graphql_models.store(Proposal, ['id', 'title', 'description', 'author_id', 'created_at']) -graphql_models.store(Debate, ['id', 'title', 'description', 'author_id', 'created_at']) -graphql_models.store(Comment, ['id', 'commentable_id', 'commentable_type', 'body']) -graphql_models.store(Geozone, ['id', 'name', 'html_map_coordinates']) +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)