graphql_rails 0.8.0 → 1.2.2

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +1 -0
  3. data/.rubocop.yml +3 -3
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +2 -2
  6. data/CHANGELOG.md +31 -0
  7. data/Gemfile +3 -2
  8. data/Gemfile.lock +147 -124
  9. data/docs/README.md +24 -8
  10. data/docs/_sidebar.md +3 -0
  11. data/docs/components/controller.md +194 -21
  12. data/docs/components/model.md +193 -5
  13. data/docs/components/routes.md +28 -0
  14. data/docs/getting_started/quick_start.md +10 -3
  15. data/docs/index.html +1 -1
  16. data/docs/other_tools/query_runner.md +49 -0
  17. data/docs/other_tools/schema_dump.md +29 -0
  18. data/docs/testing/testing.md +3 -1
  19. data/graphql_rails.gemspec +5 -5
  20. data/lib/generators/graphql_rails/install_generator.rb +50 -0
  21. data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
  22. data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
  23. data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
  24. data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
  25. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +21 -0
  26. data/lib/graphql_rails.rb +2 -0
  27. data/lib/graphql_rails/attributes/attributable.rb +20 -21
  28. data/lib/graphql_rails/attributes/attribute.rb +41 -4
  29. data/lib/graphql_rails/attributes/attribute_name_parser.rb +4 -4
  30. data/lib/graphql_rails/attributes/input_attribute.rb +24 -10
  31. data/lib/graphql_rails/attributes/input_type_parser.rb +24 -46
  32. data/lib/graphql_rails/attributes/type_parseable.rb +132 -0
  33. data/lib/graphql_rails/attributes/type_parser.rb +58 -54
  34. data/lib/graphql_rails/concerns/service.rb +19 -0
  35. data/lib/graphql_rails/controller.rb +26 -22
  36. data/lib/graphql_rails/controller/action.rb +12 -67
  37. data/lib/graphql_rails/controller/action_configuration.rb +70 -34
  38. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  39. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  40. data/lib/graphql_rails/controller/configuration.rb +56 -5
  41. data/lib/graphql_rails/controller/log_controller_action.rb +11 -6
  42. data/lib/graphql_rails/controller/request.rb +29 -8
  43. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  44. data/lib/graphql_rails/decorator/relation_decorator.rb +1 -5
  45. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  46. data/lib/graphql_rails/errors/execution_error.rb +6 -7
  47. data/lib/graphql_rails/errors/system_error.rb +14 -0
  48. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  49. data/lib/graphql_rails/input_configurable.rb +47 -0
  50. data/lib/graphql_rails/model.rb +19 -4
  51. data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
  52. data/lib/graphql_rails/model/build_connection_type.rb +52 -0
  53. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +5 -5
  54. data/lib/graphql_rails/model/build_enum_type.rb +39 -10
  55. data/lib/graphql_rails/model/build_graphql_input_type.rb +8 -4
  56. data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
  57. data/lib/graphql_rails/model/configurable.rb +6 -2
  58. data/lib/graphql_rails/model/configuration.rb +11 -10
  59. data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
  60. data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
  61. data/lib/graphql_rails/model/input.rb +10 -6
  62. data/lib/graphql_rails/query_runner.rb +68 -0
  63. data/lib/graphql_rails/railtie.rb +10 -0
  64. data/lib/graphql_rails/router.rb +40 -13
  65. data/lib/graphql_rails/router/resource_routes_builder.rb +10 -9
  66. data/lib/graphql_rails/router/route.rb +21 -6
  67. data/lib/graphql_rails/router/schema_builder.rb +30 -11
  68. data/lib/graphql_rails/rspec_controller_helpers.rb +6 -4
  69. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  70. data/lib/graphql_rails/tasks/schema.rake +14 -0
  71. data/lib/graphql_rails/version.rb +1 -1
  72. metadata +48 -21
  73. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  74. data/lib/graphql_rails/controller/format_results.rb +0 -36
  75. data/lib/graphql_rails/model/build_graphql_type.rb +0 -37
@@ -17,10 +17,6 @@ module GraphqlRails
17
17
  #
18
18
  # YourModel.new.graphql_type # => type with [:id, :title] attributes
19
19
  module Model
20
- def self.included(base)
21
- base.extend(ClassMethods)
22
- end
23
-
24
20
  # static methods for GraphqlRails::Model
25
21
  module ClassMethods
26
22
  def inherited(subclass)
@@ -36,5 +32,24 @@ module GraphqlRails
36
32
  @graphql
37
33
  end
38
34
  end
35
+
36
+ def self.included(base)
37
+ base.extend(ClassMethods)
38
+ end
39
+
40
+ def graphql_context
41
+ @graphql_context
42
+ end
43
+
44
+ def graphql_context=(value)
45
+ @graphql_context = value
46
+ end
47
+
48
+ def with_graphql_context(graphql_context)
49
+ self.graphql_context = graphql_context
50
+ yield(self)
51
+ ensure
52
+ self.graphql_context = nil
53
+ end
39
54
  end
40
55
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ module Model
5
+ # Adds graphql attributes as graphql fields to given graphql schema object.
6
+ class AddFieldsToGraphqlType
7
+ require 'graphql_rails/concerns/service'
8
+ require 'graphql_rails/model/call_graphql_model_method'
9
+
10
+ include ::GraphqlRails::Service
11
+
12
+ def initialize(klass:, attributes:)
13
+ @klass = klass
14
+ @attributes = attributes
15
+ end
16
+
17
+ def call
18
+ attributes.each { |attribute| define_graphql_field(attribute) }
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :attributes, :klass
24
+
25
+ def define_graphql_field(attribute) # rubocop:disable Metrics/MethodLength)
26
+ klass.class_eval do
27
+ field(*attribute.field_args, **attribute.field_options) do
28
+ attribute.attributes.values.each do |arg_attribute|
29
+ argument(*arg_attribute.input_argument_args, **arg_attribute.input_argument_options)
30
+ end
31
+ end
32
+
33
+ define_method(attribute.field_name) do |**kwargs|
34
+ CallGraphqlModelMethod.call(
35
+ model: object,
36
+ attribute_config: attribute,
37
+ method_keyword_arguments: kwargs,
38
+ graphql_context: context
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+ require 'graphql_rails/model/build_connection_type/count_items'
5
+
6
+ module GraphqlRails
7
+ module Model
8
+ # builds connection type from graphql type with some extra attributes
9
+ class BuildConnectionType
10
+ require 'graphql_rails/concerns/service'
11
+
12
+ include ::GraphqlRails::Service
13
+
14
+ attr_reader :initial_type
15
+
16
+ def initialize(initial_type)
17
+ @initial_type = initial_type
18
+ end
19
+
20
+ def call
21
+ build_connection_type
22
+ end
23
+
24
+ private
25
+
26
+ def build_connection_type
27
+ edge_type = build_edge_type
28
+ type = initial_type
29
+ Class.new(GraphQL::Types::Relay::BaseConnection) do
30
+ graphql_name("#{type.graphql_name}Connection")
31
+ edge_type(edge_type)
32
+
33
+ field :total, Integer, null: false
34
+
35
+ def total
36
+ CountItems.call(self)
37
+ end
38
+ end
39
+ end
40
+
41
+ def build_edge_type
42
+ type = initial_type
43
+
44
+ Class.new(GraphQL::Types::Relay::BaseEdge) do
45
+ graphql_name("#{type.graphql_name}Edge")
46
+
47
+ node_type(type)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,15 +2,15 @@
2
2
 
3
3
  module GraphqlRails
4
4
  module Model
5
- class Configuration
5
+ class BuildConnectionType
6
6
  # Used when generating ConnectionType.
7
7
  # It handles all the logic which is related with counting total items
8
8
  class CountItems
9
- def self.call(*args)
10
- new(*args).call
11
- end
9
+ require 'graphql_rails/concerns/service'
10
+
11
+ include ::GraphqlRails::Service
12
12
 
13
- def initialize(graphql_object, _args, _ctx)
13
+ def initialize(graphql_object)
14
14
  @graphql_object = graphql_object
15
15
  end
16
16
 
@@ -7,9 +7,10 @@ module GraphqlRails
7
7
  module Model
8
8
  # contains info about single graphql attribute
9
9
  class BuildEnumType
10
- def self.call(*args)
11
- new(*args).call
12
- end
10
+ class InvalidEnum < GraphqlRails::Error; end
11
+ require 'graphql_rails/concerns/service'
12
+
13
+ include ::GraphqlRails::Service
13
14
 
14
15
  def initialize(name, allowed_values:, description: nil)
15
16
  @name = name
@@ -18,22 +19,50 @@ module GraphqlRails
18
19
  end
19
20
 
20
21
  def call
21
- allowed_values = self.allowed_values
22
- enum_name = name.to_s.camelize
23
- enum_description = description
22
+ validate
23
+ build_enum
24
+ end
25
+
26
+ protected
27
+
28
+ attr_reader :name, :allowed_values, :description
29
+
30
+ def validate
31
+ return if allowed_values.is_a?(Array) && !allowed_values.empty?
32
+
33
+ validate_enum_type
34
+ validate_enum_content
35
+ end
36
+
37
+ def validate_enum_type
38
+ return if allowed_values.is_a?(Array)
39
+
40
+ raise InvalidEnum, "Enum must be instance of Array, but instance of #{allowed_values.class} was given"
41
+ end
42
+
43
+ def validate_enum_content
44
+ return unless allowed_values.empty?
45
+
46
+ raise InvalidEnum, 'At lest one enum option must be given'
47
+ end
48
+
49
+ def formatted_name
50
+ name.to_s.camelize
51
+ end
24
52
 
53
+ def build_enum(allowed_values: self.allowed_values, enum_name: formatted_name, enum_description: description)
25
54
  Class.new(GraphQL::Schema::Enum) do
26
55
  allowed_values.each do |allowed_value|
27
56
  graphql_name(enum_name)
28
57
  description(enum_description) if enum_description
29
58
  value(allowed_value.to_s.underscore.upcase, value: allowed_value)
30
59
  end
60
+
61
+ def self.inspect
62
+ "#{GraphQL::Schema::Enum}(#{graphql_name})"
63
+ end
31
64
  end
32
65
  end
33
-
34
- protected
35
-
36
- attr_reader :name, :allowed_values, :description
37
66
  end
38
67
  end
39
68
  end
@@ -4,9 +4,9 @@ module GraphqlRails
4
4
  module Model
5
5
  # stores information about model specific config, like attributes and types
6
6
  class BuildGraphqlInputType
7
- def self.call(*args)
8
- new(*args).call
9
- end
7
+ require 'graphql_rails/concerns/service'
8
+
9
+ include ::GraphqlRails::Service
10
10
 
11
11
  def initialize(name:, description: nil, attributes:)
12
12
  @name = name
@@ -24,7 +24,11 @@ module GraphqlRails
24
24
  description(type_description)
25
25
 
26
26
  type_attributes.each_value do |type_attribute|
27
- argument(*type_attribute.input_argument_args)
27
+ argument(*type_attribute.input_argument_args, **type_attribute.input_argument_options)
28
+ end
29
+
30
+ def self.inspect
31
+ "#{GraphQL::Schema::InputObject}(#{graphql_name})"
28
32
  end
29
33
  end
30
34
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ module Model
5
+ # Executes model method and adds additional meta data if needed
6
+ class CallGraphqlModelMethod
7
+ require 'graphql_rails/concerns/service'
8
+
9
+ include ::GraphqlRails::Service
10
+
11
+ PAGINATION_KEYS = %i[before after first last].freeze
12
+
13
+ def initialize(model:, method_keyword_arguments:, graphql_context:, attribute_config:)
14
+ @model = model
15
+ @method_keyword_arguments = method_keyword_arguments
16
+ @graphql_context = graphql_context
17
+ @attribute_config = attribute_config
18
+ end
19
+
20
+ def call
21
+ with_graphql_context do
22
+ run_method
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :model, :attribute_config, :graphql_context, :method_keyword_arguments
29
+
30
+ def run_method
31
+ if custom_keyword_arguments.empty?
32
+ model.send(method_name)
33
+ else
34
+ formatted_arguments = formatted_method_input(custom_keyword_arguments)
35
+ model.send(method_name, **formatted_arguments)
36
+ end
37
+ end
38
+
39
+ def formatted_method_input(keyword_arguments)
40
+ keyword_arguments.transform_values do |input_argument|
41
+ formatted_method_input_argument(input_argument)
42
+ end
43
+ end
44
+
45
+ def formatted_method_input_argument(argument)
46
+ return argument.to_h if argument.is_a?(GraphQL::Schema::InputObject)
47
+
48
+ argument
49
+ end
50
+
51
+ def method_name
52
+ attribute_config.property
53
+ end
54
+
55
+ def paginated?
56
+ attribute_config.paginated?
57
+ end
58
+
59
+ def custom_keyword_arguments
60
+ return method_keyword_arguments unless paginated?
61
+
62
+ method_keyword_arguments.except(*PAGINATION_KEYS)
63
+ end
64
+
65
+ def with_graphql_context
66
+ return yield unless model.respond_to?(:with_graphql_context)
67
+
68
+ model.with_graphql_context(graphql_context) { yield }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -9,11 +9,15 @@ module GraphqlRails
9
9
  @attributes ||= {}
10
10
  end
11
11
 
12
- def name(type_name = nil)
13
- @name = type_name if type_name
12
+ def name(graphql_name = nil)
13
+ @name = graphql_name if graphql_name
14
14
  @name || default_name
15
15
  end
16
16
 
17
+ def type_name
18
+ @type_name ||= "#{name.camelize}Type#{SecureRandom.hex}"
19
+ end
20
+
17
21
  def description(new_description = nil)
18
22
  @description = new_description if new_description
19
23
  @description
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'graphql_rails/attributes'
4
- require 'graphql_rails/model/build_graphql_type'
4
+ require 'graphql_rails/model/find_or_build_graphql_type'
5
5
  require 'graphql_rails/model/build_enum_type'
6
6
  require 'graphql_rails/model/input'
7
7
  require 'graphql_rails/model/configurable'
8
- require 'graphql_rails/model/configuration/count_items'
8
+ require 'graphql_rails/model/build_connection_type'
9
9
 
10
10
  module GraphqlRails
11
11
  module Model
@@ -32,7 +32,9 @@ module GraphqlRails
32
32
 
33
33
  attributes[key].tap do |attribute|
34
34
  attribute_options.each do |method_name, args|
35
- attribute.public_send(method_name, args)
35
+ send_args = [method_name]
36
+ send_args << args if attribute.method(method_name).parameters.present?
37
+ attribute.public_send(*send_args)
36
38
  end
37
39
 
38
40
  yield(attribute) if block_given?
@@ -54,17 +56,16 @@ module GraphqlRails
54
56
  end
55
57
 
56
58
  def graphql_type
57
- @graphql_type ||= BuildGraphqlType.call(
58
- name: name, description: description, attributes: attributes
59
+ @graphql_type ||= FindOrBuildGraphqlType.call(
60
+ name: name,
61
+ description: description,
62
+ attributes: attributes,
63
+ type_name: type_name
59
64
  )
60
65
  end
61
66
 
62
67
  def connection_type
63
- @connection_type ||= begin
64
- graphql_type.define_connection do
65
- field :total, types.Int, resolve: CountItems
66
- end
67
- end
68
+ @connection_type ||= BuildConnectionType.call(graphql_type)
68
69
  end
69
70
 
70
71
  private
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ module Model
5
+ # stores information about model specific config, like attributes and types
6
+ class FindOrBuildGraphqlType
7
+ require 'graphql_rails/concerns/service'
8
+ require 'graphql_rails/model/find_or_build_graphql_type_class'
9
+ require 'graphql_rails/model/add_fields_to_graphql_type'
10
+
11
+ include ::GraphqlRails::Service
12
+
13
+ def initialize(name:, description:, attributes:, type_name:)
14
+ @name = name
15
+ @description = description
16
+ @attributes = attributes
17
+ @type_name = type_name
18
+ end
19
+
20
+ def call
21
+ klass.tap { add_fields_to_graphql_type if new_class? }
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :name, :description, :attributes, :type_name
27
+
28
+ delegate :klass, :new_class?, to: :type_class_finder
29
+
30
+ def type_class_finder
31
+ @type_class_finder ||= FindOrBuildGraphqlTypeClass.new(
32
+ name: name,
33
+ type_name: type_name,
34
+ description: description
35
+ )
36
+ end
37
+
38
+ def add_fields_to_graphql_type
39
+ AddFieldsToGraphqlType.call(klass: klass, attributes: attributes.values.select(&:scalar_type?))
40
+
41
+ attributes.values.reject(&:scalar_type?).tap do |dynamic_attributes|
42
+ find_or_build_dynamic_graphql_types(dynamic_attributes) do |name, description, attributes, type_name|
43
+ self.class.call(
44
+ name: name, description: description,
45
+ attributes: attributes, type_name: type_name
46
+ )
47
+ end
48
+ AddFieldsToGraphqlType.call(klass: klass, attributes: dynamic_attributes)
49
+ end
50
+ end
51
+
52
+ def find_or_build_dynamic_graphql_types(dynamic_attributes)
53
+ dynamic_attributes.each do |attribute|
54
+ yield(
55
+ attribute.graphql_model.graphql.name,
56
+ attribute.graphql_model.graphql.description,
57
+ attribute.graphql_model.graphql.attributes,
58
+ attribute.graphql_model.graphql.type_name
59
+ )
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end