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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +2 -2
- data/README.md +76 -8
- data/graphql_rails.gemspec +1 -1
- data/lib/graphql_rails/attribute.rb +7 -42
- data/lib/graphql_rails/attribute/attribute_type_parser.rb +99 -0
- data/lib/graphql_rails/controller.rb +17 -5
- data/lib/graphql_rails/controller/action.rb +93 -0
- data/lib/graphql_rails/controller/action_configuration.rb +1 -2
- data/lib/graphql_rails/controller/controller_function.rb +7 -6
- data/lib/graphql_rails/controller/request.rb +3 -3
- data/lib/graphql_rails/model.rb +1 -1
- data/lib/graphql_rails/model/configuration.rb +14 -50
- data/lib/graphql_rails/model/graphql_type_builder.rb +37 -0
- data/lib/graphql_rails/router.rb +36 -19
- data/lib/graphql_rails/router/{mutation_action.rb → mutation_route.rb} +2 -2
- data/lib/graphql_rails/router/{query_action.rb → query_route.rb} +2 -2
- data/lib/graphql_rails/router/resource_routes_builder.rb +82 -0
- data/lib/graphql_rails/router/route.rb +40 -0
- data/lib/graphql_rails/router/schema_builder.rb +9 -5
- data/lib/graphql_rails/rspec_controller_helpers.rb +94 -0
- data/lib/graphql_rails/version.rb +1 -1
- metadata +14 -29
- data/lib/graphiti.rb +0 -10
- data/lib/graphiti/attribute.rb +0 -86
- data/lib/graphiti/controller.rb +0 -54
- data/lib/graphiti/controller/action_configuration.rb +0 -46
- data/lib/graphiti/controller/configuration.rb +0 -32
- data/lib/graphiti/controller/controller_function.rb +0 -41
- data/lib/graphiti/controller/request.rb +0 -40
- data/lib/graphiti/errors/execution_error.rb +0 -18
- data/lib/graphiti/errors/validation_error.rb +0 -26
- data/lib/graphiti/model.rb +0 -33
- data/lib/graphiti/model/configuration.rb +0 -81
- data/lib/graphiti/router.rb +0 -65
- data/lib/graphiti/router/action.rb +0 -21
- data/lib/graphiti/router/mutation_action.rb +0 -18
- data/lib/graphiti/router/query_action.rb +0 -18
- data/lib/graphiti/router/resource_actions_builder.rb +0 -82
- data/lib/graphiti/router/schema_builder.rb +0 -37
- data/lib/graphiti/version.rb +0 -5
- data/lib/graphql_rails/controller/action_path_parser.rb +0 -75
- data/lib/graphql_rails/router/action.rb +0 -21
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ddb3b72250678abf86432165a3023bc21f8619635277f35b1322068d4ebab7e
|
4
|
+
data.tar.gz: f13859a7e2ae0bcb65474d33b728354cad3f645d1d2fcab5e34ba1b6f6c7c634
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0be4db6d33d148a0ebf3815f70332c05254a8f4418c71f76381e1f6424d3f4b844a77926ed5f55d53d084b07e0fb7a35d95d7641eb1500b89b9380567b3c188d
|
7
|
+
data.tar.gz: ab4087c4e0af4f4bdbae330ca1c613bcd42ea803f9e70b73a20eae6ea7a400de85c2702b95f339253383fb3df636100dc4898333fbe6560695a2f24bd28a3292
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# GraphqlRails
|
2
2
|
|
3
|
-
[](https://travis-ci.org/povilasjurcys/graphql_rails)
|
4
|
+
[](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 '
|
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
|
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
|
-
|
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 `
|
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]/
|
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]/
|
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).
|
data/graphql_rails.gemspec
CHANGED
@@ -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', '
|
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, :
|
9
|
+
attr_reader :name, :graphql_field_type, :property, :type_name
|
9
10
|
|
10
|
-
def initialize(name, type = nil,
|
11
|
+
def initialize(name, type = nil, hidden: false, property: name)
|
11
12
|
@name = name.to_s
|
12
|
-
@
|
13
|
-
@
|
13
|
+
@type_name = type.to_s
|
14
|
+
@graphql_field_type = parse_type(type || type_by_attribute_name)
|
14
15
|
@hidden = hidden
|
15
|
-
|
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
|
-
|
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
|
-
|
5
|
-
|
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(
|
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
|