graphql-rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|