graphql_rails 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile.lock +2 -2
  4. data/README.md +76 -8
  5. data/graphql_rails.gemspec +1 -1
  6. data/lib/graphql_rails/attribute.rb +7 -42
  7. data/lib/graphql_rails/attribute/attribute_type_parser.rb +99 -0
  8. data/lib/graphql_rails/controller.rb +17 -5
  9. data/lib/graphql_rails/controller/action.rb +93 -0
  10. data/lib/graphql_rails/controller/action_configuration.rb +1 -2
  11. data/lib/graphql_rails/controller/controller_function.rb +7 -6
  12. data/lib/graphql_rails/controller/request.rb +3 -3
  13. data/lib/graphql_rails/model.rb +1 -1
  14. data/lib/graphql_rails/model/configuration.rb +14 -50
  15. data/lib/graphql_rails/model/graphql_type_builder.rb +37 -0
  16. data/lib/graphql_rails/router.rb +36 -19
  17. data/lib/graphql_rails/router/{mutation_action.rb → mutation_route.rb} +2 -2
  18. data/lib/graphql_rails/router/{query_action.rb → query_route.rb} +2 -2
  19. data/lib/graphql_rails/router/resource_routes_builder.rb +82 -0
  20. data/lib/graphql_rails/router/route.rb +40 -0
  21. data/lib/graphql_rails/router/schema_builder.rb +9 -5
  22. data/lib/graphql_rails/rspec_controller_helpers.rb +94 -0
  23. data/lib/graphql_rails/version.rb +1 -1
  24. metadata +14 -29
  25. data/lib/graphiti.rb +0 -10
  26. data/lib/graphiti/attribute.rb +0 -86
  27. data/lib/graphiti/controller.rb +0 -54
  28. data/lib/graphiti/controller/action_configuration.rb +0 -46
  29. data/lib/graphiti/controller/configuration.rb +0 -32
  30. data/lib/graphiti/controller/controller_function.rb +0 -41
  31. data/lib/graphiti/controller/request.rb +0 -40
  32. data/lib/graphiti/errors/execution_error.rb +0 -18
  33. data/lib/graphiti/errors/validation_error.rb +0 -26
  34. data/lib/graphiti/model.rb +0 -33
  35. data/lib/graphiti/model/configuration.rb +0 -81
  36. data/lib/graphiti/router.rb +0 -65
  37. data/lib/graphiti/router/action.rb +0 -21
  38. data/lib/graphiti/router/mutation_action.rb +0 -18
  39. data/lib/graphiti/router/query_action.rb +0 -18
  40. data/lib/graphiti/router/resource_actions_builder.rb +0 -82
  41. data/lib/graphiti/router/schema_builder.rb +0 -37
  42. data/lib/graphiti/version.rb +0 -5
  43. data/lib/graphql_rails/controller/action_path_parser.rb +0 -75
  44. data/lib/graphql_rails/router/action.rb +0 -21
  45. data/lib/graphql_rails/router/resource_actions_builder.rb +0 -82
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 403f0e906eb7580de0e9d7b94c61a260c4145754e157b82906a669e04d4e6297
4
- data.tar.gz: 4201cccb9404a1e005ca8b978cd37784ffe400742ec5ae3d338a274a2cb8b84d
3
+ metadata.gz: 4ddb3b72250678abf86432165a3023bc21f8619635277f35b1322068d4ebab7e
4
+ data.tar.gz: f13859a7e2ae0bcb65474d33b728354cad3f645d1d2fcab5e34ba1b6f6c7c634
5
5
  SHA512:
6
- metadata.gz: 1d6f9038ddd9d90597d5b58a3b506b2a3124a547639d9651bdcd4be8f7dbd4feadb487ef7f8ff74062488afdac222eebe2f9a830b6a560921f42400c8fe333ae
7
- data.tar.gz: 7135c0208d889a2429b8496337dec43c4fbf6001d6306ea3937cc938eb5b149aa0be74102f49e6a2b613a1ee7751be6769eb9d63df6994aa3b82c523bec4d372
6
+ metadata.gz: 0be4db6d33d148a0ebf3815f70332c05254a8f4418c71f76381e1f6424d3f4b844a77926ed5f55d53d084b07e0fb7a35d95d7641eb1500b89b9380567b3c188d
7
+ data.tar.gz: ab4087c4e0af4f4bdbae330ca1c613bcd42ea803f9e70b73a20eae6ea7a400de85c2702b95f339253383fb3df636100dc4898333fbe6560695a2f24bd28a3292
data/.gitignore CHANGED
@@ -11,3 +11,4 @@
11
11
 
12
12
  # rspec failure tracking
13
13
  .rspec_status
14
+ *.gem
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graphql_rails (0.1.0)
5
- activesupport (~> 5)
4
+ graphql_rails (0.2.0)
5
+ activesupport (>= 4)
6
6
  graphql (~> 1)
7
7
 
8
8
  GEM
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # GraphqlRails
2
2
 
3
- [![Build Status](https://travis-ci.org/povilasjurcys/graphiti.svg?branch=master)](https://travis-ci.org/povilasjurcys/graphiti)
4
- [![codecov](https://codecov.io/gh/povilasjurcys/graphiti/branch/master/graph/badge.svg)](https://codecov.io/gh/povilasjurcys/graphiti)
3
+ [![Build Status](https://travis-ci.org/povilasjurcys/graphql_rails.svg?branch=master)](https://travis-ci.org/povilasjurcys/graphql_rails)
4
+ [![codecov](https://codecov.io/gh/povilasjurcys/graphql_rails/branch/master/graph/badge.svg)](https://codecov.io/gh/povilasjurcys/graphql_rails)
5
5
 
6
6
  Rails style structure for GrapQL API.
7
7
 
@@ -10,7 +10,7 @@ Rails style structure for GrapQL API.
10
10
  Add this line to your application's Gemfile:
11
11
 
12
12
  ```ruby
13
- gem 'graphiti'
13
+ gem 'graphql_rails'
14
14
  ```
15
15
 
16
16
  And then execute:
@@ -19,7 +19,7 @@ And then execute:
19
19
 
20
20
  Or install it yourself as:
21
21
 
22
- $ gem install graphiti
22
+ $ gem install graphql_rails
23
23
 
24
24
  ## Usage
25
25
 
@@ -43,7 +43,7 @@ end
43
43
  class User # works with any class including ActiveRecord
44
44
  include GraphqlRails::Model
45
45
 
46
- graphiti do |c|
46
+ graphql do |c|
47
47
  # most common attributes, like :id, :name, :title has default type, so you don't have to specify it (but you can!)
48
48
  c.attribute :id
49
49
 
@@ -65,7 +65,7 @@ class UsersController < GraphqlRails::Controller
65
65
  user = User.find(params[:id])
66
66
  user.update!(password: params[:password])
67
67
 
68
- # returned value needs to have all methods defined in model `graphiti do` part
68
+ # returned value needs to have all methods defined in model `graphql do` part
69
69
  user # or SomeDecorator.new(user)
70
70
  end
71
71
 
@@ -103,6 +103,74 @@ MyGraphqlSchema = GraphqlRails::Router.draw do
103
103
  end
104
104
  ```
105
105
 
106
+ ## Testing your GrapqhlRails::Controller in RSpec
107
+
108
+ ### Setup
109
+
110
+ Add those lines in your `spec/spec_helper.rb` file
111
+
112
+ ```ruby
113
+ # spec/spec_helper.rb
114
+ require 'graphql_rails/rspec_controller_helpers'
115
+
116
+ RSpec.configure do |config|
117
+ config.include(GraphqlRails::RSpecControllerHelpers, type: :graphql_controller)
118
+ # ... your other configuration ...
119
+ end
120
+ ```
121
+
122
+ ### Helper methods
123
+
124
+ There are 3 helper methods:
125
+
126
+ * `mutation(:your_controller_action_name, params: {}, context: {})`. `params` and `context` are optional
127
+ * `query(:your_controller_action_name, params: {}, context: {})`. `params` and `context` are optional
128
+ * `response`. Response is set only after you call `mutation` or `query`
129
+
130
+ ### Test examples
131
+
132
+ ```ruby
133
+ class MyGraphqlController
134
+ def index
135
+ "Called from index: #{params[:message]}"
136
+ end
137
+
138
+ action(:create_user).permit(:full_name, :email)
139
+ def create_user
140
+ User.create!(params)
141
+ end
142
+ end
143
+
144
+ RSpec.describe MyGraphqlController, type: :graphql_controller do
145
+ describe '#index' do
146
+ it 'is successful' do
147
+ query(:index)
148
+ expect(response).to be_successful
149
+ end
150
+
151
+ it 'returns correct message' do
152
+ query(:index, params: { message: 'Hello world!' })
153
+ expect(response.result).to eq "Called from index: Hello world!"
154
+ end
155
+ end
156
+
157
+ describe '#create_user' do
158
+ context 'when bad email is given' do
159
+ it 'fails' do
160
+ mutation(:create_user, params { email: 'bad' })
161
+ expect(response).to be_failure
162
+ end
163
+
164
+ it 'contains errors' do
165
+ mutation(:create_user, params { email: 'bad' })
166
+ expect(response.errors).not_to be_empty
167
+ end
168
+ end
169
+ end
170
+ end
171
+ ```
172
+
173
+
106
174
  ## Development
107
175
 
108
176
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -111,7 +179,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
111
179
 
112
180
  ## Contributing
113
181
 
114
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/graphiti. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
182
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/graphql_rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
115
183
 
116
184
  ## License
117
185
 
@@ -119,4 +187,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
119
187
 
120
188
  ## Code of Conduct
121
189
 
122
- Everyone interacting in the GraphqlRails project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/graphiti/blob/master/CODE_OF_CONDUCT.md).
190
+ Everyone interacting in the GraphqlRails project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/graphql_rails/blob/master/CODE_OF_CONDUCT.md).
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.require_paths = ['lib']
22
22
 
23
23
  spec.add_dependency 'graphql', '~> 1'
24
- spec.add_dependency 'activesupport', '~> 5'
24
+ spec.add_dependency 'activesupport', '>= 4'
25
25
 
26
26
  spec.add_development_dependency 'bundler', '~> 1.16'
27
27
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -1,25 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'graphql'
4
+ require 'graphql_rails/attribute/attribute_type_parser'
4
5
 
5
6
  module GraphqlRails
6
7
  # contains info about single graphql attribute
7
8
  class Attribute
8
- attr_reader :name, :type
9
+ attr_reader :name, :graphql_field_type, :property, :type_name
9
10
 
10
- def initialize(name, type = nil, required: false, hidden: false)
11
+ def initialize(name, type = nil, hidden: false, property: name)
11
12
  @name = name.to_s
12
- @type = parse_type(type || type_by_attribute_name)
13
- @required = required
13
+ @type_name = type.to_s
14
+ @graphql_field_type = parse_type(type || type_by_attribute_name)
14
15
  @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
16
+ @property = property.to_s
23
17
  end
24
18
 
25
19
  def hidden?
@@ -51,36 +45,7 @@ module GraphqlRails
51
45
  end
52
46
 
53
47
  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
48
+ AttributeTypeParser.new(type).call
84
49
  end
85
50
  end
86
51
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module GraphqlRails
6
+ class Attribute
7
+ # converts string value in to GraphQL type
8
+ class AttributeTypeParser
9
+ class UnknownTypeError < ArgumentError; end
10
+
11
+ TYPE_MAPPING = {
12
+ 'id' => GraphQL::ID_TYPE,
13
+
14
+ 'int' => GraphQL::INT_TYPE,
15
+ 'integer' => GraphQL::INT_TYPE,
16
+
17
+ 'string' => GraphQL::STRING_TYPE,
18
+ 'str' => GraphQL::STRING_TYPE,
19
+ 'text' => GraphQL::STRING_TYPE,
20
+ 'time' => GraphQL::STRING_TYPE,
21
+ 'date' => GraphQL::STRING_TYPE,
22
+
23
+ 'bool' => GraphQL::BOOLEAN_TYPE,
24
+ 'boolean' => GraphQL::BOOLEAN_TYPE,
25
+
26
+ 'float' => GraphQL::FLOAT_TYPE,
27
+ 'double' => GraphQL::FLOAT_TYPE,
28
+ 'decimal' => GraphQL::FLOAT_TYPE
29
+ }.freeze
30
+
31
+ def initialize(unparsed_type)
32
+ @unparsed_type = unparsed_type
33
+ end
34
+
35
+ def call
36
+ return unparsed_type if raw_graphql_type?
37
+
38
+ if list?
39
+ parsed_list_type
40
+ else
41
+ parsed_inner_type
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :unparsed_type
48
+
49
+ def parsed_list_type
50
+ list_type = parsed_inner_type.to_list_type
51
+
52
+ if required_list_type?
53
+ list_type.to_non_null_type
54
+ else
55
+ list_type
56
+ end
57
+ end
58
+
59
+ def parsed_inner_type
60
+ if required_inner_type?
61
+ type_by_name.to_non_null_type
62
+ else
63
+ type_by_name
64
+ end
65
+ end
66
+
67
+ def required_inner_type?
68
+ !!unparsed_type[/\w!/] # rubocop:disable Style/DoubleNegation
69
+ end
70
+
71
+ def list?
72
+ unparsed_type.to_s.include?(']')
73
+ end
74
+
75
+ def required_list_type?
76
+ unparsed_type.to_s.include?(']!')
77
+ end
78
+
79
+ def raw_graphql_type?
80
+ unparsed_type.is_a?(GraphQL::BaseType) ||
81
+ unparsed_type.is_a?(GraphQL::ObjectType) ||
82
+ (defined?(GraphQL::Schema::Member) && unparsed_type.is_a?(Class) && unparsed_type < GraphQL::Schema::Member)
83
+ end
84
+
85
+ def inner_type_name
86
+ unparsed_type.to_s.downcase.tr('[]!', '')
87
+ end
88
+
89
+ def type_by_name
90
+ TYPE_MAPPING.fetch(inner_type_name) do
91
+ raise(
92
+ UnknownTypeError,
93
+ "Type #{unparsed_type.inspect} is not supported. Supported types are: #{TYPE_MAPPING.keys}"
94
+ )
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/hash_with_indifferent_access'
4
- require_relative 'controller/configuration'
5
- require_relative 'controller/request'
4
+ require 'graphql_rails/controller/configuration'
5
+ require 'graphql_rails/controller/request'
6
6
 
7
7
  module GraphqlRails
8
8
  # base class for all graphql_rails controllers
@@ -26,9 +26,8 @@ module GraphqlRails
26
26
  end
27
27
 
28
28
  def call(method_name)
29
- self.class.controller_configuration.before_actions.each { |action_name| send(action_name) }
30
-
31
29
  begin
30
+ self.class.controller_configuration.before_actions.each { |action_name| send(action_name) }
32
31
  response = public_send(method_name)
33
32
  render response if graphql_request.no_object_to_return?
34
33
  rescue StandardError => error
@@ -42,7 +41,10 @@ module GraphqlRails
42
41
 
43
42
  attr_reader :graphql_request
44
43
 
45
- def render(object = nil, error: nil, errors: Array(error))
44
+ def render(object_or_errors)
45
+ errors = grapqhl_errors_from_render_params(object_or_errors)
46
+ object = errors.empty? ? object_or_errors : nil
47
+
46
48
  graphql_request.errors = errors
47
49
  graphql_request.object_to_return = object
48
50
  end
@@ -50,5 +52,15 @@ module GraphqlRails
50
52
  def params
51
53
  @params = HashWithIndifferentAccess.new(graphql_request.params)
52
54
  end
55
+
56
+ private
57
+
58
+ def grapqhl_errors_from_render_params(rendering_params)
59
+ return [] unless rendering_params.is_a?(Hash)
60
+ return [] if rendering_params.keys.count != 1
61
+
62
+ errors = rendering_params[:error] || rendering_params[:errors]
63
+ Array(errors)
64
+ end
53
65
  end
54
66
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string/inflections'
4
+ require_relative 'request'
5
+
6
+ module GraphqlRails
7
+ class Controller
8
+ # analyzes route and extracts controller action related data
9
+ class Action
10
+ def initialize(route)
11
+ @route = route
12
+ end
13
+
14
+ def return_type
15
+ action_config.return_type || default_type
16
+ end
17
+
18
+ def arguments
19
+ action_config.attributes.values
20
+ end
21
+
22
+ def controller
23
+ @controller ||= "#{namespaced_controller_name}_controller".classify.constantize
24
+ end
25
+
26
+ def name
27
+ @name ||= action_path.split('#').last
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :route
33
+
34
+ def default_inner_return_type
35
+ if action_config.can_return_nil?
36
+ model_graphql_type
37
+ else
38
+ model_graphql_type.to_non_null_type
39
+ end
40
+ end
41
+
42
+ def default_type
43
+ type = default_inner_return_type
44
+ type = type.to_list_type.to_non_null_type if route.collection?
45
+ type
46
+ end
47
+
48
+ def action_path
49
+ route.path
50
+ end
51
+
52
+ def module_name
53
+ route.module_name
54
+ end
55
+
56
+ def action_config
57
+ controller.controller_configuration.action(name)
58
+ end
59
+
60
+ def namespaced_controller_name
61
+ [module_name, controller_name].reject(&:empty?).join('/')
62
+ end
63
+
64
+ def controller_name
65
+ @controller_name ||= action_path.split('#').first
66
+ end
67
+
68
+ def action_model
69
+ namespace = namespaced_controller_name.singularize.classify.split('::')
70
+ model_name = namespace.pop
71
+ model = nil
72
+
73
+ while model.nil? && !namespace.empty?
74
+ model = namespaced_model(namespace, model_name)
75
+ namespace.pop
76
+ end
77
+
78
+ model || model_name.constantize
79
+ end
80
+
81
+ def namespaced_model(namespace, model_name)
82
+ [namespace, model_name].join('::').constantize
83
+ rescue NameError => err
84
+ raise unless err.message.match?(/uninitialized constant/)
85
+ nil
86
+ end
87
+
88
+ def model_graphql_type
89
+ action_model.graphql.graphql_type
90
+ end
91
+ end
92
+ end
93
+ end