diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb index d2353e308..e9e64bac4 100644 --- a/config/initializers/graphql.rb +++ b/config/initializers/graphql.rb @@ -1,12 +1,84 @@ API_TYPE_DEFINITIONS = { - User => %I[ id username gender geozone_id geozone ], - Debate => %I[ id title description created_at cached_votes_total cached_votes_up cached_votes_down comments_count hot_score confidence_score geozone_id geozone comments public_author ], - Proposal => %I[ id title description external_url cached_votes_up comments_count hot_score confidence_score created_at summary video_url geozone_id retired_at retired_reason retired_explanation geozone comments proposal_notifications public_author ], - Comment => %I[ id commentable_id commentable_type body created_at cached_votes_total cached_votes_up cached_votes_down ancestry confidence_score public_author ], - Geozone => %I[ id name ], - ProposalNotification => %I[ title body proposal_id created_at proposal ], - Tag => %I[ id name taggings_count kind ], - Vote => %I[ votable_id votable_type created_at public_voter ] + User => { + id: :integer, + username: :string, + gender: :string, + geozone_id: :integer, + geozone: Geozone + }, + Debate => { + id: :integer, + title: :string, + description: :string, + created_at: :string, + cached_votes_total: :integer, + cached_votes_up: :integer, + cached_votes_down: :integer, + comments_count: :integer, + hot_score: :integer, + confidence_score: :integer, + geozone_id: :integer, + geozone: Geozone, + comments: [Comment], + public_author: User + }, + Proposal => { + id: :integer, + title: :string, + description: :sting, + external_url: :string, + cached_votes_up: :integer, + comments_count: :integer, + hot_score: :integer, + confidence_score: :integer, + 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 + }, + Comment => { + id: :integer, + commentable_id: :integer, + commentable_type: :string, + body: :string, + created_at: :string, + cached_votes_total: :integer, + cached_votes_up: :integer, + cached_votes_down: :integer, + ancestry: :string, + confidence_score: :integer, + public_author: User + }, + Geozone => { + id: :integer, + name: :string + }, + ProposalNotification => { + title: :string, + body: :string, + proposal_id: :integer, + created_at: :string, + proposal: Proposal + }, + Tag => { + id: :integer, + name: :string, + taggings_count: :integer, + kind: :string + }, + Vote => { + votable_id: :integer, + votable_type: :string, + created_at: :string, + public_voter: User + } } type_creator = GraphQL::TypeCreator.new @@ -35,7 +107,7 @@ QueryRoot = GraphQL::ObjectType.define do type_creator.created_types.each do |model, created_type| # create an entry field to retrive a single object - if API_TYPE_DEFINITIONS[model].include?(:id) + if API_TYPE_DEFINITIONS[model][:id] field model.name.underscore.to_sym do type created_type description "Find one #{model.model_name.human} by ID" diff --git a/lib/graph_ql/type_creator.rb b/lib/graph_ql/type_creator.rb index 74c9b6fbe..468577cff 100644 --- a/lib/graph_ql/type_creator.rb +++ b/lib/graph_ql/type_creator.rb @@ -1,12 +1,13 @@ module GraphQL class TypeCreator # Return a GraphQL type for a 'database_type' - TYPES_CONVERSION = Hash.new(GraphQL::STRING_TYPE).merge( + SCALAR_TYPES = { integer: GraphQL::INT_TYPE, boolean: GraphQL::BOOLEAN_TYPE, float: GraphQL::FLOAT_TYPE, - double: GraphQL::FLOAT_TYPE - ) + double: GraphQL::FLOAT_TYPE, + string: GraphQL::STRING_TYPE + } attr_accessor :created_types @@ -14,7 +15,7 @@ module GraphQL @created_types = {} end - def create(model, field_names) + def create(model, fields) type_creator = self created_type = GraphQL::ObjectType.define do @@ -23,41 +24,43 @@ module GraphQL description("#{model.model_name.human}") # Make a field for each column, association or method - field_names.each do |field_name| - if model.column_names.include?(field_name.to_s) - field(field_name.to_s, TYPES_CONVERSION[model.columns_hash[field_name.to_s].type]) - else - association = type_creator.class.association?(model, field_name) - target_model = association.klass - public_elements = target_model.respond_to?(:public_for_api) ? target_model.public_for_api : target_model.all - - if type_creator.class.needs_pagination?(association) - connection(association.name, -> { type_creator.created_types[target_model].connection_type }) do - resolve -> (object, arguments, context) do - object.send(association.name).all & public_elements.all - end + fields.each do |name, type| + case GraphQL::TypeCreator.type_kind(type) + when :scalar + field name, SCALAR_TYPES[type] + when :simple_association + field(name, -> { type_creator.created_types[type] }) do + resolve -> (object, arguments, context) do + target_public_elements = type.respond_to?(:public_for_api) ? type.public_for_api : type.all + wanted_element = object.send(name) + target_public_elements.include?(wanted_element) ? wanted_element : nil end - else - field(association.name, -> { type_creator.created_types[target_model] }) do - resolve -> (object, arguments, context) do - linked_element = object.send(field_name) - public_elements.include?(linked_element) ? linked_element : nil - end + end + when :paginated_association + type = type.first + connection(name, -> { type_creator.created_types[type].connection_type }) do + resolve -> (object, arguments, context) do + target_public_elements = type.respond_to?(:public_for_api) ? type.public_for_api : type.all + object.send(name).all & target_public_elements.all end end end end + end created_types[model] = created_type return created_type # GraphQL::ObjectType end - def self.association?(model, field_name) - model.reflect_on_all_associations.find { |a| a.name == field_name } + def self.type_kind(type) + if SCALAR_TYPES[type] + :scalar + elsif type.class == Class + :simple_association + elsif type.class == Array + :paginated_association + end end - def self.needs_pagination?(association) - association.macro == :has_many - end end end diff --git a/spec/lib/type_creator_spec.rb b/spec/lib/type_creator_spec.rb index 2fc1bf78d..1e25b91fd 100644 --- a/spec/lib/type_creator_spec.rb +++ b/spec/lib/type_creator_spec.rb @@ -5,7 +5,7 @@ describe GraphQL::TypeCreator do describe "::create" do it "creates fields for Int attributes" do - debate_type = type_creator.create(Debate, %I[ id ]) + debate_type = type_creator.create(Debate, { id: :integer }) created_field = debate_type.fields['id'] expect(created_field).to be_a(GraphQL::Field) @@ -14,7 +14,7 @@ describe GraphQL::TypeCreator do end it "creates fields for String attributes" do - debate_type = type_creator.create(Debate, %I[ title ]) + debate_type = type_creator.create(Debate, { title: :string }) created_field = debate_type.fields['title'] expect(created_field).to be_a(GraphQL::Field) @@ -23,8 +23,8 @@ describe GraphQL::TypeCreator do end it "creates connections for :belongs_to associations" do - user_type = type_creator.create(User, %I[ id ]) - debate_type = type_creator.create(Debate, %I[ author ]) + user_type = type_creator.create(User, { id: :integer }) + debate_type = type_creator.create(Debate, { author: User }) connection = debate_type.fields['author'] @@ -34,8 +34,8 @@ describe GraphQL::TypeCreator do end it "creates connections for :has_one associations" do - user_type = type_creator.create(User, %I[ organization ]) - organization_type = type_creator.create(Organization, %I[ id ]) + user_type = type_creator.create(User, { organization: Organization }) + organization_type = type_creator.create(Organization, { id: :integer }) connection = user_type.fields['organization'] @@ -45,8 +45,8 @@ describe GraphQL::TypeCreator do end it "creates connections for :has_many associations" do - comment_type = type_creator.create(Comment, %I[ id ]) - debate_type = type_creator.create(Debate, %I[ comments ]) + comment_type = type_creator.create(Comment, { id: :integer }) + debate_type = type_creator.create(Debate, { comments: [Comment] }) connection = debate_type.fields['comments']