graphql-rails-resolver 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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: []