hq-graphql 2.1.9 → 2.2.1

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: 981f1e51c20c41befde482f0ab312887f597727265508b2f8b4290420c91e2e1
4
- data.tar.gz: 3038b46301c4b612a99988ba7fe9aa66aadc401a7a8a4ea8a959b3e8c23e8743
3
+ metadata.gz: '08dfdf7baf4c44a54bdb3ecb9e0e89baacfaab9e6dda53525a0f8dded456a8c9'
4
+ data.tar.gz: 2b12d46d565e00710f49618e603c6a88f7fd09cc753d82cb8395b8e2d7f36447
5
5
  SHA512:
6
- metadata.gz: 30e473198ed03eaf14a9a4c9730c2a5816f5914dc745ef1fe0acc0b8e16f6a0b9b1f80bb2861fcb7bec16a5a0860bca1197b54892450686e545ef04f4f141611
7
- data.tar.gz: e4b0b57d5674e48fe75ec61de48cf484425b504500b1e201322650211856de5557da6353f95907d77bb41bf0b41ef52c0434349d805dc8b29fec27eb0182c57c
6
+ metadata.gz: 157d2faad665955ce59916bf809e265ff95f3910c8f2999fb88d697f9b1e8bacf624ee3750a90cdaf38d1777b848659ded147da5669c46b60febe6c5c05e4679
7
+ data.tar.gz: 2fa8a1a3b59f900553eb91673dc5b86daf9e25ad35d45ada3ee0cb8799882a4de9da7a3f5e59e82ebd24570f1eb86cb0813ebea959462fd3248495eb61538f13
data/README.md CHANGED
@@ -8,6 +8,7 @@ OneHQ GraphQL interface to [Ruby Graphql](https://github.com/rmosolgo/graphql-ru
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
@@ -13,31 +13,32 @@ module HQ
13
13
  non_breaking: 2
14
14
  }
15
15
 
16
- class << self
17
- def compare(old_schema, new_schema, criticality: :breaking)
18
- level = CRITICALITY[criticality]
19
- raise ::ArgumentError, "Invalid criticality. Possible values are #{CRITICALITY.keys.join(", ")}" unless level
20
-
21
- result = ::GraphQL::SchemaComparator.compare(convert_schema_to_string(old_schema), convert_schema_to_string(new_schema))
22
- return nil if result.identical?
23
-
24
- changes = {}
25
- changes[:breaking] = result.breaking_changes
26
- if level >= CRITICALITY[:dangerous]
27
- changes[:dangerous] = result.dangerous_changes
28
- end
29
- if level >= CRITICALITY[:non_breaking]
30
- changes[:non_breaking] = result.non_breaking_changes
31
- end
32
- return nil unless changes.values.flatten.any?
33
-
34
- changes
16
+ def self.compare(old_schema, new_schema, criticality: :breaking)
17
+ level = CRITICALITY[criticality]
18
+ raise ::ArgumentError, "Invalid criticality. Possible values are #{CRITICALITY.keys.join(", ")}" unless level
19
+
20
+ result = ::GraphQL::SchemaComparator.compare(prepare_schema(old_schema), prepare_schema(new_schema))
21
+ return if result.identical?
22
+ changes = {}
23
+ changes[:breaking] = result.breaking_changes
24
+ if level >= CRITICALITY[:dangerous]
25
+ changes[:dangerous] = result.dangerous_changes
35
26
  end
27
+ if level >= CRITICALITY[:non_breaking]
28
+ changes[:non_breaking] = result.non_breaking_changes
29
+ end
30
+ return unless changes.values.flatten.any?
31
+
32
+ changes
33
+ end
36
34
 
35
+ class << self
37
36
  private
38
37
 
39
- def convert_schema_to_string(schema)
40
- schema.is_a?(::String) ? schema : schema.to_definition
38
+ def prepare_schema(schema)
39
+ schema = ::GraphQL::Schema.from_definition(schema) if schema.is_a?(String)
40
+ schema.load_types!
41
+ schema
41
42
  end
42
43
  end
43
44
  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