graphql-rails-resolver 0.1.2
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 +7 -0
- data/README.md +154 -0
- data/lib/graphql/rails.rb +2 -0
- data/lib/graphql/rails/resolver.rb +118 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eac8d006df6f747647df533f2606819a2b6e304a
|
4
|
+
data.tar.gz: c2d7486872ecc83c143fd1654f369ab8fc58045e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: de1cd692a89ecf230ede5977aecf4e36a6ad5be227354643228d139c620f6e281547a8d259560a80e6016c3a3cbb6db8b3bf0bd691963d3ae71cb997eff14d91
|
7
|
+
data.tar.gz: a5de48a5fb24727abd8f92f39fb3946f9e913aa8ca2116d6ecff9bc107e3110f1568f4d07398d550f5a7fa5e211e9ebb7d4c829ccb2ba4068b2dc0a667753f4f
|
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# ActiveResolver (graphql-rails-resolver)
|
2
|
+
A utility for ease graphql-ruby integration into a Rails project. ActiveResolver offers a declarative approach to resolving Field arguments in a Rails environment.
|
3
|
+
|
4
|
+
# How it works:
|
5
|
+
`ActiveResolver.rb` serves as a base class for your GraphQL Ruby schema. When a resolver inherits from this base class, you can easily map arguments in a GraphQL Field to an attribute on an ActiveRecord model or a custom method.
|
6
|
+
|
7
|
+
## Why?
|
8
|
+
**tl;dr; Active Resolvers achieves three goals: maintainable query type, code re-use, and a declarative integration with Ruby on Rails.**
|
9
|
+
|
10
|
+
Take for example the following Rails model:
|
11
|
+
|
12
|
+
```
|
13
|
+
class Post < ApplicationRecord
|
14
|
+
belongs_to :author
|
15
|
+
has_many :comments
|
16
|
+
|
17
|
+
scope :is_public, -> { where(is_public: true) }
|
18
|
+
scope :is_private, -> { where(is_public: false) }
|
19
|
+
|
20
|
+
def tags
|
21
|
+
["hello", "world"]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
The standard implementation for resolving a `Post` is as follows:
|
28
|
+
|
29
|
+
```
|
30
|
+
field :post, PostType do
|
31
|
+
argument :is_public, types.Boolean, default_value: true
|
32
|
+
resolve -> (obj, args, ctx) {
|
33
|
+
post.is_public if args[:is_public]
|
34
|
+
post.is_private unless args[:is_public]
|
35
|
+
}
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
This implementation is cumbersome and when your application grows it will become unmanageable. In [GraphQL Ruby: Clean Up your Query Type](https://m.alphasights.com/graphql-ruby-clean-up-your-query-type-d7ab05a47084) we see a better pattern emerge for building resolvers that can be re-used.
|
40
|
+
|
41
|
+
Using the pattern from this article, our Field becomes much simpler:
|
42
|
+
|
43
|
+
**/app/graph/types/query_type.rb**
|
44
|
+
```
|
45
|
+
field :post, PostType do
|
46
|
+
argument :is_public, types.Boolean, default_value: true
|
47
|
+
resolve Resolvers::Post.new
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
**/app/graph/resolvers/post.rb**
|
52
|
+
```
|
53
|
+
module Resolvers
|
54
|
+
class Post
|
55
|
+
def call(_, arguments, _)
|
56
|
+
if arguments[:ids]
|
57
|
+
::Post.where(id: arguments[:ids])
|
58
|
+
elsif arguments.key? :is_public
|
59
|
+
::Post.is_public if arguments[:is_public]
|
60
|
+
::Post.is_private unless arguments[:is_public]
|
61
|
+
else
|
62
|
+
::Post.all
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
This solution addresses code re-use, however this series of conditionals do not allow you to resolve more than one argument, and it may become difficult to maintain this imperative approach.
|
69
|
+
|
70
|
+
|
71
|
+
## Hello Active Resolver:
|
72
|
+
**Out with imperative, in with declarative.**
|
73
|
+
|
74
|
+
Take the Resolver from the previous example. Using `ActiveResolver`, we inherit and use declarations for arguments and how they will be resolved. These declarations will be mapped to the attributes on the resolved model.
|
75
|
+
|
76
|
+
```
|
77
|
+
# Class name must match the Rails model name exactly.
|
78
|
+
|
79
|
+
class Post < GraphQL::Rails::ActiveResolver
|
80
|
+
# ID argument is resolved within ActiveResolver
|
81
|
+
|
82
|
+
# Resolve :is_public to a class method
|
83
|
+
resolve_method :is_public
|
84
|
+
|
85
|
+
# Resolve :title, :created_at, :updated_at to Post.where() arguments
|
86
|
+
resolve_where :title
|
87
|
+
resolve_where :created_at
|
88
|
+
resolve_where :updated_at
|
89
|
+
|
90
|
+
def is_public(value)
|
91
|
+
@result.is_public if value
|
92
|
+
@result.is_private unless value
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
In the example above, there are two declarations:
|
99
|
+
|
100
|
+
`resolve_where` is a declarative approach using `ActiveRecord.where` to resolve arguments.
|
101
|
+
|
102
|
+
`resolve_method` is an imperative approach that's useful for using Model scopes or custom resolution.
|
103
|
+
|
104
|
+
[Help make scopes declarative!](#making-scopes-declarative)
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
### Detecting the Model
|
110
|
+
The resolver will automatically resolve to a Rails model with the same name. This behavior can be overridden by defining a `Post#model` which returns the appropriate model.
|
111
|
+
```
|
112
|
+
def model
|
113
|
+
::AnotherModel
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
### Find Model by ID
|
118
|
+
Every Active Resolver includes the ability to resolve a model by ID. Using the following method, by default the resolver will find a model by **NodeIdentification.from_global_id(value)** or **Model.where(:id => value)**. This means a model can be resolved by Global ID or Integer ID.
|
119
|
+
```
|
120
|
+
def lookup_id(value)
|
121
|
+
...
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
|
126
|
+
### Override Default Resolution
|
127
|
+
The default behavior is to use `Model.all` to seed the resolution. This seed can be changed by providing a block or lambda to the class instance:
|
128
|
+
```
|
129
|
+
Resolvers::Post.new(Proc.new {
|
130
|
+
::Post.where(:created_at => ...)
|
131
|
+
})
|
132
|
+
```
|
133
|
+
|
134
|
+
|
135
|
+
# Needs Help
|
136
|
+
I wanted to release this utility for the hopes of sparking interest in Rails integration with `graphql-ruby`.
|
137
|
+
|
138
|
+
If you wish to contribute to this project, any pull request is warmly welcomed. If time permits, I will continue to update this project to achieve the following:
|
139
|
+
|
140
|
+
### [Making Scopes Declarative](#making-scopes-declarative):
|
141
|
+
For first release, scopes can only be resolved using `resolve_method`. The goal for further development is to stop using `resolve_method` and adapt other methods to facilitate resolution.
|
142
|
+
|
143
|
+
The current syntax planned for scope resolution is as follows, where the argument is passed to the scope:
|
144
|
+
|
145
|
+
```
|
146
|
+
resolve_scope :is_public, -> (args) { args[:is_public] == true }
|
147
|
+
resolve_scope :is_private, -> (args) { args[:is_public] == false }
|
148
|
+
```
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
# Credits
|
153
|
+
- Cole Turner ([@colepatrickturner](/colepatrickturner))
|
154
|
+
- Peter Salanki ([@salanki](/salanki))
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Rails
|
3
|
+
class Resolver
|
4
|
+
VERSION = '0.1.2'
|
5
|
+
|
6
|
+
def initialize(callable=nil)
|
7
|
+
unless callable.nil?
|
8
|
+
raise ArgumentError, "Resolver requires a callable type or nil" unless callable.respond_to? :call
|
9
|
+
end
|
10
|
+
|
11
|
+
@callable = callable || Proc.new { model.all }
|
12
|
+
@obj = nil
|
13
|
+
@args = nil
|
14
|
+
@ctx = nil
|
15
|
+
@resolvers = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(obj, args, ctx)
|
19
|
+
@obj = obj
|
20
|
+
@args = args
|
21
|
+
@ctx = ctx
|
22
|
+
|
23
|
+
@result = @callable.call(obj, args, ctx)
|
24
|
+
|
25
|
+
# If there's an ID type, offer ID resolution_strategy
|
26
|
+
if has_id_field and args.key? :id
|
27
|
+
lookup_id(args[:id])
|
28
|
+
end
|
29
|
+
|
30
|
+
@resolvers.each do |field,method|
|
31
|
+
if args.key? field
|
32
|
+
method
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
result = payload
|
37
|
+
|
38
|
+
@obj = nil
|
39
|
+
@args = nil
|
40
|
+
@ctx = nil
|
41
|
+
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
def payload
|
46
|
+
# Return all results if it's a list or a connection
|
47
|
+
if connection? or list?
|
48
|
+
@result.all
|
49
|
+
else
|
50
|
+
@result.first
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def field_name
|
55
|
+
@ctx.ast_node.name
|
56
|
+
end
|
57
|
+
|
58
|
+
def has_id_field
|
59
|
+
@ctx.irep_node.children.any? {|x| x[1].return_type == GraphQL::ID_TYPE }
|
60
|
+
end
|
61
|
+
|
62
|
+
def connection?
|
63
|
+
@ctx.irep_node.definitions.all? { |type_defn, field_defn| field_defn.resolve_proc.is_a?(GraphQL::Relay::ConnectionResolve) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def list?
|
67
|
+
@ctx.irep_node.definitions.all? { |type_defn, field_defn| field_defn.type.kind.eql?(GraphQL::TypeKinds::LIST) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def model
|
71
|
+
unless self.class < Resolvers::Base
|
72
|
+
raise ArgumentError, "Cannot call `model` on BaseResolver"
|
73
|
+
end
|
74
|
+
|
75
|
+
"::#{self.class.name.demodulize}".constantize
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.resolve(field, method)
|
79
|
+
self.class_eval do
|
80
|
+
@resolvers[field] = method
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.resolve_where(field)
|
85
|
+
self.class_eval do
|
86
|
+
resolve(field, Proc.new {
|
87
|
+
@result = @result.where(field, @args[field])
|
88
|
+
})
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.resolve_method(field)
|
93
|
+
self.class_eval do
|
94
|
+
resolve(field, Proc.new {
|
95
|
+
send(field, @args[field])
|
96
|
+
})
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def lookup_id(value)
|
101
|
+
if is_global_id(value)
|
102
|
+
type_name, id = NodeIdentification.from_global_id(value)
|
103
|
+
constantized = "::#{type_name}".constantize
|
104
|
+
|
105
|
+
if constantized == model
|
106
|
+
@result = @result.where(:id => id)
|
107
|
+
else
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
else
|
111
|
+
@result = @result.where(:id => value)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graphql-rails-resolver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cole Turner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: graphql
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.18.14
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.18.14
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: A utility for ease graphql-ruby integration into a Rails project.
|
42
|
+
email: turner.cole@gmail.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- README.md
|
48
|
+
- lib/graphql/rails.rb
|
49
|
+
- lib/graphql/rails/resolver.rb
|
50
|
+
homepage: http://rubygems.org/gems/graphql-rails-resolver
|
51
|
+
licenses:
|
52
|
+
- MIT
|
53
|
+
metadata: {}
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 2.3.0
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 2.4.5.1
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: GraphQL + Rails integration for Field Resolvers.
|
74
|
+
test_files: []
|