graphql_rails 0.1.0
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/.gitignore +13 -0
- data/.hound.yml +3 -0
- data/.rspec +3 -0
- data/.rubocop.yml +34 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +98 -0
- data/LICENSE.txt +21 -0
- data/README.md +122 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/graphql_rails.gemspec +29 -0
- data/lib/graphiti.rb +10 -0
- data/lib/graphiti/attribute.rb +86 -0
- data/lib/graphiti/controller.rb +54 -0
- data/lib/graphiti/controller/action_configuration.rb +46 -0
- data/lib/graphiti/controller/configuration.rb +32 -0
- data/lib/graphiti/controller/controller_function.rb +41 -0
- data/lib/graphiti/controller/request.rb +40 -0
- data/lib/graphiti/errors/execution_error.rb +18 -0
- data/lib/graphiti/errors/validation_error.rb +26 -0
- data/lib/graphiti/model.rb +33 -0
- data/lib/graphiti/model/configuration.rb +81 -0
- data/lib/graphiti/router.rb +65 -0
- data/lib/graphiti/router/action.rb +21 -0
- data/lib/graphiti/router/mutation_action.rb +18 -0
- data/lib/graphiti/router/query_action.rb +18 -0
- data/lib/graphiti/router/resource_actions_builder.rb +82 -0
- data/lib/graphiti/router/schema_builder.rb +37 -0
- data/lib/graphiti/version.rb +5 -0
- data/lib/graphql_rails.rb +10 -0
- data/lib/graphql_rails/attribute.rb +86 -0
- data/lib/graphql_rails/controller.rb +54 -0
- data/lib/graphql_rails/controller/action_configuration.rb +46 -0
- data/lib/graphql_rails/controller/action_path_parser.rb +75 -0
- data/lib/graphql_rails/controller/configuration.rb +32 -0
- data/lib/graphql_rails/controller/controller_function.rb +41 -0
- data/lib/graphql_rails/controller/request.rb +40 -0
- data/lib/graphql_rails/errors/execution_error.rb +18 -0
- data/lib/graphql_rails/errors/validation_error.rb +26 -0
- data/lib/graphql_rails/model.rb +33 -0
- data/lib/graphql_rails/model/configuration.rb +81 -0
- data/lib/graphql_rails/router.rb +65 -0
- data/lib/graphql_rails/router/action.rb +21 -0
- data/lib/graphql_rails/router/mutation_action.rb +18 -0
- data/lib/graphql_rails/router/query_action.rb +18 -0
- data/lib/graphql_rails/router/resource_actions_builder.rb +82 -0
- data/lib/graphql_rails/router/schema_builder.rb +37 -0
- data/lib/graphql_rails/version.rb +5 -0
- metadata +166 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
|
5
|
+
require_relative 'router/schema_builder'
|
6
|
+
require_relative 'router/mutation_action'
|
7
|
+
require_relative 'router/query_action'
|
8
|
+
require_relative 'router/resource_actions_builder'
|
9
|
+
|
10
|
+
module GraphqlRails
|
11
|
+
# graphql router that mimics Rails.application.routes
|
12
|
+
class Router
|
13
|
+
def self.draw(&block)
|
14
|
+
router = new
|
15
|
+
router.instance_eval(&block)
|
16
|
+
router.graphql_schema
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :actions, :namespace_name
|
20
|
+
|
21
|
+
def initialize(module_name: '')
|
22
|
+
@module_name = module_name
|
23
|
+
@actions ||= Set.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def scope(**options, &block)
|
27
|
+
full_module_name = [module_name, options[:module]].reject(&:empty?).join('/')
|
28
|
+
scoped_router = self.class.new(module_name: full_module_name)
|
29
|
+
scoped_router.instance_eval(&block)
|
30
|
+
actions.merge(scoped_router.actions)
|
31
|
+
end
|
32
|
+
|
33
|
+
def resources(name, **options, &block)
|
34
|
+
builder_options = default_action_options.merge(options)
|
35
|
+
actions_builder = ResourceActionsBuilder.new(name, **builder_options)
|
36
|
+
actions_builder.instance_eval(&block) if block
|
37
|
+
actions.merge(actions_builder.actions)
|
38
|
+
end
|
39
|
+
|
40
|
+
def query(name, **options)
|
41
|
+
actions << build_action(QueryAction, name, **options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def mutation(name, **options)
|
45
|
+
actions << build_action(MutationAction, name, **options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def graphql_schema
|
49
|
+
SchemaBuilder.new(queries: actions.select(&:query?), mutations: actions.select(&:mutation?)).call
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :module_name
|
55
|
+
|
56
|
+
def build_action(action_builder, name, **options)
|
57
|
+
action_options = default_action_options.merge(options)
|
58
|
+
action_builder.new(name, action_options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def default_action_options
|
62
|
+
{ module: module_name }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../controller/controller_function'
|
4
|
+
|
5
|
+
module GraphqlRails
|
6
|
+
class Router
|
7
|
+
# Generic class for any type graphql action. Should not be used directly
|
8
|
+
class Action
|
9
|
+
attr_reader :name, :controller_action_path
|
10
|
+
|
11
|
+
def initialize(name, to:, **options)
|
12
|
+
@name = name.to_s.camelize(:lower)
|
13
|
+
@controller_action_path = [options[:module].to_s, to].reject(&:empty?).join('/')
|
14
|
+
end
|
15
|
+
|
16
|
+
def options
|
17
|
+
{ function: Controller::ControllerFunction.build(controller_action_path) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'action'
|
4
|
+
|
5
|
+
module GraphqlRails
|
6
|
+
class Router
|
7
|
+
# stores mutation type graphql action info
|
8
|
+
class MutationAction < Action
|
9
|
+
def query?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def mutation?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'action'
|
4
|
+
|
5
|
+
module GraphqlRails
|
6
|
+
class Router
|
7
|
+
# stores query type graphql action info
|
8
|
+
class QueryAction < Action
|
9
|
+
def query?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def mutation?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'query_action'
|
4
|
+
require_relative 'mutation_action'
|
5
|
+
|
6
|
+
module GraphqlRails
|
7
|
+
class Router
|
8
|
+
# Generates graphql actions based on resource name and options
|
9
|
+
class ResourceActionsBuilder
|
10
|
+
AVAILABLE_ACTIONS = %i[show index create update destroy].freeze
|
11
|
+
|
12
|
+
def initialize(name, only: nil, except: [], **options)
|
13
|
+
@name = name.to_s
|
14
|
+
|
15
|
+
@options = options
|
16
|
+
@autogenerated_action_names = initial_action_names(only, except, AVAILABLE_ACTIONS)
|
17
|
+
end
|
18
|
+
|
19
|
+
def actions
|
20
|
+
@actions ||= initial_actions
|
21
|
+
end
|
22
|
+
|
23
|
+
def query(*args)
|
24
|
+
actions << build_query(*args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def mutation(*args)
|
28
|
+
actions << build_mutation(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :autogenerated_action_names, :name, :options
|
34
|
+
|
35
|
+
def initial_actions
|
36
|
+
actions = initial_query_actions
|
37
|
+
actions << build_mutation(:create, on: :member) if autogenerated_action_names.include?(:create)
|
38
|
+
actions << build_mutation(:update, on: :member) if autogenerated_action_names.include?(:update)
|
39
|
+
actions << build_mutation(:destroy, on: :member) if autogenerated_action_names.include?(:destroy)
|
40
|
+
actions
|
41
|
+
end
|
42
|
+
|
43
|
+
def initial_query_actions
|
44
|
+
actions = Set.new
|
45
|
+
|
46
|
+
if autogenerated_action_names.include?(:show)
|
47
|
+
actions << build_action(QueryAction, 'show', to: "#{name}#show", prefix: '', on: :member)
|
48
|
+
end
|
49
|
+
|
50
|
+
if autogenerated_action_names.include?(:index)
|
51
|
+
actions << build_action(QueryAction, 'index', to: "#{name}#index", prefix: '', on: :collection)
|
52
|
+
end
|
53
|
+
|
54
|
+
actions
|
55
|
+
end
|
56
|
+
|
57
|
+
def build_mutation(*args)
|
58
|
+
build_action(MutationAction, *args)
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_query(*args)
|
62
|
+
build_action(QueryAction, *args)
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_action(builder, action, prefix: action, on: :member, **custom_options)
|
66
|
+
action_options = options.merge(custom_options)
|
67
|
+
action_name = [prefix, resource_name(on)].reject(&:empty?).join('_')
|
68
|
+
builder.new(action_name, to: "#{name}##{action}", **action_options)
|
69
|
+
end
|
70
|
+
|
71
|
+
def initial_action_names(only, except, available)
|
72
|
+
alowed_actions = Array(only || available) & available
|
73
|
+
only_actions = alowed_actions.map(&:to_sym) - Array(except).map(&:to_sym)
|
74
|
+
Set.new(only_actions)
|
75
|
+
end
|
76
|
+
|
77
|
+
def resource_name(type)
|
78
|
+
type.to_sym == :member ? name.singularize : name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlRails
|
4
|
+
class Router
|
5
|
+
# builds GraphQL::Schema based on previously defined grahiti data
|
6
|
+
class SchemaBuilder
|
7
|
+
attr_reader :queries, :mutations
|
8
|
+
|
9
|
+
def initialize(queries:, mutations:)
|
10
|
+
@queries = queries
|
11
|
+
@mutations = mutations
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
query_type = build_type('Query', queries)
|
16
|
+
mutation_type = build_type('Mutation', mutations)
|
17
|
+
|
18
|
+
GraphQL::Schema.define do
|
19
|
+
query(query_type)
|
20
|
+
mutation(mutation_type)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_type(type_name, actions)
|
27
|
+
GraphQL::ObjectType.define do
|
28
|
+
name type_name
|
29
|
+
|
30
|
+
actions.each do |action|
|
31
|
+
field action.name, action.options
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql'
|
4
|
+
|
5
|
+
module GraphqlRails
|
6
|
+
# contains info about single graphql attribute
|
7
|
+
class Attribute
|
8
|
+
attr_reader :name, :type
|
9
|
+
|
10
|
+
def initialize(name, type = nil, required: false, hidden: false)
|
11
|
+
@name = name.to_s
|
12
|
+
@type = parse_type(type || type_by_attribute_name)
|
13
|
+
@required = required
|
14
|
+
@hidden = hidden
|
15
|
+
end
|
16
|
+
|
17
|
+
def graphql_field_type
|
18
|
+
@graphql_field_type ||= required? ? type.to_non_null_type : type
|
19
|
+
end
|
20
|
+
|
21
|
+
def required?
|
22
|
+
@required
|
23
|
+
end
|
24
|
+
|
25
|
+
def hidden?
|
26
|
+
@hidden
|
27
|
+
end
|
28
|
+
|
29
|
+
def field_name
|
30
|
+
field =
|
31
|
+
if name.end_with?('?')
|
32
|
+
"is_#{name.remove(/\?\Z/)}"
|
33
|
+
else
|
34
|
+
name
|
35
|
+
end
|
36
|
+
|
37
|
+
field.camelize(:lower)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def type_by_attribute_name
|
43
|
+
case name
|
44
|
+
when 'id', /_id\Z/
|
45
|
+
GraphQL::ID_TYPE
|
46
|
+
when /\?\Z/
|
47
|
+
GraphQL::BOOLEAN_TYPE
|
48
|
+
else
|
49
|
+
GraphQL::STRING_TYPE
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_type(type)
|
54
|
+
if graphql_type?(type)
|
55
|
+
type
|
56
|
+
elsif type.is_a?(String) || type.is_a?(Symbol)
|
57
|
+
map_type_name_to_type(type.to_s.downcase)
|
58
|
+
else
|
59
|
+
raise "Unsupported type #{type.inspect} (class: #{type.class})"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def graphql_type?(type)
|
64
|
+
type.is_a?(GraphQL::BaseType) ||
|
65
|
+
type.is_a?(GraphQL::ObjectType) ||
|
66
|
+
(defined?(GraphQL::Schema::Member) && type.is_a?(Class) && type < GraphQL::Schema::Member)
|
67
|
+
end
|
68
|
+
|
69
|
+
def map_type_name_to_type(type_name)
|
70
|
+
case type_name
|
71
|
+
when 'id'
|
72
|
+
GraphQL::ID_TYPE
|
73
|
+
when 'int', 'integer'
|
74
|
+
GraphQL::INT_TYPE
|
75
|
+
when 'string', 'str', 'text', 'time', 'date'
|
76
|
+
GraphQL::STRING_TYPE
|
77
|
+
when 'bool', 'boolean', 'mongoid::boolean'
|
78
|
+
GraphQL::BOOLEAN_TYPE
|
79
|
+
when 'float', 'double', 'decimal'
|
80
|
+
GraphQL::FLOAT_TYPE
|
81
|
+
else
|
82
|
+
raise "Don't know how to parse type with name #{type_name.inspect}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/hash_with_indifferent_access'
|
4
|
+
require_relative 'controller/configuration'
|
5
|
+
require_relative 'controller/request'
|
6
|
+
|
7
|
+
module GraphqlRails
|
8
|
+
# base class for all graphql_rails controllers
|
9
|
+
class Controller
|
10
|
+
class << self
|
11
|
+
def before_action(action_name)
|
12
|
+
controller_configuration.add_before_action(action_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def action(method_name)
|
16
|
+
controller_configuration.action(method_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def controller_configuration
|
20
|
+
@controller_configuration ||= Controller::Configuration.new(self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(graphql_request)
|
25
|
+
@graphql_request = graphql_request
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(method_name)
|
29
|
+
self.class.controller_configuration.before_actions.each { |action_name| send(action_name) }
|
30
|
+
|
31
|
+
begin
|
32
|
+
response = public_send(method_name)
|
33
|
+
render response if graphql_request.no_object_to_return?
|
34
|
+
rescue StandardError => error
|
35
|
+
render error: error
|
36
|
+
end
|
37
|
+
|
38
|
+
graphql_request.object_to_return
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
attr_reader :graphql_request
|
44
|
+
|
45
|
+
def render(object = nil, error: nil, errors: Array(error))
|
46
|
+
graphql_request.errors = errors
|
47
|
+
graphql_request.object_to_return = object
|
48
|
+
end
|
49
|
+
|
50
|
+
def params
|
51
|
+
@params = HashWithIndifferentAccess.new(graphql_request.params)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/string/filters'
|
4
|
+
require 'graphql_rails/attribute'
|
5
|
+
|
6
|
+
module GraphqlRails
|
7
|
+
class Controller
|
8
|
+
# stores all graphql_rails contoller specific config
|
9
|
+
class ActionConfiguration
|
10
|
+
attr_reader :attributes, :return_type
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@attributes = {}
|
14
|
+
@can_return_nil = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def permit(*no_type_attributes, **typed_attributes)
|
18
|
+
no_type_attributes.each { |attribute| permit_attribute(attribute) }
|
19
|
+
typed_attributes.each { |attribute, type| permit_attribute(attribute, type) }
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def can_return_nil
|
24
|
+
@can_return_nil = true
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def returns(new_return_type)
|
29
|
+
@return_type = new_return_type
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def can_return_nil?
|
34
|
+
@can_return_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def permit_attribute(name, type = nil)
|
40
|
+
field_name = name.to_s.remove(/!\Z/)
|
41
|
+
required = name.to_s.end_with?('!')
|
42
|
+
attributes[field_name] = Attribute.new(field_name, type, required: required)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|