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 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,2 @@
1
+ require 'graphql'
2
+ require 'graphql/rails/resolver'
@@ -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: []