hq-graphql 2.1.8 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b3620be84fad5d620dfb18c0f51bd0cf726671b49320ec1910bd534ef31d569
4
- data.tar.gz: 8974a6d0e3aa4dd0ad5e1dd37b0802c8b70d428365abfdb3342da4fffe66a0cb
3
+ metadata.gz: a158f615a83d3743a038f248bc82c04285631e6575f62b5124bbb5767ab6ede9
4
+ data.tar.gz: 630bea394ce6e073b1f4ee2bd62047fcf46ba433357d1c759b99d7ecf350bfec
5
5
  SHA512:
6
- metadata.gz: c67704e43e662552da1818d5f53fcd5acd3bb38da62fb556fac427d590bf002906e58a4a39d7141bc33f22c689c76c9fb1b33cf7b155f9590df0ea9ba8f056b6
7
- data.tar.gz: f82c433ab64e9e5d34cb62f51464811621242de62220294766107fe96ad75cbaa665b69a4c99042f5a063c9f22106c1a980dbeee037de3c3e7353c4dd2cd41d0
6
+ metadata.gz: b6076f33bc12f04a7e701f2deeb7420fc44792af3e99727029196d24d68280276a15c7c8d70d875de82c3ec48e41afdc13b5578a45fbcff2ddcc0edfa8971628
7
+ data.tar.gz: c85f1b1af47ddee4f049a431277ca63bb1728ab74738fe8bc1bbc0f7b31f9dedd14f12fb850045f82ff301d83729d3f896628154e0fcaecea17c117dfd6140d5
data/README.md CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  OneHQ GraphQL interface to [Ruby Graphql](https://github.com/rmosolgo/graphql-ruby).
4
4
 
5
- [![CircleCI](https://img.shields.io/circleci/project/github/OneHQ/hq-graphql.svg)](https://circleci.com/gh/OneHQ/hq-graphql/tree/master)
5
+ ![Test and Lint](https://github.com/OneHQ/hq-graphql/workflows/Test%20and%20Lint/badge.svg)
6
6
  [![GitHub tag](https://img.shields.io/github/tag/OneHQ/hq-graphql.svg)](https://github.com/OneHQ/hq-graphql)
7
7
 
8
8
  ## Configuration
9
9
 
10
10
  ### Default Scope
11
+
11
12
  Define a global default scope.
12
13
 
13
14
  ```ruby
@@ -18,10 +19,22 @@ Define a global default scope.
18
19
  end
19
20
  ```
20
21
 
22
+ ### Excluded Inputs
23
+
24
+ Define a global excluded input fields. Useful for excluding (autogenerated | auto set) fields like below.
25
+
26
+ ```ruby
27
+ ::HQ::GraphQL.configure do |config|
28
+ config.excluded_inputs = [:id, :created_at, :updated_at]
29
+ end
30
+ ```
31
+
21
32
  ## GraphQL Resource
33
+
22
34
  Connect to ActiveRecord to auto generate queries and mutations.
23
35
 
24
36
  ### Queries
37
+
25
38
  Include `::HQ::GraphQL::Resource` and set `self.model_name` to start using queries.
26
39
  Fields will be created for all active record columns. Association fields will be created if the association
27
40
  is also a GraphQL Resource.
@@ -36,6 +49,7 @@ end
36
49
  ```
37
50
 
38
51
  #### Customize
52
+
39
53
  ```ruby
40
54
  class AdvisorResource
41
55
  include ::HQ::GraphQL::Resource
@@ -57,6 +71,7 @@ end
57
71
  ```
58
72
 
59
73
  ### Mutations
74
+
60
75
  Mutations will not be created by default. Add `mutations` to a resource to build mutations for create, update, and destroy.
61
76
 
62
77
  ```ruby
@@ -79,34 +94,40 @@ end
79
94
  ```
80
95
 
81
96
  ### Enums
97
+
82
98
  Auto generate enums from the database using ActiveRecord
83
99
  This comes in handy when we have constants that we want represented as enums.
84
100
 
85
101
  #### Example
102
+
86
103
  Let's assume we're saving data into a user types table
104
+
87
105
  ```postgresl
88
106
  # select * from user_types;
89
- id | name
107
+ id | name
90
108
  --- +-------------
91
- 1 | Admin
109
+ 1 | Admin
92
110
  2 | Support User
93
111
  (2 rows)
94
112
  ```
95
113
 
96
114
  ```ruby
97
- class Enums::UserType < ::HQ::GraphQL::Enum
115
+ class Enums::UserType < ::GraphQL::Schema::Enum
98
116
  with_model
99
117
  end
100
118
  ```
119
+
101
120
  This class automatically uses the UserType ActiveRecord model to generate an enum:
121
+
102
122
  ```graphql
103
- enum UserType {
104
- Admin
105
- SupportUser
106
- }
123
+ enum UserType {
124
+ Admin
125
+ SupportUser
126
+ }
107
127
  ```
108
128
 
109
129
  ### Root Mutations
130
+
110
131
  Add mutations to your schema
111
132
 
112
133
  ```ruby
@@ -118,7 +139,9 @@ end
118
139
  ```
119
140
 
120
141
  ### Default Root Queries
142
+
121
143
  Create a root query:
144
+
122
145
  ```ruby
123
146
  class AdvisorResource
124
147
  include ::HQ::GraphQL::Resource
@@ -135,6 +158,7 @@ end
135
158
  ```
136
159
 
137
160
  ### Custom Root Queries
161
+
138
162
  ```ruby
139
163
  class AdvisorResource
140
164
  include ::HQ::GraphQL::Resource
@@ -154,9 +178,10 @@ class AdvisorResource
154
178
  end
155
179
  ```
156
180
 
157
- ## Create a new ::HQ::GraphQL::Object
181
+ ## Create a new ::GraphQL::Schema::Object
182
+
158
183
  ```ruby
159
- class AdvisorType < ::HQ::GraphQL::Object
184
+ class AdvisorType < ::GraphQL::Schema::Object
160
185
  # Supports graphql-ruby functionality
161
186
  field :id, Int, null: false
162
187
 
data/lib/hq/graphql.rb CHANGED
@@ -36,6 +36,10 @@ module HQ
36
36
  config.extract_class.call(klass)
37
37
  end
38
38
 
39
+ def self.excluded_inputs
40
+ config.excluded_inputs || []
41
+ end
42
+
39
43
  def self.lookup_resource(klass)
40
44
  [klass, klass.base_class, klass.superclass].lazy.map do |k|
41
45
  config.resource_lookup.call(k) || resources.detect { |r| r.model_klass == k }
@@ -47,6 +51,7 @@ module HQ
47
51
  end
48
52
 
49
53
  def self.reset!
54
+ @lazy_load_classes = nil
50
55
  @root_queries = nil
51
56
  @enums = nil
52
57
  @resources = nil
@@ -54,16 +59,28 @@ module HQ
54
59
  ::HQ::GraphQL::Types.reset!
55
60
  end
56
61
 
62
+ def self.load_types!
63
+ lazy_load_classes.pop.lazy_load! while lazy_load_classes.length > 0
64
+ end
65
+
66
+ def self.lazy_load(klass)
67
+ lazy_load_classes << klass unless lazy_load_classes.include?(klass)
68
+ end
69
+
70
+ def self.lazy_load_classes
71
+ @lazy_load_classes ||= []
72
+ end
73
+
57
74
  def self.root_queries
58
- @root_queries ||= Set.new
75
+ @root_queries ||= []
59
76
  end
60
77
 
61
78
  def self.enums
62
- @enums ||= Set.new
79
+ @enums ||= []
63
80
  end
64
81
 
65
82
  def self.resources
66
- @resources ||= Set.new
83
+ @resources ||= []
67
84
  end
68
85
  end
69
86
  end
@@ -71,16 +88,12 @@ end
71
88
  require "hq/graphql/association_loader"
72
89
  require "hq/graphql/scalars"
73
90
  require "hq/graphql/comparator"
74
- require "hq/graphql/enum"
91
+ require "hq/graphql/ext"
75
92
  require "hq/graphql/inputs"
76
- require "hq/graphql/input_object"
77
- require "hq/graphql/mutation"
78
- require "hq/graphql/object"
79
93
  require "hq/graphql/paginated_association_loader"
80
94
  require "hq/graphql/record_loader"
81
95
  require "hq/graphql/resource"
82
96
  require "hq/graphql/root_mutation"
83
97
  require "hq/graphql/root_query"
84
- require "hq/graphql/schema"
85
98
  require "hq/graphql/types"
86
99
  require "hq/graphql/engine"
@@ -4,6 +4,7 @@ module HQ
4
4
  module GraphQL
5
5
  class AssociationLoader < ::GraphQL::Batch::Loader
6
6
  def initialize(model, association_name)
7
+ super()
7
8
  @model = model
8
9
  @association_name = association_name
9
10
  validate
@@ -15,12 +15,13 @@ module HQ
15
15
 
16
16
  class << self
17
17
  def compare(old_schema, new_schema, criticality: :breaking)
18
+ old_schema.load_types! if old_schema < ::GraphQL::Schema
19
+ new_schema.load_types! if old_schema < ::GraphQL::Schema
18
20
  level = CRITICALITY[criticality]
19
21
  raise ::ArgumentError, "Invalid criticality. Possible values are #{CRITICALITY.keys.join(", ")}" unless level
20
22
 
21
- result = ::GraphQL::SchemaComparator.compare(convert_schema_to_string(old_schema), convert_schema_to_string(new_schema))
23
+ result = ::GraphQL::SchemaComparator.compare(old_schema, new_schema)
22
24
  return nil if result.identical?
23
-
24
25
  changes = {}
25
26
  changes[:breaking] = result.breaking_changes
26
27
  if level >= CRITICALITY[:dangerous]
@@ -33,12 +34,6 @@ module HQ
33
34
 
34
35
  changes
35
36
  end
36
-
37
- private
38
-
39
- def convert_schema_to_string(schema)
40
- schema.is_a?(::String) ? schema : schema.to_definition
41
- end
42
37
  end
43
38
  end
44
39
  end
@@ -10,6 +10,7 @@ module HQ
10
10
  :extract_class,
11
11
  :resource_lookup,
12
12
  :use_experimental_associations,
13
+ :excluded_inputs,
13
14
  keyword_init: true
14
15
  )
15
16
  def initialize(
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hq/graphql/enum"
3
+ require "hq/graphql/ext/enum_extensions"
4
4
 
5
5
  module HQ
6
- class GraphQL::Enum::SortBy < ::HQ::GraphQL::Enum
7
- value "CreatedAt", value: :created_at
8
- value "UpdatedAt", value: :updated_at
6
+ module GraphQL
7
+ module Enum
8
+ class SortBy < ::GraphQL::Schema::Enum
9
+ value "CreatedAt", value: :created_at
10
+ value "UpdatedAt", value: :updated_at
11
+ end
12
+ end
9
13
  end
10
14
  end
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hq/graphql/enum"
3
+ require "hq/graphql/ext/enum_extensions"
4
4
 
5
5
  module HQ
6
- class GraphQL::Enum::SortOrder < ::HQ::GraphQL::Enum
7
- value "ASC", value: :asc
8
- value "DESC", value: :desc
6
+ module GraphQL
7
+ module Enum
8
+ class SortOrder < ::GraphQL::Schema::Enum
9
+ value "ASC", value: :asc
10
+ value "DESC", value: :desc
11
+ end
12
+ end
9
13
  end
10
14
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hq/graphql/ext/active_record_extensions"
4
+ require "hq/graphql/ext/enum_extensions"
5
+ require "hq/graphql/ext/input_object_extensions"
6
+ require "hq/graphql/ext/mutation_extensions"
7
+ require "hq/graphql/ext/object_extensions"
8
+ require "hq/graphql/ext/schema_extensions"
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HQ
4
+ module GraphQL
5
+ module Ext
6
+ module ActiveRecordExtensions
7
+ class ActiveRecordError < StandardError
8
+ MISSING_MODEL_MSG = "Your GraphQL object must be connected to a model: `::GraphQL::Schema::Object.with_model 'User'`"
9
+ MISSING_ATTR_MSG = "Can't find attr %{model}.%{attr}'`"
10
+ MISSING_ASSOC_MSG = "Can't find association %{model}.%{assoc}'`"
11
+ end
12
+
13
+ def self.included(klass)
14
+ klass.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ attr_accessor :model_name,
19
+ :authorize_action,
20
+ :auto_load_attributes,
21
+ :auto_load_associations,
22
+ :auto_load_enums
23
+
24
+ def lazy_load(&block)
25
+ @lazy_load ||= []
26
+ if block
27
+ ::HQ::GraphQL.lazy_load(self)
28
+ @lazy_load << block
29
+ end
30
+ @lazy_load
31
+ end
32
+
33
+ def lazy_load!
34
+ lazy_load.shift.call while lazy_load.length > 0
35
+ @lazy_load = nil
36
+ end
37
+
38
+ def model_columns
39
+ model_columns =
40
+ if auto_load_attributes
41
+ model_klass.columns
42
+ else
43
+ added_attributes.map { |attr| column_from_model(attr) }
44
+ end
45
+
46
+ # validate removed_attributes exist
47
+ removed_attributes.each { |attr| column_from_model(attr) }
48
+
49
+ model_columns.reject { |c| removed_attributes.include?(c.name.to_sym) }.sort_by(&:name)
50
+ end
51
+
52
+ def model_associations
53
+ model_associations = []
54
+ enums = model_klass.reflect_on_all_associations.select { |a| is_enum?(a) }
55
+ associatons = model_klass.reflect_on_all_associations - enums
56
+
57
+ if auto_load_enums
58
+ model_associations.concat(enums)
59
+ end
60
+
61
+ if auto_load_associations
62
+ model_associations.concat(associatons)
63
+ end
64
+
65
+ model_associations.concat(added_associations.map { |association| association_from_model(association) }).uniq
66
+
67
+ # validate removed_associations exist
68
+ removed_associations.each { |association| association_from_model(association) }
69
+
70
+ model_associations.reject { |a| removed_associations.include?(a.name.to_sym) }.sort_by(&:name)
71
+ end
72
+
73
+ private
74
+
75
+ def add_attributes(*attrs)
76
+ validate_model!
77
+ added_attributes.concat attrs.map(&:to_sym)
78
+ end
79
+ alias_method :add_attribute, :add_attributes
80
+ alias_method :add_attrs, :add_attributes
81
+ alias_method :add_attr, :add_attributes
82
+
83
+ def remove_attributes(*attrs)
84
+ validate_model!
85
+ removed_attributes.concat attrs.map(&:to_sym)
86
+ end
87
+ alias_method :remove_attribute, :remove_attributes
88
+ alias_method :remove_attrs, :remove_attributes
89
+ alias_method :remove_attr, :remove_attributes
90
+
91
+ def add_associations(*associations)
92
+ validate_model!
93
+ added_associations.concat associations.map(&:to_sym)
94
+ end
95
+ alias_method :add_association, :add_associations
96
+
97
+ def remove_associations(*associations)
98
+ validate_model!
99
+ removed_associations.concat associations.map(&:to_sym)
100
+ end
101
+ alias_method :remove_association, :remove_associations
102
+
103
+ def model_klass
104
+ @model_klass ||= model_name.constantize
105
+ end
106
+
107
+ def column_from_model(attr)
108
+ model_klass.columns_hash[attr.to_s] || raise(ActiveRecordError, ActiveRecordError::MISSING_ATTR_MSG % { model: model_name, attr: attr })
109
+ end
110
+
111
+ def association_from_model(association)
112
+ model_klass.reflect_on_association(association) || raise(ActiveRecordError, ActiveRecordError::MISSING_ASSOC_MSG % { model: model_name, assoc: association })
113
+ end
114
+
115
+ def added_attributes
116
+ @added_attributes ||= []
117
+ end
118
+
119
+ def removed_attributes
120
+ @removed_attributes ||= []
121
+ end
122
+
123
+ def added_associations
124
+ @added_associations ||= []
125
+ end
126
+
127
+ def removed_associations
128
+ @removed_associations ||= []
129
+ end
130
+
131
+ def camelize(name)
132
+ name.to_s.camelize(:lower)
133
+ end
134
+
135
+ def is_enum?(association)
136
+ ::HQ::GraphQL.enums.include?(association.klass)
137
+ end
138
+
139
+ def validate_model!
140
+ lazy_load do
141
+ model_name || raise(ActiveRecordError, ActiveRecordError::MISSING_MODEL_MSG)
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end