From c984e666ffa3751add7c45cb933d304f41bfc599 Mon Sep 17 00:00:00 2001 From: Finn Heemeyer Date: Wed, 5 Jan 2022 13:23:11 +0100 Subject: [PATCH] Add new GraphQL types, schema (with fields) & base mutation The current consul GraphQL API has two problems. 1) It uses some unnecessary complicated magic to automatically create the GraphQL types and querys using an `api.yml` file. This approach is over-engineered, complex and has no benefits. It's just harder to understand the code for people which are not familiar with the project (like me, lol). 2) It uses a deprecated DSL [1] that is soon going to be removed from `graphql-ruby` completely. We are already seeing deprecation warning because of this (see References). There was one problem. I wanted to create the API so that it is fully backwards compatible with the old one, BUT the old one uses field names which are directly derived from the ruby code, which results in snake_case field names - not the GraphQL way. When I'm using the graphql-ruby Class-based syntax, it automatically creates the fields in camelCase, which breaks backwards-compatibility. So I've added deprecated snake_case field names to keep it backwards-compatible. [1] https://graphql-ruby.org/schema/class_based_api.html --- app/controllers/graphql_controller.rb | 50 ++++---- app/graphql/consul_schema.rb | 11 ++ app/graphql/mutations/base_mutation.rb | 5 + app/graphql/types/base_object.rb | 29 +++++ app/graphql/types/comment_type.rb | 16 +++ app/graphql/types/debate_type.rb | 22 ++++ app/graphql/types/geozone_type.rb | 6 + app/graphql/types/mutation_type.rb | 4 + .../types/proposal_notification_type.rb | 14 +++ app/graphql/types/proposal_type.rb | 32 ++++++ app/graphql/types/query_type.rb | 107 ++++++++++++++++++ app/graphql/types/tag_type.rb | 8 ++ app/graphql/types/user_type.rb | 9 ++ app/graphql/types/vote_type.rb | 9 ++ app/models/concerns/graphqlable.rb | 26 ----- app/models/concerns/has_public_author.rb | 2 +- config/api.yml | 82 -------------- config/initializers/acts_as_taggable_on.rb | 12 -- config/routes/graphql.rb | 4 +- lib/graph_ql/api_types_creator.rb | 85 -------------- lib/graph_ql/query_type_creator.rb | 28 ----- spec/lib/graph_ql/api_types_creator_spec.rb | 58 ---------- spec/lib/graph_ql/query_type_creator_spec.rb | 37 ------ spec/lib/graphql_spec.rb | 22 ++-- 24 files changed, 315 insertions(+), 363 deletions(-) create mode 100644 app/graphql/consul_schema.rb create mode 100644 app/graphql/mutations/base_mutation.rb create mode 100644 app/graphql/types/base_object.rb create mode 100644 app/graphql/types/comment_type.rb create mode 100644 app/graphql/types/debate_type.rb create mode 100644 app/graphql/types/geozone_type.rb create mode 100644 app/graphql/types/mutation_type.rb create mode 100644 app/graphql/types/proposal_notification_type.rb create mode 100644 app/graphql/types/proposal_type.rb create mode 100644 app/graphql/types/query_type.rb create mode 100644 app/graphql/types/tag_type.rb create mode 100644 app/graphql/types/user_type.rb create mode 100644 app/graphql/types/vote_type.rb delete mode 100644 config/api.yml delete mode 100644 lib/graph_ql/api_types_creator.rb delete mode 100644 lib/graph_ql/query_type_creator.rb delete mode 100644 spec/lib/graph_ql/api_types_creator_spec.rb delete mode 100644 spec/lib/graph_ql/query_type_creator_spec.rb diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 5b1c27d16..877a3a451 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -6,52 +6,56 @@ class GraphqlController < ApplicationController skip_before_action :verify_authenticity_token skip_authorization_check - class QueryStringError < StandardError - end + class QueryStringError < StandardError; end - def query + def execute begin - if query_string.nil? then raise GraphqlController::QueryStringError end + raise GraphqlController::QueryStringError if query_string.nil? - response = consul_schema.execute query_string, variables: query_variables - render json: response, status: :ok + result = ConsulSchema.execute(query_string, + variables: prepare_variables, + context: {}, + operation_name: params[:operationName] + ) + render json: result rescue GraphqlController::QueryStringError render json: { message: "Query string not present" }, status: :bad_request rescue JSON::ParserError render json: { message: "Error parsing JSON" }, status: :bad_request rescue GraphQL::ParseError render json: { message: "Query string is not valid JSON" }, status: :bad_request - rescue - unless Rails.env.production? then raise end + rescue ArgumentError => e + render json: { message: e.message }, status: :bad_request end end private - def consul_schema - api_types = GraphQL::ApiTypesCreator.create - query_type = GraphQL::QueryTypeCreator.create(api_types) - - GraphQL::Schema.define do - query query_type - max_depth 8 - max_complexity 2500 - end - end - def query_string if request.headers["CONTENT_TYPE"] == "application/graphql" - request.body.string # request.body.class => StringIO + request.body.string else params[:query] end end - def query_variables - if params[:variables].blank? || params[:variables] == "null" + # Handle variables in URL query string and JSON body + def prepare_variables + case variables_param = params[:variables] + # URL query string + when String + if variables_param.present? + JSON.parse(variables_param) || {} + else + {} + end + # JSON object in request body gets converted to ActionController::Parameters + when ActionController::Parameters + variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables. + when nil {} else - JSON.parse(params[:variables]) + raise ArgumentError, "Unexpected parameter: #{variables_param}" end end end diff --git a/app/graphql/consul_schema.rb b/app/graphql/consul_schema.rb new file mode 100644 index 000000000..13faaaeae --- /dev/null +++ b/app/graphql/consul_schema.rb @@ -0,0 +1,11 @@ +class ConsulSchema < GraphQL::Schema + mutation(Types::MutationType) + query(Types::QueryType) + + # Opt in to the new runtime (default in future graphql-ruby versions) + use GraphQL::Execution::Interpreter + use GraphQL::Analysis::AST + + # Add built-in connections for pagination + use GraphQL::Pagination::Connections +end diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb new file mode 100644 index 000000000..e57726c73 --- /dev/null +++ b/app/graphql/mutations/base_mutation.rb @@ -0,0 +1,5 @@ +module Mutations + class BaseMutation < GraphQL::Schema::RelayClassicMutation + object_class Types::BaseObject + end +end diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb new file mode 100644 index 000000000..a86f4ff40 --- /dev/null +++ b/app/graphql/types/base_object.rb @@ -0,0 +1,29 @@ +module Types + class BaseObject < GraphQL::Schema::Object + def self.field(*args, **kwargs, &block) + super(*args, **kwargs, &block) + + # The old api contained non-camelized fields + # We want to support these for now, but throw a deprecation warning + # + # Example: + # proposal_notifications => Deprecation warning (Old api) + # proposalNotifications => No deprecation warning (New api) + field_name = args[0] + + if field_name.to_s.include?("_") + reason = "Snake case fields are deprecated. Please use #{field_name.to_s.camelize(:lower)}." + kwargs = kwargs.merge({ camelize: false, deprecation_reason: reason }) + super(*args, **kwargs, &block) + end + + # Make sure associations only return public records + # by automatically calling 'public_for_api' + type_class = args[1] + + if type_class.is_a?(Class) && type_class.ancestors.include?(GraphQL::Types::Relay::BaseConnection) + define_method(field_name) { object.send(field_name).public_for_api } + end + end + end +end diff --git a/app/graphql/types/comment_type.rb b/app/graphql/types/comment_type.rb new file mode 100644 index 000000000..f022d1f0c --- /dev/null +++ b/app/graphql/types/comment_type.rb @@ -0,0 +1,16 @@ +module Types + class CommentType < Types::BaseObject + field :ancestry, String, null: true + field :body, String, null: true + field :cached_votes_down, Integer, null: true + field :cached_votes_total, Integer, null: true + field :cached_votes_up, Integer, null: true + field :commentable_id, Integer, null: true + field :commentable_type, String, null: true + field :confidence_score, Integer, null: false + field :id, ID, null: false + field :public_author, Types::UserType, null: true + field :public_created_at, String, null: true + field :votes_for, Types::VoteType.connection_type, null: true + end +end diff --git a/app/graphql/types/debate_type.rb b/app/graphql/types/debate_type.rb new file mode 100644 index 000000000..a5edaf313 --- /dev/null +++ b/app/graphql/types/debate_type.rb @@ -0,0 +1,22 @@ +module Types + class DebateType < Types::BaseObject + field :cached_votes_down, Integer, null: true + field :cached_votes_total, Integer, null: true + field :cached_votes_up, Integer, null: true + field :comments, Types::CommentType.connection_type, null: true + field :comments_count, Integer, null: true + field :confidence_score, Integer, null: true + field :description, String, null: true + field :hot_score, Integer, null: true + field :id, ID, null: false + field :public_author, Types::UserType, null: true + field :public_created_at, String, null: true + field :tags, Types::TagType.connection_type, null: true + field :title, String, null: true + field :votes_for, Types::VoteType.connection_type, null: true + + def tags + object.tags.public_for_api + end + end +end diff --git a/app/graphql/types/geozone_type.rb b/app/graphql/types/geozone_type.rb new file mode 100644 index 000000000..3f63621fd --- /dev/null +++ b/app/graphql/types/geozone_type.rb @@ -0,0 +1,6 @@ +module Types + class GeozoneType < Types::BaseObject + field :id, ID, null: false + field :name, String, null: true + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb new file mode 100644 index 000000000..113861978 --- /dev/null +++ b/app/graphql/types/mutation_type.rb @@ -0,0 +1,4 @@ +module Types + class MutationType < Types::BaseObject + end +end diff --git a/app/graphql/types/proposal_notification_type.rb b/app/graphql/types/proposal_notification_type.rb new file mode 100644 index 000000000..cd4e4afb3 --- /dev/null +++ b/app/graphql/types/proposal_notification_type.rb @@ -0,0 +1,14 @@ +module Types + class ProposalNotificationType < Types::BaseObject + field :body, String, null: true + field :id, ID, null: false + field :proposal, Types::ProposalType, null: true + field :proposal_id, Integer, null: true + field :public_created_at, String, null: true + field :title, String, null: true + + def proposal + Proposal.public_for_api.find_by(id: object.proposal) + end + end +end diff --git a/app/graphql/types/proposal_type.rb b/app/graphql/types/proposal_type.rb new file mode 100644 index 000000000..40b39cd01 --- /dev/null +++ b/app/graphql/types/proposal_type.rb @@ -0,0 +1,32 @@ +module Types + class ProposalType < Types::BaseObject + field :cached_votes_up, Integer, null: true + field :comments, Types::CommentType.connection_type, null: true + field :comments_count, Integer, null: true + field :confidence_score, Integer, null: true + field :description, String, null: true + field :geozone, Types::GeozoneType, null: true + field :geozone_id, Integer, null: true + field :hot_score, Integer, null: true + field :id, ID, null: false + field :proposal_notifications, Types::ProposalNotificationType.connection_type, null: true + field :public_author, Types::UserType, null: true + field :public_created_at, String, null: true + field :retired_at, GraphQL::Types::ISO8601DateTime, null: true + field :retired_explanation, String, null: true + field :retired_reason, String, null: true + field :summary, String, null: true + field :tags, Types::TagType.connection_type, null: true + field :title, String, null: true + field :video_url, String, null: true + field :votes_for, Types::VoteType.connection_type, null: true + + def tags + object.tags.public_for_api + end + + def geozone + Geozone.public_for_api.find_by(id: object.geozone) + end + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb new file mode 100644 index 000000000..06d855355 --- /dev/null +++ b/app/graphql/types/query_type.rb @@ -0,0 +1,107 @@ +module Types + class QueryType < Types::BaseObject + field :comments, Types::CommentType.connection_type, "Returns all comments", null: false + field :comment, Types::CommentType, "Returns comment for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :debates, Types::DebateType.connection_type, "Returns all debates", null: false + field :debate, Types::DebateType, "Returns debate for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :geozones, Types::GeozoneType.connection_type, "Returns all geozones", null: false + field :geozone, Types::GeozoneType, "Returns geozone for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :proposals, Types::ProposalType.connection_type, "Returns all proposals", null: false + field :proposal, Types::ProposalType, "Returns proposal for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :proposal_notifications, Types::ProposalNotificationType.connection_type, "Returns all proposal notifications", null: false + field :proposal_notification, Types::ProposalNotificationType, "Returns proposal notification for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :tags, Types::TagType.connection_type, "Returns all tags", null: false + field :tag, Types::TagType, "Returns tag for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :users, Types::UserType.connection_type, "Returns all users", null: false + field :user, Types::UserType, "Returns user for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :votes, Types::VoteType.connection_type, "Returns all votes", null: false + field :vote, Types::VoteType, "Returns vote for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + def comments + Comment.public_for_api + end + + def comment(id:) + Comment.find(id) + end + + def debates + Debate.public_for_api + end + + def debate(id:) + Debate.find(id) + end + + def geozones + Geozone.public_for_api + end + + def geozone(id:) + Geozone.find(id) + end + + def proposals + Proposal.public_for_api + end + + def proposal(id:) + Proposal.find(id) + end + + def proposal_notifications + ProposalNotification.public_for_api + end + + def proposal_notification(id:) + ProposalNotification.find(id) + end + + def tags + Tag.public_for_api + end + + def tag(id:) + Tag.find(id) + end + + def users + User.public_for_api + end + + def user(id:) + User.find(id) + end + + def votes + Vote.public_for_api + end + + def vote(id:) + Vote.find(id) + end + end +end diff --git a/app/graphql/types/tag_type.rb b/app/graphql/types/tag_type.rb new file mode 100644 index 000000000..3ad42f189 --- /dev/null +++ b/app/graphql/types/tag_type.rb @@ -0,0 +1,8 @@ +module Types + class TagType < Types::BaseObject + field :id, ID, null: false + field :kind, String, null: true + field :name, String, null: true + field :taggings_count, Integer, null: true + end +end diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb new file mode 100644 index 000000000..57713ed92 --- /dev/null +++ b/app/graphql/types/user_type.rb @@ -0,0 +1,9 @@ +module Types + class UserType < Types::BaseObject + field :id, ID, null: false + field :public_comments, Types::CommentType.connection_type, null: true + field :public_debates, Types::DebateType.connection_type, null: true + field :public_proposals, Types::ProposalType.connection_type, null: true + field :username, String, null: true + end +end diff --git a/app/graphql/types/vote_type.rb b/app/graphql/types/vote_type.rb new file mode 100644 index 000000000..16b0d7f50 --- /dev/null +++ b/app/graphql/types/vote_type.rb @@ -0,0 +1,9 @@ +module Types + class VoteType < Types::BaseObject + field :id, ID, null: false + field :public_created_at, String, null: true + field :votable_id, Integer, null: true + field :votable_type, String, null: true + field :vote_flag, Boolean, null: true + end +end diff --git a/app/models/concerns/graphqlable.rb b/app/models/concerns/graphqlable.rb index 2284838da..7cbb06199 100644 --- a/app/models/concerns/graphqlable.rb +++ b/app/models/concerns/graphqlable.rb @@ -1,32 +1,6 @@ module Graphqlable extend ActiveSupport::Concern - class_methods do - def graphql_field_name - name.gsub("::", "_").underscore.to_sym - end - - def graphql_field_description - "Find one #{model_name.human} by ID" - end - - def graphql_pluralized_field_name - name.gsub("::", "_").underscore.pluralize.to_sym - end - - def graphql_pluralized_field_description - "Find all #{model_name.human.pluralize}" - end - - def graphql_type_name - name.gsub("::", "_") - end - - def graphql_type_description - model_name.human.to_s - end - end - def public_created_at created_at.change(min: 0) end diff --git a/app/models/concerns/has_public_author.rb b/app/models/concerns/has_public_author.rb index 5132c33c9..edd1e45bb 100644 --- a/app/models/concerns/has_public_author.rb +++ b/app/models/concerns/has_public_author.rb @@ -1,5 +1,5 @@ module HasPublicAuthor def public_author - author.public_activity? ? author : nil + author.public_activity? ? User.public_for_api.find_by(id: author) : nil end end diff --git a/config/api.yml b/config/api.yml deleted file mode 100644 index 6e47c090d..000000000 --- a/config/api.yml +++ /dev/null @@ -1,82 +0,0 @@ -User: - fields: - id: integer - username: string - public_debates: [Debate] - public_proposals: [Proposal] - public_comments: [Comment] -Debate: - fields: - id: integer - title: string - description: string - public_created_at: string - cached_votes_total: integer - cached_votes_up: integer - cached_votes_down: integer - comments_count: integer - hot_score: integer - confidence_score: integer - comments: [Comment] - public_author: User - votes_for: [Vote] - tags: [Tag] -Proposal: - fields: - id: integer - title: string - description: string - cached_votes_up: integer - comments_count: integer - hot_score: integer - confidence_score: integer - public_created_at: string - summary: string - video_url: string - geozone_id: integer - retired_at: string - retired_reason: string - retired_explanation: string - geozone: Geozone - comments: [Comment] - proposal_notifications: [ProposalNotification] - public_author: User - votes_for: [Vote] - tags: [Tag] -Comment: - fields: - id: integer - commentable_id: integer - commentable_type: string - body: string - public_created_at: string - cached_votes_total: integer - cached_votes_up: integer - cached_votes_down: integer - ancestry: string - confidence_score: integer - public_author: User - votes_for: [Vote] -Geozone: - fields: - id: integer - name: string -ProposalNotification: - fields: - title: string - body: string - proposal_id: integer - public_created_at: string - proposal: Proposal -Tag: - fields: - id: integer - name: string - taggings_count: integer - kind: string -Vote: - fields: - votable_id: integer - votable_type: string - public_created_at: string - vote_flag: boolean diff --git a/config/initializers/acts_as_taggable_on.rb b/config/initializers/acts_as_taggable_on.rb index d9ec982a4..a3ebeb853 100644 --- a/config/initializers/acts_as_taggable_on.rb +++ b/config/initializers/acts_as_taggable_on.rb @@ -64,18 +64,6 @@ module ActsAsTaggableOn Tag.category.pluck(:name) end - def self.graphql_field_name - :tag - end - - def self.graphql_pluralized_field_name - :tags - end - - def self.graphql_type_name - "Tag" - end - private def custom_counter_field_name_for(taggable_type) diff --git a/config/routes/graphql.rb b/config/routes/graphql.rb index 83e4594fc..c62d6ead2 100644 --- a/config/routes/graphql.rb +++ b/config/routes/graphql.rb @@ -1,3 +1,3 @@ -get "/graphql", to: "graphql#query" -post "/graphql", to: "graphql#query" +post "/graphql", to: "graphql#execute" +get "/graphql", to: "graphql#execute" mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" diff --git a/lib/graph_ql/api_types_creator.rb b/lib/graph_ql/api_types_creator.rb deleted file mode 100644 index 3fbb3d704..000000000 --- a/lib/graph_ql/api_types_creator.rb +++ /dev/null @@ -1,85 +0,0 @@ -require "graphql" - -module GraphQL - class ApiTypesCreator - SCALAR_TYPES = { - integer: GraphQL::INT_TYPE, - boolean: GraphQL::BOOLEAN_TYPE, - float: GraphQL::FLOAT_TYPE, - double: GraphQL::FLOAT_TYPE, - string: GraphQL::STRING_TYPE - }.freeze - - def self.create - created_types = {} - api_types_definitions.each do |model, info| - create_type(model, info[:fields], created_types) - end - created_types - end - - def self.api_types_definitions - @api_types_definitions ||= parse_api_config_file(YAML.load_file(Rails.root.join("config/api.yml"))) - end - - def self.type_kind(type) - if SCALAR_TYPES[type] - :scalar - elsif type.class == Class - :singular_association - elsif type.class == Array - :multiple_association - end - end - - def self.create_type(model, fields, created_types) - created_types[model] = GraphQL::ObjectType.define do - name model.graphql_type_name - description model.graphql_type_description - - # Make a field for each column, association or method - fields.each do |field_name, field_type| - case ApiTypesCreator.type_kind(field_type) - when :scalar - field(field_name, SCALAR_TYPES[field_type], model.human_attribute_name(field_name)) - when :singular_association - field(field_name, -> { created_types[field_type] }) do - resolve ->(object, arguments, context) do - association_target = object.send(field_name) - association_target.present? ? field_type.public_for_api.find_by(id: association_target.id) : nil - end - end - when :multiple_association - field_type = field_type.first - connection(field_name, -> { created_types[field_type].connection_type }, max_page_size: 50, complexity: 1000) do - resolve ->(object, arguments, context) { object.send(field_name).public_for_api } - end - end - end - end - end - - def self.parse_api_config_file(file) - api_type_definitions = {} - - file.each do |api_type_model, api_type_info| - model = api_type_model.constantize - fields = {} - - api_type_info["fields"].each do |field_name, field_type| - if field_type.is_a?(Array) # paginated association - fields[field_name.to_sym] = [field_type.first.constantize] - elsif SCALAR_TYPES[field_type.to_sym] - fields[field_name.to_sym] = field_type.to_sym - else # simple association - fields[field_name.to_sym] = field_type.constantize - end - end - - api_type_definitions[model] = { fields: fields } - end - - api_type_definitions - end - end -end diff --git a/lib/graph_ql/query_type_creator.rb b/lib/graph_ql/query_type_creator.rb deleted file mode 100644 index 35c2b4d2c..000000000 --- a/lib/graph_ql/query_type_creator.rb +++ /dev/null @@ -1,28 +0,0 @@ -require "graphql" - -module GraphQL - class QueryTypeCreator - def self.create(api_types) - GraphQL::ObjectType.define do - name "QueryType" - description "The root query for the schema" - - api_types.each do |model, created_type| - if created_type.fields["id"] - field model.graphql_field_name do - type created_type - description model.graphql_field_description - argument :id, !types.ID - resolve ->(object, arguments, context) { model.public_for_api.find_by(id: arguments["id"]) } - end - end - - connection(model.graphql_pluralized_field_name, created_type.connection_type, max_page_size: 50, complexity: 1000) do - description model.graphql_pluralized_field_description - resolve ->(object, arguments, context) { model.public_for_api } - end - end - end - end - end -end diff --git a/spec/lib/graph_ql/api_types_creator_spec.rb b/spec/lib/graph_ql/api_types_creator_spec.rb deleted file mode 100644 index 019254aa5..000000000 --- a/spec/lib/graph_ql/api_types_creator_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -require "rails_helper" - -describe GraphQL::ApiTypesCreator do - let(:created_types) { {} } - - describe "::create_type" do - it "creates fields for Int attributes" do - debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { id: :integer }, created_types) - created_field = debate_type.fields["id"] - - expect(created_field).to be_a(GraphQL::Field) - expect(created_field.type).to be_a(GraphQL::ScalarType) - expect(created_field.type.name).to eq("Int") - end - - it "creates fields for String attributes" do - debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { title: :string }, created_types) - created_field = debate_type.fields["title"] - - expect(created_field).to be_a(GraphQL::Field) - expect(created_field.type).to be_a(GraphQL::ScalarType) - expect(created_field.type.name).to eq("String") - end - - it "creates connections for :belongs_to associations" do - user_type = GraphQL::ApiTypesCreator.create_type(User, { id: :integer }, created_types) - debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { author: User }, created_types) - - connection = debate_type.fields["author"] - - expect(connection).to be_a(GraphQL::Field) - expect(connection.type).to eq(user_type) - expect(connection.name).to eq("author") - end - - it "creates connections for :has_one associations" do - user_type = GraphQL::ApiTypesCreator.create_type(User, { organization: Organization }, created_types) - organization_type = GraphQL::ApiTypesCreator.create_type(Organization, { id: :integer }, created_types) - - connection = user_type.fields["organization"] - - expect(connection).to be_a(GraphQL::Field) - expect(connection.type).to eq(organization_type) - expect(connection.name).to eq("organization") - end - - it "creates connections for :has_many associations" do - comment_type = GraphQL::ApiTypesCreator.create_type(Comment, { id: :integer }, created_types) - debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { comments: [Comment] }, created_types) - - connection = debate_type.fields["comments"] - - expect(connection).to be_a(GraphQL::Field) - expect(connection.type).to eq(comment_type.connection_type) - expect(connection.name).to eq("comments") - end - end -end diff --git a/spec/lib/graph_ql/query_type_creator_spec.rb b/spec/lib/graph_ql/query_type_creator_spec.rb deleted file mode 100644 index 36d671c5c..000000000 --- a/spec/lib/graph_ql/query_type_creator_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require "rails_helper" - -describe GraphQL::QueryTypeCreator do - before do - allow(GraphQL::ApiTypesCreator).to receive(:api_types_definitions).and_return( - { - ProposalNotification => { fields: { title: :string }}, - Proposal => { fields: { id: :integer, title: :string }} - } - ) - end - let(:api_types) { GraphQL::ApiTypesCreator.create } - - describe "::create" do - let(:query_type) { GraphQL::QueryTypeCreator.create(api_types) } - - it "creates a QueryType with fields to retrieve single objects whose model fields included an ID" do - field = query_type.fields["proposal"] - - expect(field).to be_a(GraphQL::Field) - expect(field.type).to eq(api_types[Proposal]) - expect(field.name).to eq("proposal") - end - - it "creates a QueryType without fields to retrieve single objects whose model fields did not include an ID" do - expect(query_type.fields["proposal_notification"]).to be_nil - end - - it "creates a QueryType with connections to retrieve collections of objects" do - connection = query_type.fields["proposals"] - - expect(connection).to be_a(GraphQL::Field) - expect(connection.type).to eq(api_types[Proposal].connection_type) - expect(connection.name).to eq("proposals") - end - end -end diff --git a/spec/lib/graphql_spec.rb b/spec/lib/graphql_spec.rb index 0675c0a66..de8b80c61 100644 --- a/spec/lib/graphql_spec.rb +++ b/spec/lib/graphql_spec.rb @@ -1,12 +1,5 @@ require "rails_helper" -api_types = GraphQL::ApiTypesCreator.create -query_type = GraphQL::QueryTypeCreator.create(api_types) -ConsulSchema = GraphQL::Schema.define do - query query_type - max_depth 12 -end - def execute(query_string, context = {}, variables = {}) ConsulSchema.execute(query_string, context: context, variables: variables) end @@ -40,8 +33,8 @@ describe "Consul Schema" do let(:proposal) { create(:proposal, author: user) } it "returns fields of Int type" do - response = execute("{ proposal(id: #{proposal.id}) { id } }") - expect(dig(response, "data.proposal.id")).to eq(proposal.id) + response = execute("{ proposal(id: #{proposal.id}) { cached_votes_up } }") + expect(dig(response, "data.proposal.cached_votes_up")).to eq(proposal.cached_votes_up) end it "returns fields of String type" do @@ -373,6 +366,17 @@ describe "Consul Schema" do expect(received_comments).not_to include(not_public_poll_comment.body) end + it "only links public comments" do + user = create(:administrator).user + create(:comment, author: user, body: "Public") + create(:budget_investment_comment, author: user, valuation: true, body: "Valuation") + + response = execute("{ user(id: #{user.id}) { public_comments { edges { node { body } } } } }") + received_comments = dig(response, "data.user.public_comments.edges") + + expect(received_comments).to eq [{ "node" => { "body" => "Public" }}] + end + it "only returns date and hour for created_at" do created_at = Time.zone.parse("2017-12-31 9:30:15") create(:comment, created_at: created_at)