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.
- 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
|
-
[![Build Status](https://travis-ci.org/povilasjurcys/
|
4
|
-
[![codecov](https://codecov.io/gh/povilasjurcys/
|
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 '
|
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
|