graphql-rails 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/app/controllers/graphql/rails/schema_controller.rb +59 -0
- data/config/initializers/graphiql.rb +1 -0
- data/config/routes.rb +12 -0
- data/lib/graphql/rails.rb +16 -0
- data/lib/graphql/rails/callbacks.rb +49 -0
- data/lib/graphql/rails/config.rb +28 -0
- data/lib/graphql/rails/controller_extensions.rb +23 -0
- data/lib/graphql/rails/dsl.rb +18 -0
- data/lib/graphql/rails/engine.rb +68 -0
- data/lib/graphql/rails/extensions/cancan.rb +50 -0
- data/lib/graphql/rails/extensions/mongoid.rb +96 -0
- data/lib/graphql/rails/node_identification.rb +13 -0
- data/lib/graphql/rails/operations.rb +100 -0
- data/lib/graphql/rails/schema.rb +44 -0
- data/lib/graphql/rails/types.rb +92 -0
- data/lib/graphql/rails/version.rb +5 -0
- data/lib/tasks/graphql_rails_tasks.rake +4 -0
- metadata +125 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 8386088857c27bbcdea43d4891771e8bea89e8f7
|
|
4
|
+
data.tar.gz: 2bedc9fc92d44483612f9253df2dfde7ce2f8973
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: df543107f1fa308fb2228f7951b70ac28c263f0aea93024d121dfcf093d75e515b146bd71655fc0e6e23beb548924d28c7700284833c3f8725b4c573c8c2eec7
|
|
7
|
+
data.tar.gz: 06057d2317334ed33e8a16e4f7f35e9b6f8209c2f8207ae03c57421a1f28a3b076879f2fd924e74ac76828caf07b6c875b1feae2b8de7efd32d5545cc12f05b2
|
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2016 James Reggio <james.reggio@gmail.com>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
class SchemaController < ActionController::Base
|
|
4
|
+
include ControllerExtensions
|
|
5
|
+
rescue_from Exception, :with => :internal_error
|
|
6
|
+
rescue_from GraphQL::ParseError, :with => :invalid_query
|
|
7
|
+
rescue_from JSON::ParserError, :with => :invalid_variables
|
|
8
|
+
|
|
9
|
+
def execute
|
|
10
|
+
query_string = params[:query]
|
|
11
|
+
query_variables = to_hash(params[:variables])
|
|
12
|
+
render json: Schema.instance.execute(
|
|
13
|
+
query_string,
|
|
14
|
+
variables: query_variables,
|
|
15
|
+
context: context,
|
|
16
|
+
debug: true
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def context
|
|
23
|
+
@context ||= {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_hash(param)
|
|
27
|
+
if param.blank?
|
|
28
|
+
{}
|
|
29
|
+
elsif param.is_a?(String)
|
|
30
|
+
JSON.parse(param)
|
|
31
|
+
else
|
|
32
|
+
param
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def render_error(status, message)
|
|
37
|
+
render json: {
|
|
38
|
+
:errors => [{:message => message}],
|
|
39
|
+
}, :status => status
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def invalid_request(message)
|
|
43
|
+
render_error 400, message
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def invalid_query
|
|
47
|
+
invalid_request 'Unable to parse query'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def invalid_variables
|
|
51
|
+
invalid_request 'Unable to parse variables'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def internal_error
|
|
55
|
+
render_error 500, 'Internal error'
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GraphiQL::Rails.config.csrf = true
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
Engine.routes.draw do
|
|
4
|
+
if Rails.config.graphiql
|
|
5
|
+
# Empty :graphql_path will cause GraphiQL to use its own URL.
|
|
6
|
+
mount GraphiQL::Rails::Engine => '/', :graphql_path => ''
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
post '/' => 'schema#execute'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'rails'
|
|
2
|
+
require 'graphql'
|
|
3
|
+
require 'graphql/relay'
|
|
4
|
+
require 'graphiql/rails'
|
|
5
|
+
|
|
6
|
+
require 'graphql/rails/version'
|
|
7
|
+
require 'graphql/rails/dsl'
|
|
8
|
+
require 'graphql/rails/engine'
|
|
9
|
+
require 'graphql/rails/config'
|
|
10
|
+
require 'graphql/rails/config'
|
|
11
|
+
require 'graphql/rails/types'
|
|
12
|
+
require 'graphql/rails/node_identification'
|
|
13
|
+
require 'graphql/rails/controller_extensions'
|
|
14
|
+
require 'graphql/rails/schema'
|
|
15
|
+
require 'graphql/rails/callbacks'
|
|
16
|
+
require 'graphql/rails/operations'
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
class Operations
|
|
4
|
+
module Callbacks
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
include ActiveSupport::Callbacks
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
define_callbacks :perform_operation
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
[:before, :after, :around].each do |callback|
|
|
14
|
+
define_method "#{callback}_operation" do |*names, &blk|
|
|
15
|
+
insert_callbacks(names, blk) do |name, options|
|
|
16
|
+
set_callback(:perform_operation, callback, name, options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
alias_method :"#{callback}_filter", :"#{callback}_operation"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def normalize_callback_options(options)
|
|
25
|
+
normalize_callback_option(options, :only, :if)
|
|
26
|
+
normalize_callback_option(options, :except, :unless)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def normalize_callback_option(options, from, to)
|
|
30
|
+
return unless options[from]
|
|
31
|
+
check = -> do
|
|
32
|
+
Array(options[from]).find { |operation| name == operation }
|
|
33
|
+
end
|
|
34
|
+
options[to] = Array(options[to]) + [check]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def insert_callbacks(callbacks, block = nil)
|
|
38
|
+
options = callbacks.extract_options!
|
|
39
|
+
normalize_callback_options(options)
|
|
40
|
+
callbacks.push(block) if block
|
|
41
|
+
callbacks.each do |callback|
|
|
42
|
+
yield callback, options
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
def configure
|
|
6
|
+
yield config
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def config
|
|
10
|
+
@config ||= OpenStruct.new({
|
|
11
|
+
# Should the GraphiQL web interface be served?
|
|
12
|
+
:graphiql => ::Rails.env.development?,
|
|
13
|
+
|
|
14
|
+
# Should names be converted to lowerCamelCase per GraphQL convention?
|
|
15
|
+
# For example, should :get_user_tasks become 'getUserTasks'?
|
|
16
|
+
:camel_case => true,
|
|
17
|
+
|
|
18
|
+
# Should object IDs be globally unique?
|
|
19
|
+
# This is necessary to conform to the Relay Global Object ID spec.
|
|
20
|
+
:global_ids => true,
|
|
21
|
+
|
|
22
|
+
# Should the following extensions be loaded?
|
|
23
|
+
:mongoid => defined?(::Mongoid),
|
|
24
|
+
:cancan => defined?(::CanCan),
|
|
25
|
+
})
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
module ControllerExtensions
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
def add(&block)
|
|
7
|
+
callbacks.push block
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def included(base)
|
|
11
|
+
callbacks.each do |callback|
|
|
12
|
+
base.class_eval(&callback)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def callbacks
|
|
19
|
+
@callbacks ||= []
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
class DSL < BasicObject
|
|
4
|
+
def run(&block)
|
|
5
|
+
@self = eval('self', block.binding)
|
|
6
|
+
instance_eval(&block)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def method_missing(method, *args, &block)
|
|
10
|
+
begin
|
|
11
|
+
@self.send(method, *args, &block)
|
|
12
|
+
rescue
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Inflections must be added before the namespace is isolated, because the
|
|
2
|
+
# namespace's route prefix is calculated and cached at that time.
|
|
3
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
|
4
|
+
inflect.acronym 'GraphQL'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module GraphQL
|
|
8
|
+
module Rails
|
|
9
|
+
mattr_accessor :logger
|
|
10
|
+
|
|
11
|
+
class Engine < ::Rails::Engine
|
|
12
|
+
isolate_namespace GraphQL::Rails
|
|
13
|
+
|
|
14
|
+
initializer 'graphql-rails.autoload', :before => :set_autoload_paths do |app|
|
|
15
|
+
# Even though we aren't using symbolic autoloading of operations, they
|
|
16
|
+
# must be included in autoload_paths in order to be unloaded during
|
|
17
|
+
# reload operations.
|
|
18
|
+
@graph_path = app.root.join('app', 'graph')
|
|
19
|
+
app.config.autoload_paths += [
|
|
20
|
+
@graph_path.join('types'),
|
|
21
|
+
@graph_path.join('operations'),
|
|
22
|
+
]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
initializer 'graphql-rails.logger', :after => :initialize_logger do |app|
|
|
26
|
+
logger = ::Rails.logger.clone
|
|
27
|
+
logger.class_eval do
|
|
28
|
+
def exception(e)
|
|
29
|
+
begin
|
|
30
|
+
error "#{e.class.name} (#{e.message}):"
|
|
31
|
+
error " #{e.backtrace.join("\n ")}"
|
|
32
|
+
rescue
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
Rails.logger = logger
|
|
37
|
+
Rails.logger.debug 'Initialized logger'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
initializer 'graphql-rails.extensions', :after => :load_config_initializers do |app|
|
|
41
|
+
# These depend upon a loaded Rails app, so we load them dynamically.
|
|
42
|
+
extensions = File.join(File.dirname(__FILE__), 'extensions', '*.rb')
|
|
43
|
+
Dir[extensions].each do |file|
|
|
44
|
+
require file
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
initializer 'graphql-rails.prepare', :before => :add_to_prepare_blocks do
|
|
49
|
+
# The block executes in the context of the reloader, so we have to
|
|
50
|
+
# preserve a reference to the engine instance.
|
|
51
|
+
engine = self
|
|
52
|
+
config.to_prepare_blocks.push -> do
|
|
53
|
+
engine.reload!
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def reload!
|
|
58
|
+
Types.clear
|
|
59
|
+
Schema.clear
|
|
60
|
+
Rails.logger.debug 'Loading operations'
|
|
61
|
+
Dir[@graph_path.join('operations', '**', '*.rb')].each do |file|
|
|
62
|
+
Rails.logger.debug "Loading file: #{file}"
|
|
63
|
+
require_dependency file
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
if Rails.config.cancan
|
|
4
|
+
Rails.logger.debug 'Loading CanCan extensions'
|
|
5
|
+
|
|
6
|
+
Operations.class_eval do
|
|
7
|
+
extend Forwardable
|
|
8
|
+
def_delegators :current_ability, :can?, :cannot?
|
|
9
|
+
|
|
10
|
+
def self.check_authorization(options = {})
|
|
11
|
+
self.after_filter(options.slice(:only, :except)) do |instance|
|
|
12
|
+
next if instance.instance_variable_defined?(:@authorized)
|
|
13
|
+
next if options[:if] && !instance.send(options[:if])
|
|
14
|
+
next if options[:unless] && instance.send(options[:unless])
|
|
15
|
+
raise 'This operation failed to perform an authorization check'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def skip_authorization_check(*args)
|
|
20
|
+
self.before_filter(*args) do |instance|
|
|
21
|
+
instance.instance_variable_set(:@authorized, true)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def authorize!(*args)
|
|
26
|
+
begin
|
|
27
|
+
@authorized = true
|
|
28
|
+
current_ability.authorize!(*args)
|
|
29
|
+
rescue CanCan::AccessDenied
|
|
30
|
+
raise 'You are not authorized to perform this operation'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def current_ability
|
|
35
|
+
@current_ability ||= ::Ability.new(current_user)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def current_user
|
|
39
|
+
ctx[:current_user]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
ControllerExtensions.add do
|
|
44
|
+
before_filter do
|
|
45
|
+
context[:current_user] = current_user
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
if Rails.config.mongoid
|
|
4
|
+
Rails.logger.debug 'Loading Mongoid extensions'
|
|
5
|
+
|
|
6
|
+
GraphQL::Relay::BaseConnection.register_connection_implementation(
|
|
7
|
+
::Mongoid::Relations::Targets::Enumerable,
|
|
8
|
+
GraphQL::Relay::RelationConnection
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
module Mongoid
|
|
12
|
+
extend self
|
|
13
|
+
|
|
14
|
+
NAMESPACE = 'MG'
|
|
15
|
+
|
|
16
|
+
def clear
|
|
17
|
+
@types = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def resolve(type)
|
|
21
|
+
types[type] || build_type(type)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def lookup(type_name, id)
|
|
25
|
+
return if Types.use_namespaces? && !type_name.starts_with?(NAMESPACE)
|
|
26
|
+
types.each_pair do |type, graph_type|
|
|
27
|
+
return type.find(id) if graph_type.name == type_name
|
|
28
|
+
end
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def types
|
|
35
|
+
@types ||= {
|
|
36
|
+
::Mongoid::Boolean => GraphQL::BOOLEAN_TYPE,
|
|
37
|
+
BSON::ObjectId => GraphQL::STRING_TYPE,
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def build_type(type)
|
|
42
|
+
return nil unless type.included_modules.include?(::Mongoid::Document)
|
|
43
|
+
Rails.logger.debug "Building Mongoid::Document type: #{type.name}"
|
|
44
|
+
|
|
45
|
+
# TODO: Support parent types/interfaces.
|
|
46
|
+
type_name = to_name(type)
|
|
47
|
+
types[type] = GraphQL::ObjectType.define do
|
|
48
|
+
name type_name
|
|
49
|
+
|
|
50
|
+
if Rails.config.global_ids
|
|
51
|
+
interfaces [NodeIdentification.interface]
|
|
52
|
+
global_id_field :id
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
type.fields.each_value do |field_value|
|
|
56
|
+
field field_value.name do
|
|
57
|
+
type -> { Types.resolve(field_value.type) }
|
|
58
|
+
description field_value.label unless field_value.label.blank?
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
type.relations.each_value do |relationship|
|
|
63
|
+
# TODO: Add polymorphic support.
|
|
64
|
+
if relationship.polymorphic?
|
|
65
|
+
Rails.logger.warn(
|
|
66
|
+
"Skipping polymorphic relationship: #{relationship.name}"
|
|
67
|
+
)
|
|
68
|
+
next
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if relationship.many?
|
|
72
|
+
connection relationship.name do
|
|
73
|
+
type -> { Types.resolve(relationship.klass).connection_type }
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
field relationship.name do
|
|
77
|
+
type -> { Types.resolve(relationship.klass) }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def to_name(type)
|
|
85
|
+
if Types.use_namespaces?
|
|
86
|
+
NAMESPACE + type.name
|
|
87
|
+
else
|
|
88
|
+
type.name
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
Types.add_extension Mongoid
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define do
|
|
4
|
+
object_from_id -> (id, ctx) do
|
|
5
|
+
Types.lookup(*NodeIdentification.from_global_id(id))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
type_from_object -> (obj) do
|
|
9
|
+
Types.resolve(obj.class)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
class Operations
|
|
4
|
+
extend Forwardable
|
|
5
|
+
include Callbacks
|
|
6
|
+
|
|
7
|
+
def initialize(options = {})
|
|
8
|
+
@options = OpenStruct.new(options)
|
|
9
|
+
self.class.instance_eval do
|
|
10
|
+
def_delegators :@options, *options.keys
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.query(hash, &block)
|
|
15
|
+
hash = extract_pair(hash)
|
|
16
|
+
Rails.logger.debug "Adding query: #{to_name(hash[:name])}"
|
|
17
|
+
|
|
18
|
+
definition = QueryDefinition.new(self)
|
|
19
|
+
definition.run(&block)
|
|
20
|
+
definition.run do
|
|
21
|
+
name hash[:name]
|
|
22
|
+
type hash[:type]
|
|
23
|
+
end
|
|
24
|
+
Schema.add_query definition.field
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# TODO: Implement mutations and subscriptions.
|
|
28
|
+
# TODO: Implement model functions (only, exclude, rename, etc.)
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def self.extract_pair(hash)
|
|
33
|
+
unless hash.length == 1
|
|
34
|
+
raise 'Hash must contain a single :name => Type pair.'
|
|
35
|
+
end
|
|
36
|
+
{name: hash.keys.first, type: hash.values.first}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# TODO: Ensure consistent naming convention around everything.
|
|
40
|
+
def self.to_name(symbol)
|
|
41
|
+
if Rails.config.camel_case
|
|
42
|
+
symbol.to_s.camelize(:lower)
|
|
43
|
+
else
|
|
44
|
+
symbol.to_s
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class QueryDefinition < DSL
|
|
49
|
+
attr_reader :field
|
|
50
|
+
|
|
51
|
+
def initialize(klass)
|
|
52
|
+
@klass = klass
|
|
53
|
+
@field = ::GraphQL::Field.new
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def name(name)
|
|
57
|
+
@name = name
|
|
58
|
+
@field.name = to_name(name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def type(type)
|
|
62
|
+
@type = type
|
|
63
|
+
@field.type = Types.resolve(type)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def description(description)
|
|
67
|
+
@field.description = description
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def argument(name, type, required = false)
|
|
71
|
+
argument = ::GraphQL::Argument.new
|
|
72
|
+
argument.name = to_name(name)
|
|
73
|
+
argument.type = Types.resolve(type, required == :required)
|
|
74
|
+
@field.arguments[argument.name] = argument
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def resolve(&block)
|
|
78
|
+
field.resolve = -> (obj, args, ctx) do
|
|
79
|
+
instance = @klass.new({
|
|
80
|
+
op: :query, name: @name, type: @type,
|
|
81
|
+
obj: obj, args: args, ctx: ctx
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
instance.run_callbacks(:perform_operation) do
|
|
86
|
+
instance.instance_eval(&block)
|
|
87
|
+
end
|
|
88
|
+
rescue => e
|
|
89
|
+
::GraphQL::ExecutionError.new(e.message)
|
|
90
|
+
rescue ::Exception => e
|
|
91
|
+
Rails.logger.error "Unexpected exception during query: #{@name}"
|
|
92
|
+
Rails.logger.exception e
|
|
93
|
+
::GraphQL::ExecutionError.new('Internal error')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
module Schema
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
def clear
|
|
7
|
+
@schema = nil
|
|
8
|
+
@fields = Hash.new { |hash, key| hash[key] = [] }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
TYPES = [:query, :mutation, :subscription]
|
|
12
|
+
|
|
13
|
+
TYPES.each do |type|
|
|
14
|
+
define_method "add_#{type.to_s}" do |field|
|
|
15
|
+
@schema = nil # Invalidate cached schema.
|
|
16
|
+
@fields[type].push field
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def instance
|
|
21
|
+
# TODO: Support max_depth and types.
|
|
22
|
+
# TODO: Sweep available options and expose in config.
|
|
23
|
+
@schema ||= GraphQL::Schema.new begin
|
|
24
|
+
TYPES.reduce({}) do |schema, type|
|
|
25
|
+
fields = @fields[type]
|
|
26
|
+
unless fields.empty?
|
|
27
|
+
schema[type] = GraphQL::ObjectType.define do
|
|
28
|
+
name type.to_s.capitalize
|
|
29
|
+
description "Root #{type.to_s} for this schema"
|
|
30
|
+
fields.each do |value|
|
|
31
|
+
field value.name, field: value
|
|
32
|
+
end
|
|
33
|
+
if Rails.config.global_ids && type == :query
|
|
34
|
+
field :node, field: NodeIdentification.field
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
schema
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module GraphQL
|
|
2
|
+
module Rails
|
|
3
|
+
module Types
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
def clear
|
|
7
|
+
@types = nil
|
|
8
|
+
extensions.each do |extension|
|
|
9
|
+
extension.clear
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Resolve an arbitrary type to a GraphQL type.
|
|
14
|
+
def resolve(type, required = false)
|
|
15
|
+
if type.nil?
|
|
16
|
+
raise 'Cannot resolve nil type.'
|
|
17
|
+
elsif required
|
|
18
|
+
resolve(type).to_non_null_type
|
|
19
|
+
elsif type.is_a?(GraphQL::BaseType)
|
|
20
|
+
type
|
|
21
|
+
elsif type.is_a?(Array)
|
|
22
|
+
unless type.length == 1
|
|
23
|
+
raise 'Lists must be specified with single-element arrays.'
|
|
24
|
+
end
|
|
25
|
+
resolve(type.first).to_list_type
|
|
26
|
+
elsif types.include?(type)
|
|
27
|
+
resolve(types[type])
|
|
28
|
+
else
|
|
29
|
+
resolve(try_extensions(:resolve, type) || begin
|
|
30
|
+
# TODO: Remove this hack.
|
|
31
|
+
Rails.logger.warn "Unable to resolve type: #{type.name}"
|
|
32
|
+
String
|
|
33
|
+
end)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Lookup an arbitrary object from its GraphQL type name and ID.
|
|
38
|
+
def lookup(type_name, id)
|
|
39
|
+
try_extensions(:lookup, type_name, id)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Should extensions namespace their type names?
|
|
43
|
+
# This is necessary if multiple extensions are loaded, so as to avoid
|
|
44
|
+
# collisions in the shared type namespace.
|
|
45
|
+
def use_namespaces?
|
|
46
|
+
extensions.count > 1
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Add an extension to the type system.
|
|
50
|
+
# Generally, each ORM will have its own extension.
|
|
51
|
+
def add_extension(extension)
|
|
52
|
+
extensions.push extension
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Default mapping of built-in scalar types to GraphQL types.
|
|
58
|
+
def types
|
|
59
|
+
@types ||= {
|
|
60
|
+
String => GraphQL::STRING_TYPE,
|
|
61
|
+
Boolean => GraphQL::BOOLEAN_TYPE,
|
|
62
|
+
|
|
63
|
+
Fixnum => GraphQL::INT_TYPE,
|
|
64
|
+
Integer => GraphQL::INT_TYPE,
|
|
65
|
+
Float => GraphQL::FLOAT_TYPE,
|
|
66
|
+
|
|
67
|
+
Date => GraphQL::STRING_TYPE,
|
|
68
|
+
Time => GraphQL::STRING_TYPE,
|
|
69
|
+
DateTime => GraphQL::STRING_TYPE,
|
|
70
|
+
|
|
71
|
+
Array => GraphQL::STRING_TYPE,
|
|
72
|
+
Object => GraphQL::STRING_TYPE,
|
|
73
|
+
Hash => GraphQL::STRING_TYPE,
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def extensions
|
|
78
|
+
@extensions ||= []
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Try a function on each extension, and return the result from
|
|
82
|
+
# the first extension that returns a non-nil value.
|
|
83
|
+
def try_extensions(method, *args, &block)
|
|
84
|
+
extensions.each do |extension|
|
|
85
|
+
result = extension.send(method, *args, &block)
|
|
86
|
+
return result unless result.nil?
|
|
87
|
+
end
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: graphql-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- James Reggio
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-06-07 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ~>
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '4'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ~>
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '4'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: graphql
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ~>
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.13'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ~>
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.13'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: graphql-relay
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ~>
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.9'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ~>
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.9'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: graphiql-rails
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ~>
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.2'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ~>
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.2'
|
|
69
|
+
description: |
|
|
70
|
+
Zero-configuration GraphQL + Relay support for Rails.
|
|
71
|
+
|
|
72
|
+
- Adds a route to process GraphQL operations.
|
|
73
|
+
- Provides a visual editor (GraphiQL) for development.
|
|
74
|
+
- Allows you to specify GraphQL queries and mutations as though they were controller actions.
|
|
75
|
+
- Automatically maps Mongoid models to GraphQL types.
|
|
76
|
+
- Seamlessly integrates with CanCan.
|
|
77
|
+
email:
|
|
78
|
+
- james.reggio@gmail.com
|
|
79
|
+
executables: []
|
|
80
|
+
extensions: []
|
|
81
|
+
extra_rdoc_files: []
|
|
82
|
+
files:
|
|
83
|
+
- app/controllers/graphql/rails/schema_controller.rb
|
|
84
|
+
- config/initializers/graphiql.rb
|
|
85
|
+
- config/routes.rb
|
|
86
|
+
- lib/graphql/rails/callbacks.rb
|
|
87
|
+
- lib/graphql/rails/config.rb
|
|
88
|
+
- lib/graphql/rails/controller_extensions.rb
|
|
89
|
+
- lib/graphql/rails/dsl.rb
|
|
90
|
+
- lib/graphql/rails/engine.rb
|
|
91
|
+
- lib/graphql/rails/extensions/cancan.rb
|
|
92
|
+
- lib/graphql/rails/extensions/mongoid.rb
|
|
93
|
+
- lib/graphql/rails/node_identification.rb
|
|
94
|
+
- lib/graphql/rails/operations.rb
|
|
95
|
+
- lib/graphql/rails/schema.rb
|
|
96
|
+
- lib/graphql/rails/types.rb
|
|
97
|
+
- lib/graphql/rails/version.rb
|
|
98
|
+
- lib/graphql/rails.rb
|
|
99
|
+
- lib/tasks/graphql_rails_tasks.rake
|
|
100
|
+
- LICENSE
|
|
101
|
+
homepage: https://github.com/jamesreggio/graphql-rails
|
|
102
|
+
licenses:
|
|
103
|
+
- MIT
|
|
104
|
+
metadata: {}
|
|
105
|
+
post_install_message:
|
|
106
|
+
rdoc_options: []
|
|
107
|
+
require_paths:
|
|
108
|
+
- lib
|
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
110
|
+
requirements:
|
|
111
|
+
- - '>='
|
|
112
|
+
- !ruby/object:Gem::Version
|
|
113
|
+
version: '0'
|
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - '>='
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '0'
|
|
119
|
+
requirements: []
|
|
120
|
+
rubyforge_project:
|
|
121
|
+
rubygems_version: 2.0.14.1
|
|
122
|
+
signing_key:
|
|
123
|
+
specification_version: 4
|
|
124
|
+
summary: Zero-configuration GraphQL + Relay support for Rails
|
|
125
|
+
test_files: []
|