graphql_rails 0.1.0 → 0.2.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 (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