graphql_rails 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) 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 +3 -0
  7. data/Gemfile +3 -2
  8. data/Gemfile.lock +141 -117
  9. data/docs/README.md +2 -2
  10. data/docs/components/controller.md +21 -5
  11. data/docs/components/model.md +43 -3
  12. data/docs/index.html +1 -1
  13. data/graphql_rails.gemspec +5 -5
  14. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +10 -7
  15. data/lib/graphql_rails/attributes/attributable.rb +2 -4
  16. data/lib/graphql_rails/attributes/attribute.rb +26 -6
  17. data/lib/graphql_rails/attributes/attribute_name_parser.rb +1 -1
  18. data/lib/graphql_rails/attributes/input_attribute.rb +5 -1
  19. data/lib/graphql_rails/concerns/service.rb +6 -2
  20. data/lib/graphql_rails/controller.rb +6 -6
  21. data/lib/graphql_rails/controller/action.rb +5 -1
  22. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +2 -2
  23. data/lib/graphql_rails/controller/request.rb +1 -1
  24. data/lib/graphql_rails/controller/request/format_errors.rb +1 -1
  25. data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
  26. data/lib/graphql_rails/model/build_graphql_input_type.rb +1 -1
  27. data/lib/graphql_rails/model/call_graphql_model_method.rb +14 -1
  28. data/lib/graphql_rails/model/configurable.rb +6 -2
  29. data/lib/graphql_rails/model/configuration.rb +9 -4
  30. data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
  31. data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
  32. data/lib/graphql_rails/router.rb +2 -2
  33. data/lib/graphql_rails/router/resource_routes_builder.rb +8 -8
  34. data/lib/graphql_rails/router/route.rb +3 -7
  35. data/lib/graphql_rails/router/schema_builder.rb +1 -1
  36. data/lib/graphql_rails/rspec_controller_helpers.rb +2 -2
  37. data/lib/graphql_rails/version.rb +1 -1
  38. metadata +21 -14
  39. data/lib/graphql_rails/model/build_graphql_type.rb +0 -53
@@ -17,13 +17,13 @@ end
17
17
 
18
18
  ## graphql
19
19
 
20
- This method must be called inside your model body. `grapqhl` is used for making your model convertible to graphql type.
20
+ This method must be called inside your model body. `graphql` is used for making your model convertible to graphql type.
21
21
 
22
22
  ## attribute
23
23
 
24
24
  Most commonly you will use `attribute` to make your model methods and attributes visible via graphql endpoint.
25
25
 
26
- ## attribute.type
26
+ ### attribute.type
27
27
 
28
28
  Some types can be determined by attribute name, so you can skip this attribute:
29
29
 
@@ -54,9 +54,33 @@ class User
54
54
  end
55
55
  ```
56
56
 
57
+ #### attribute.type: using graphql-ruby objects
58
+
59
+ You can also use raw graphql-ruby objects as attribute types. Here is an example:
60
+
61
+ ```ruby
62
+ # raw graphql-ruby type:
63
+ class AddressType < GraphQL::Schema::Object
64
+ graphql_name 'Address'
65
+
66
+ field :city, String, null: false
67
+ field :street_name, String, null: false
68
+ field :street_number, Integer
69
+ end
70
+
71
+ # GraphqlRails model:
72
+ class User
73
+ include GraphqlRails::Model
74
+
75
+ graphql.attribute :address, type: AddressType, required: true
76
+ end
77
+ ```
78
+
79
+ Check [graphql-ruby documentation](https://graphql-ruby.org) for more details about graphql-ruby types.
80
+
57
81
  ### attribute.property
58
82
 
59
- By default graphql attribute names are expected to be same as model methods/attributes, but if you want to use different name on grapqhl side, you can use `propery` option:
83
+ By default graphql attribute names are expected to be same as model methods/attributes, but if you want to use different name on grapqhl side, you can use `property` option:
60
84
 
61
85
  ```ruby
62
86
  class User
@@ -86,6 +110,22 @@ class User
86
110
  end
87
111
  ```
88
112
 
113
+ ### attribute.options
114
+
115
+ Allows passing options to attribute definition. Available options:
116
+
117
+ * `attribute_name_format` - if `:original` value is passed, it will not camelCase attribute name.
118
+
119
+ ```ruby
120
+ class User
121
+ include GraphqlRails::Model
122
+
123
+ graphql do |c|
124
+ c.attribute :first_name # will be accessible as firstName from client side
125
+ c.attribute :first_name, options: { attribute_name_format: :original } # will be accessible as first_name from client side
126
+ end
127
+ end
128
+
89
129
  ### attribute.permit
90
130
 
91
131
  To define attributes which are accepted by each model method, you need to call `permit` method, like this:
@@ -2,7 +2,7 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <title>Document</title>
5
+ <title>GraphqlRails</title>
6
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
7
7
  <meta name="description" content="Description">
8
8
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ['Povilas Jurčys']
10
10
  spec.email = ['po.jurcys@gmail.com']
11
11
 
12
- spec.summary = %q{GraphQL server and client for rails}
12
+ spec.summary = %q{Rails style structure for GraphQL API.}
13
13
  spec.homepage = 'https://github.com/samesystem/graphql_rails'
14
14
  spec.license = 'MIT'
15
15
 
@@ -20,13 +20,13 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ['lib']
22
22
 
23
- spec.add_dependency 'graphql', '>= 1.9.12'
23
+ spec.add_dependency 'graphql', '~> 1.11', '>= 1.11.6'
24
24
  spec.add_dependency 'activesupport', '>= 4'
25
25
 
26
- spec.add_development_dependency 'bundler', '~> 1.16'
27
- spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'bundler', '~> 2'
27
+ spec.add_development_dependency 'rake', '~> 13.0'
28
28
  spec.add_development_dependency 'rspec', '~> 3.0'
29
29
  spec.add_development_dependency 'activerecord'
30
30
  spec.add_development_dependency 'pry-byebug'
31
- spec.add_development_dependency 'rails', '~> 5'
31
+ spec.add_development_dependency 'rails', '~> 6'
32
32
  end
@@ -3,16 +3,19 @@
3
3
  require 'rails_helper'
4
4
 
5
5
  RSpec.describe GraphqlRouter do
6
- describe '#execute' do
6
+ subject(:schema) { described_class.graphql_schema }
7
+
8
+ describe '#to_definition' do
9
+ subject(:to_definition) { schema.to_definition.strip }
10
+
11
+ let(:schema_dump_path) { Rails.root.join('spec', 'fixtures', 'graphql_schema.graphql') }
12
+ let(:previous_definition) { File.read(schema_dump_path).strip }
13
+
7
14
  it 'returns correct structure' do
8
15
  # if you need to update saved_schema, run rake task:
9
- # $ RAILS_ENV=test rake graphql_rails:schema:dump
10
-
11
- schema_path = Rails.root.join('spec', 'fixtures', 'graphql_schema.graphql')
12
- saved_schema = File.read(schema_path).strip
13
- real_schema = described_class.to_definition.strip
16
+ # $ RAILS_ENV=test bin/rake graphql_rails:schema:dump
14
17
 
15
- expect(real_schema).to eq(saved_schema)
18
+ expect(to_definition).to eq(previous_definition)
16
19
  end
17
20
  end
18
21
  end
@@ -46,10 +46,8 @@ module GraphqlRails
46
46
  !required?
47
47
  end
48
48
 
49
- protected
50
-
51
- def options
52
- {}
49
+ def scalar_type?
50
+ type_parser.raw_graphql_type? || type_parser.unwrapped_scalar_type.class == GraphQL::ScalarType
53
51
  end
54
52
 
55
53
  private
@@ -13,14 +13,17 @@ module GraphqlRails
13
13
 
14
14
  attr_reader :attributes
15
15
 
16
- def initialize(name, type = nil, description: nil, property: name, required: nil)
16
+ # rubocop:disable Metrics/ParameterLists
17
+ def initialize(name, type = nil, description: nil, property: name, required: nil, options: {})
17
18
  @initial_type = type
18
19
  @initial_name = name
20
+ @options = options
19
21
  @description = description
20
22
  @property = property.to_s
21
23
  @required = required
22
24
  @attributes ||= {}
23
25
  end
26
+ # rubocop:enable Metrics/ParameterLists
24
27
 
25
28
  def type(new_type = nil)
26
29
  return @initial_type if new_type.nil?
@@ -43,18 +46,29 @@ module GraphqlRails
43
46
  self
44
47
  end
45
48
 
49
+ def options(new_options = {})
50
+ return @options if new_options.blank?
51
+
52
+ @options = new_options
53
+ self
54
+ end
55
+
46
56
  def field_args
47
57
  [
48
58
  field_name,
49
59
  type_parser.type_arg,
50
- *description,
51
- {
52
- method: property.to_sym,
53
- null: optional?
54
- }
60
+ *description
55
61
  ]
56
62
  end
57
63
 
64
+ def field_options
65
+ {
66
+ method: property.to_sym,
67
+ null: optional?,
68
+ camelize: camelize?
69
+ }
70
+ end
71
+
58
72
  def argument_args
59
73
  [
60
74
  field_name,
@@ -69,6 +83,12 @@ module GraphqlRails
69
83
  protected
70
84
 
71
85
  attr_reader :initial_type, :initial_name
86
+
87
+ private
88
+
89
+ def camelize?
90
+ options[:input_format] != :original && options[:attribute_name_format] != :original
91
+ end
72
92
  end
73
93
  end
74
94
  end
@@ -44,7 +44,7 @@ module GraphqlRails
44
44
  attr_reader :options
45
45
 
46
46
  def original_format?
47
- options[:input_format] == :original
47
+ options[:input_format] == :original || options[:attribute_name_format] == :original
48
48
  end
49
49
 
50
50
  def preprocesed_name
@@ -24,7 +24,11 @@ module GraphqlRails
24
24
  def input_argument_args
25
25
  type = raw_input_type || input_type_parser.input_type_arg
26
26
 
27
- [field_name, type, { required: required?, description: description, camelize: false }]
27
+ [field_name, type]
28
+ end
29
+
30
+ def input_argument_options
31
+ { required: required?, description: description, camelize: false }
28
32
  end
29
33
 
30
34
  def paginated?
@@ -7,8 +7,12 @@ module GraphqlRails
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  class_methods do
10
- def call(*args, &block)
11
- new(*args).call(&block)
10
+ def call(*args, **kwargs, &block)
11
+ if kwargs.present?
12
+ new(*args, **kwargs).call(&block)
13
+ else
14
+ new(*args).call(&block)
15
+ end
12
16
  end
13
17
  end
14
18
  end
@@ -18,16 +18,16 @@ module GraphqlRails
18
18
  subclass.instance_variable_set(:@controller_configuration, new_config)
19
19
  end
20
20
 
21
- def before_action(*args, &block)
22
- controller_configuration.add_action_hook(:before, *args, &block)
21
+ def before_action(*args, **kwargs, &block)
22
+ controller_configuration.add_action_hook(:before, *args, **kwargs, &block)
23
23
  end
24
24
 
25
- def around_action(*args, &block)
26
- controller_configuration.add_action_hook(:around, *args, &block)
25
+ def around_action(*args, **kwargs, &block)
26
+ controller_configuration.add_action_hook(:around, *args, **kwargs, &block)
27
27
  end
28
28
 
29
- def after_action(*args, &block)
30
- controller_configuration.add_action_hook(:after, *args, &block)
29
+ def after_action(*args, **kwargs, &block)
30
+ controller_configuration.add_action_hook(:after, *args, **kwargs, &block)
31
31
  end
32
32
 
33
33
  def action(action_name)
@@ -36,7 +36,11 @@ module GraphqlRails
36
36
  end
37
37
 
38
38
  def type_args
39
- [type_parser.type_arg, null: !type_parser.required?]
39
+ [type_parser.type_arg]
40
+ end
41
+
42
+ def type_options
43
+ { null: !type_parser.required? }
40
44
  end
41
45
 
42
46
  private
@@ -19,13 +19,13 @@ module GraphqlRails
19
19
  action = build_action
20
20
 
21
21
  Class.new(ControllerActionResolver) do
22
- type(*action.type_args)
22
+ type(*action.type_args, **action.type_options)
23
23
  description(action.description)
24
24
  controller(action.controller)
25
25
  controller_action_name(action.name)
26
26
 
27
27
  action.arguments.each do |attribute|
28
- argument(*attribute.input_argument_args)
28
+ argument(*attribute.input_argument_args, **attribute.input_argument_options)
29
29
  end
30
30
 
31
31
  def self.inspect
@@ -16,7 +16,7 @@ module GraphqlRails
16
16
  end
17
17
 
18
18
  def errors=(new_errors)
19
- @errors = FormatErrors.call(new_errors)
19
+ @errors = FormatErrors.call(not_formatted_errors: new_errors)
20
20
 
21
21
  @errors.each { |error| context.add_error(error) }
22
22
  end
@@ -12,7 +12,7 @@ module GraphqlRails
12
12
  class FormatErrors
13
13
  include Service
14
14
 
15
- def initialize(not_formatted_errors)
15
+ def initialize(not_formatted_errors:)
16
16
  @not_formatted_errors = not_formatted_errors
17
17
  end
18
18
 
@@ -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
@@ -24,7 +24,7 @@ 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
28
  end
29
29
 
30
30
  def self.inspect
@@ -31,10 +31,23 @@ module GraphqlRails
31
31
  if custom_keyword_arguments.empty?
32
32
  model.send(method_name)
33
33
  else
34
- model.send(method_name, **custom_keyword_arguments)
34
+ formatted_arguments = formatted_method_input(custom_keyword_arguments)
35
+ model.send(method_name, **formatted_arguments)
35
36
  end
36
37
  end
37
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
+
38
51
  def method_name
39
52
  attribute_config.property
40
53
  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,7 +1,7 @@
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'
@@ -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,8 +56,11 @@ 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