Merge pull request #4766 from tonekk/refactor_graphql
Add new GraphQL types & schema
This commit is contained in:
@@ -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
|
||||
|
||||
11
app/graphql/consul_schema.rb
Normal file
11
app/graphql/consul_schema.rb
Normal file
@@ -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
|
||||
5
app/graphql/mutations/base_mutation.rb
Normal file
5
app/graphql/mutations/base_mutation.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
module Mutations
|
||||
class BaseMutation < GraphQL::Schema::RelayClassicMutation
|
||||
object_class Types::BaseObject
|
||||
end
|
||||
end
|
||||
29
app/graphql/types/base_object.rb
Normal file
29
app/graphql/types/base_object.rb
Normal file
@@ -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
|
||||
16
app/graphql/types/comment_type.rb
Normal file
16
app/graphql/types/comment_type.rb
Normal file
@@ -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
|
||||
22
app/graphql/types/debate_type.rb
Normal file
22
app/graphql/types/debate_type.rb
Normal file
@@ -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
|
||||
6
app/graphql/types/geozone_type.rb
Normal file
6
app/graphql/types/geozone_type.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
module Types
|
||||
class GeozoneType < Types::BaseObject
|
||||
field :id, ID, null: false
|
||||
field :name, String, null: true
|
||||
end
|
||||
end
|
||||
4
app/graphql/types/mutation_type.rb
Normal file
4
app/graphql/types/mutation_type.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
module Types
|
||||
class MutationType < Types::BaseObject
|
||||
end
|
||||
end
|
||||
14
app/graphql/types/proposal_notification_type.rb
Normal file
14
app/graphql/types/proposal_notification_type.rb
Normal file
@@ -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
|
||||
32
app/graphql/types/proposal_type.rb
Normal file
32
app/graphql/types/proposal_type.rb
Normal file
@@ -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
|
||||
107
app/graphql/types/query_type.rb
Normal file
107
app/graphql/types/query_type.rb
Normal file
@@ -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
|
||||
8
app/graphql/types/tag_type.rb
Normal file
8
app/graphql/types/tag_type.rb
Normal file
@@ -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
|
||||
9
app/graphql/types/user_type.rb
Normal file
9
app/graphql/types/user_type.rb
Normal file
@@ -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
|
||||
9
app/graphql/types/vote_type.rb
Normal file
9
app/graphql/types/vote_type.rb
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user