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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.hound.yml +3 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +34 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +5 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +20 -0
  10. data/Gemfile.lock +98 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +122 -0
  13. data/Rakefile +6 -0
  14. data/bin/console +14 -0
  15. data/bin/setup +8 -0
  16. data/graphql_rails.gemspec +29 -0
  17. data/lib/graphiti.rb +10 -0
  18. data/lib/graphiti/attribute.rb +86 -0
  19. data/lib/graphiti/controller.rb +54 -0
  20. data/lib/graphiti/controller/action_configuration.rb +46 -0
  21. data/lib/graphiti/controller/configuration.rb +32 -0
  22. data/lib/graphiti/controller/controller_function.rb +41 -0
  23. data/lib/graphiti/controller/request.rb +40 -0
  24. data/lib/graphiti/errors/execution_error.rb +18 -0
  25. data/lib/graphiti/errors/validation_error.rb +26 -0
  26. data/lib/graphiti/model.rb +33 -0
  27. data/lib/graphiti/model/configuration.rb +81 -0
  28. data/lib/graphiti/router.rb +65 -0
  29. data/lib/graphiti/router/action.rb +21 -0
  30. data/lib/graphiti/router/mutation_action.rb +18 -0
  31. data/lib/graphiti/router/query_action.rb +18 -0
  32. data/lib/graphiti/router/resource_actions_builder.rb +82 -0
  33. data/lib/graphiti/router/schema_builder.rb +37 -0
  34. data/lib/graphiti/version.rb +5 -0
  35. data/lib/graphql_rails.rb +10 -0
  36. data/lib/graphql_rails/attribute.rb +86 -0
  37. data/lib/graphql_rails/controller.rb +54 -0
  38. data/lib/graphql_rails/controller/action_configuration.rb +46 -0
  39. data/lib/graphql_rails/controller/action_path_parser.rb +75 -0
  40. data/lib/graphql_rails/controller/configuration.rb +32 -0
  41. data/lib/graphql_rails/controller/controller_function.rb +41 -0
  42. data/lib/graphql_rails/controller/request.rb +40 -0
  43. data/lib/graphql_rails/errors/execution_error.rb +18 -0
  44. data/lib/graphql_rails/errors/validation_error.rb +26 -0
  45. data/lib/graphql_rails/model.rb +33 -0
  46. data/lib/graphql_rails/model/configuration.rb +81 -0
  47. data/lib/graphql_rails/router.rb +65 -0
  48. data/lib/graphql_rails/router/action.rb +21 -0
  49. data/lib/graphql_rails/router/mutation_action.rb +18 -0
  50. data/lib/graphql_rails/router/query_action.rb +18 -0
  51. data/lib/graphql_rails/router/resource_actions_builder.rb +82 -0
  52. data/lib/graphql_rails/router/schema_builder.rb +37 -0
  53. data/lib/graphql_rails/version.rb +5 -0
  54. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql_rails/version'
4
+ require 'graphql_rails/model'
5
+ require 'graphql_rails/router'
6
+ require 'graphql_rails/controller'
7
+
8
+ # wonders starts here
9
+ module GraphqlRails
10
+ 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