Class: Tina4::GraphQL
- Inherits:
-
Object
- Object
- Tina4::GraphQL
- Defined in:
- lib/tina4/graphql.rb
Overview
─── Main GraphQL class ──────────────────────────────────────────────
Class Attribute Summary collapse
-
.default_instance ⇒ Object
Returns the value of attribute default_instance.
Instance Attribute Summary collapse
-
#schema ⇒ Object
readonly
Returns the value of attribute schema.
Class Method Summary collapse
-
._clear_class_resolvers! ⇒ Object
Test-only — clear the class-level registry.
-
.auto_schema_enabled? ⇒ Boolean
Class-level toggle for ORM auto-schema generation.
- .class_resolvers ⇒ Object
-
.resolve(type_name, field_name, &block) ⇒ Object
Decorator-style resolver registration.
Instance Method Summary collapse
-
#execute(query, variables: {}, context: {}, operation_name: nil) ⇒ Object
Execute a query string directly.
-
#field_resolver(type_name, field_name) ⇒ Object
Get the field resolver registered for an object type, if any.
-
#handle_request(body, context: {}) ⇒ Object
Handle an HTTP request body (JSON string).
-
#initialize(schema = nil) ⇒ GraphQL
constructor
A new instance of GraphQL.
-
#introspect ⇒ Object
Return schema metadata for debugging.
-
#register_route(path = nil) ⇒ Object
── Route Registration ───────────────────────────────────────────── Register a POST /graphql route in the Tina4 router.
-
#schema_sdl ⇒ Object
Return schema as GraphQL SDL string.
Constructor Details
#initialize(schema = nil) ⇒ GraphQL
Returns a new instance of GraphQL.
913 914 915 916 917 918 919 920 921 922 923 924 925 |
# File 'lib/tina4/graphql.rb', line 913 def initialize(schema = nil) @schema = schema || GraphQLSchema.new @executor = GraphQLExecutor.new(@schema) @field_resolvers = {} # Drain any resolvers registered via the class-level GraphQL.resolve() # BEFORE this instance was constructed. self.class.class_resolvers.each do |type_name, fields| fields.each do |field_name, resolver| attach_resolver(type_name, field_name, resolver) end end end |
Class Attribute Details
.default_instance ⇒ Object
Returns the value of attribute default_instance.
869 870 871 |
# File 'lib/tina4/graphql.rb', line 869 def default_instance @default_instance end |
Instance Attribute Details
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
846 847 848 |
# File 'lib/tina4/graphql.rb', line 846 def schema @schema end |
Class Method Details
._clear_class_resolvers! ⇒ Object
Test-only — clear the class-level registry. Used by parity tests to avoid bleed-over between cases.
907 908 909 910 |
# File 'lib/tina4/graphql.rb', line 907 def _clear_class_resolvers! @class_resolvers = {} @default_instance = nil end |
.auto_schema_enabled? ⇒ Boolean
Class-level toggle for ORM auto-schema generation. Defaults to true, can be disabled via TINA4_GRAPHQL_AUTO_SCHEMA=false. Initializers and user app code can branch on this before calling ‘schema.from_orm(…)`.
851 852 853 854 |
# File 'lib/tina4/graphql.rb', line 851 def self.auto_schema_enabled? val = ENV.fetch("TINA4_GRAPHQL_AUTO_SCHEMA", "true").to_s.strip.downcase !%w[false 0 no off].include?(val) end |
.class_resolvers ⇒ Object
871 872 873 |
# File 'lib/tina4/graphql.rb', line 871 def class_resolvers @class_resolvers ||= {} end |
.resolve(type_name, field_name, &block) ⇒ Object
Decorator-style resolver registration.
Tina4::GraphQL.resolve("Query", "products") do |root, args, ctx|
Product.all
end
Tina4::GraphQL.resolve("Mutation", "createProduct") do |root, args, ctx|
Product.create(args["input"])
end
Tina4::GraphQL.resolve("Product", "reviews") do |product, args, ctx|
Review.where("product_id = ?", [product["id"]])
end
Resolvers registered before any GraphQL instance accumulate in a class-level registry. new GraphQL drains them into its schema. Resolvers registered after .default_instance is set wire into the live schema immediately.
893 894 895 896 897 898 899 900 901 902 903 |
# File 'lib/tina4/graphql.rb', line 893 def resolve(type_name, field_name, &block) class_resolvers[type_name] ||= {} class_resolvers[type_name][field_name] = block # If a default instance is active, attach immediately so post-startup # registrations take effect without re-instantiation. if @default_instance @default_instance.send(:attach_resolver, type_name, field_name, block) end block end |
Instance Method Details
#execute(query, variables: {}, context: {}, operation_name: nil) ⇒ Object
Execute a query string directly
956 957 958 959 960 961 962 963 964 |
# File 'lib/tina4/graphql.rb', line 956 def execute(query, variables: {}, context: {}, operation_name: nil) parser = GraphQLParser.new(query) document = parser.parse @executor.execute(document, variables: variables, context: context, operation_name: operation_name) rescue GraphQLError => e { "data" => nil, "errors" => [{ "message" => e. }] } rescue => e { "data" => nil, "errors" => [{ "message" => "Internal error: #{e.}" }] } end |
#field_resolver(type_name, field_name) ⇒ Object
Get the field resolver registered for an object type, if any. Used by the executor during nested field resolution.
951 952 953 |
# File 'lib/tina4/graphql.rb', line 951 def field_resolver(type_name, field_name) @field_resolvers.dig(type_name, field_name) end |
#handle_request(body, context: {}) ⇒ Object
Handle an HTTP request body (JSON string)
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 |
# File 'lib/tina4/graphql.rb', line 1003 def handle_request(body, context: {}) payload = JSON.parse(body) query = payload["query"] || "" variables = payload["variables"] || {} op_name = payload["operationName"] execute(query, variables: variables, context: context, operation_name: op_name) rescue JSON::ParserError { "data" => nil, "errors" => [{ "message" => "Invalid JSON in request body" }] } end |
#introspect ⇒ Object
Return schema metadata for debugging.
996 997 998 999 1000 |
# File 'lib/tina4/graphql.rb', line 996 def introspect queries = @schema.queries.transform_values { |v| { type: v[:type], args: v[:args] || {} } } mutations = @schema.mutations.transform_values { |v| { type: v[:type], args: v[:args] || {} } } { types: @schema.types.keys, queries: queries, mutations: mutations } end |
#register_route(path = nil) ⇒ Object
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 |
# File 'lib/tina4/graphql.rb', line 1021 def register_route(path = nil) # TINA4_GRAPHQL_ENDPOINT — defaults to /graphql when caller doesn't override. path ||= ENV.fetch("TINA4_GRAPHQL_ENDPOINT", "/graphql") path = path.to_s path = "/#{path}" unless path.start_with?("/") graphql = self Tina4.post path, auth: false do |request, response| body = request.body result = graphql.handle_request(body, context: { request: request }) response.json(result) end # Optional: GET for GraphiQL/introspection Tina4.get path, auth: false do |request, response| query = request.params["query"] if query variables = request.params["variables"] variables = JSON.parse(variables) if variables.is_a?(String) && !variables.empty? result = graphql.execute(query, variables: variables || {}, context: { request: request }) response.json(result) else response.html(graphiql_html(path)) end end end |
#schema_sdl ⇒ Object
Return schema as GraphQL SDL string.
967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 |
# File 'lib/tina4/graphql.rb', line 967 def schema_sdl sdl = "" @schema.types.each do |name, type_obj| sdl += "type #{name} {\n" type_obj.fields.each { |f| sdl += " #{f[:name]}: #{f[:type]}\n" } sdl += "}\n\n" end unless @schema.queries.empty? sdl += "type Query {\n" @schema.queries.each do |name, config| args = (config[:args] || {}).map { |k, v| "#{k}: #{v}" }.join(", ") arg_str = args.empty? ? "" : "(#{args})" sdl += " #{name}#{arg_str}: #{config[:type]}\n" end sdl += "}\n\n" end unless @schema.mutations.empty? sdl += "type Mutation {\n" @schema.mutations.each do |name, config| args = (config[:args] || {}).map { |k, v| "#{k}: #{v}" }.join(", ") arg_str = args.empty? ? "" : "(#{args})" sdl += " #{name}#{arg_str}: #{config[:type]}\n" end sdl += "}\n\n" end sdl end |