hq-graphql 2.1.12 → 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: 34f3a14ee58de081c8d52088ebc7986cbc2ccf597392015bcb44aa629aef6556
4
- data.tar.gz: 39a436fca18cae2f81e672e78ae3118c4893512781a594c24d086cbf0f7e00c1
3
+ metadata.gz: a158f615a83d3743a038f248bc82c04285631e6575f62b5124bbb5767ab6ede9
4
+ data.tar.gz: 630bea394ce6e073b1f4ee2bd62047fcf46ba433357d1c759b99d7ecf350bfec
5
5
  SHA512:
6
- metadata.gz: '09606ace844e47c5c414a73d1f3b2ffd7a7183b173090ade1e6287bc42aa6cc84eed691dd3d3a2b8d7ad4f8045779550f619fab017ed0dd79c49621c007ad9d1'
7
- data.tar.gz: d8a07ccf03a311b807c7669202ce54d64e89db640f15911f2a1c6ddc3237d9fa94410e8527ae70539659a86ca06cf89317409b00d98b589056b9d6db5ae80dfc
6
+ metadata.gz: b6076f33bc12f04a7e701f2deeb7420fc44792af3e99727029196d24d68280276a15c7c8d70d875de82c3ec48e41afdc13b5578a45fbcff2ddcc0edfa8971628
7
+ data.tar.gz: c85f1b1af47ddee4f049a431277ca63bb1728ab74738fe8bc1bbc0f7b31f9dedd14f12fb850045f82ff301d83729d3f896628154e0fcaecea17c117dfd6140d5
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
@@ -19,6 +20,7 @@ end
19
20
  ```
20
21
 
21
22
  ### Excluded Inputs
23
+
22
24
  Define a global excluded input fields. Useful for excluding (autogenerated | auto set) fields like below.
23
25
 
24
26
  ```ruby
@@ -28,9 +30,11 @@ end
28
30
  ```
29
31
 
30
32
  ## GraphQL Resource
33
+
31
34
  Connect to ActiveRecord to auto generate queries and mutations.
32
35
 
33
36
  ### Queries
37
+
34
38
  Include `::HQ::GraphQL::Resource` and set `self.model_name` to start using queries.
35
39
  Fields will be created for all active record columns. Association fields will be created if the association
36
40
  is also a GraphQL Resource.
@@ -45,6 +49,7 @@ end
45
49
  ```
46
50
 
47
51
  #### Customize
52
+
48
53
  ```ruby
49
54
  class AdvisorResource
50
55
  include ::HQ::GraphQL::Resource
@@ -66,6 +71,7 @@ end
66
71
  ```
67
72
 
68
73
  ### Mutations
74
+
69
75
  Mutations will not be created by default. Add `mutations` to a resource to build mutations for create, update, and destroy.
70
76
 
71
77
  ```ruby
@@ -88,34 +94,40 @@ end
88
94
  ```
89
95
 
90
96
  ### Enums
97
+
91
98
  Auto generate enums from the database using ActiveRecord
92
99
  This comes in handy when we have constants that we want represented as enums.
93
100
 
94
101
  #### Example
102
+
95
103
  Let's assume we're saving data into a user types table
104
+
96
105
  ```postgresl
97
106
  # select * from user_types;
98
- id | name
107
+ id | name
99
108
  --- +-------------
100
- 1 | Admin
109
+ 1 | Admin
101
110
  2 | Support User
102
111
  (2 rows)
103
112
  ```
104
113
 
105
114
  ```ruby
106
- class Enums::UserType < ::HQ::GraphQL::Enum
115
+ class Enums::UserType < ::GraphQL::Schema::Enum
107
116
  with_model
108
117
  end
109
118
  ```
119
+
110
120
  This class automatically uses the UserType ActiveRecord model to generate an enum:
121
+
111
122
  ```graphql
112
- enum UserType {
113
- Admin
114
- SupportUser
115
- }
123
+ enum UserType {
124
+ Admin
125
+ SupportUser
126
+ }
116
127
  ```
117
128
 
118
129
  ### Root Mutations
130
+
119
131
  Add mutations to your schema
120
132
 
121
133
  ```ruby
@@ -127,7 +139,9 @@ end
127
139
  ```
128
140
 
129
141
  ### Default Root Queries
142
+
130
143
  Create a root query:
144
+
131
145
  ```ruby
132
146
  class AdvisorResource
133
147
  include ::HQ::GraphQL::Resource
@@ -144,6 +158,7 @@ end
144
158
  ```
145
159
 
146
160
  ### Custom Root Queries
161
+
147
162
  ```ruby
148
163
  class AdvisorResource
149
164
  include ::HQ::GraphQL::Resource
@@ -163,9 +178,10 @@ class AdvisorResource
163
178
  end
164
179
  ```
165
180
 
166
- ## Create a new ::HQ::GraphQL::Object
181
+ ## Create a new ::GraphQL::Schema::Object
182
+
167
183
  ```ruby
168
- class AdvisorType < ::HQ::GraphQL::Object
184
+ class AdvisorType < ::GraphQL::Schema::Object
169
185
  # Supports graphql-ruby functionality
170
186
  field :id, Int, null: false
171
187
 
data/lib/hq/graphql.rb CHANGED
@@ -51,6 +51,7 @@ module HQ
51
51
  end
52
52
 
53
53
  def self.reset!
54
+ @lazy_load_classes = nil
54
55
  @root_queries = nil
55
56
  @enums = nil
56
57
  @resources = nil
@@ -58,16 +59,28 @@ module HQ
58
59
  ::HQ::GraphQL::Types.reset!
59
60
  end
60
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
+
61
74
  def self.root_queries
62
- @root_queries ||= Set.new
75
+ @root_queries ||= []
63
76
  end
64
77
 
65
78
  def self.enums
66
- @enums ||= Set.new
79
+ @enums ||= []
67
80
  end
68
81
 
69
82
  def self.resources
70
- @resources ||= Set.new
83
+ @resources ||= []
71
84
  end
72
85
  end
73
86
  end
@@ -75,16 +88,12 @@ end
75
88
  require "hq/graphql/association_loader"
76
89
  require "hq/graphql/scalars"
77
90
  require "hq/graphql/comparator"
78
- require "hq/graphql/enum"
91
+ require "hq/graphql/ext"
79
92
  require "hq/graphql/inputs"
80
- require "hq/graphql/input_object"
81
- require "hq/graphql/mutation"
82
- require "hq/graphql/object"
83
93
  require "hq/graphql/paginated_association_loader"
84
94
  require "hq/graphql/record_loader"
85
95
  require "hq/graphql/resource"
86
96
  require "hq/graphql/root_mutation"
87
97
  require "hq/graphql/root_query"
88
- require "hq/graphql/schema"
89
98
  require "hq/graphql/types"
90
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
@@ -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
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hq/graphql/types"
4
+
5
+ module HQ::GraphQL
6
+ module Ext
7
+ module EnumExtensions
8
+ ## Auto generate enums from the database using ActiveRecord
9
+ # This comes in handy when we have constants that we want represented as enums.
10
+ #
11
+ # == Example
12
+ # Let's assume we're saving data into a user types table
13
+ # # select * from user_types;
14
+ # id | name
15
+ # --- +-------------
16
+ # 1 | Admin
17
+ # 2 | Support User
18
+ # (2 rows)
19
+ #
20
+ # ```ruby
21
+ # class Enums::UserType < ::HQ::GraphQL::Enum
22
+ # with_model
23
+ # end
24
+ # ```
25
+ #
26
+ # Creates the following enum:
27
+ # ```graphql
28
+ # enum UserType {
29
+ # Admin
30
+ # SupportUser
31
+ # }
32
+ # ```
33
+ def with_model(
34
+ klass = default_model_name.safe_constantize,
35
+ prefix: nil,
36
+ register: true,
37
+ scope: nil,
38
+ strip: /(^[^_a-zA-Z])|([^_a-zA-Z0-9]*)/,
39
+ value_method: :name
40
+ )
41
+ raise ArgumentError.new(<<~ERROR) if !klass
42
+ `::HQ::GraphQL::Enum.with_model {...}' had trouble automatically inferring the class name.
43
+ Avoid this by manually passing in the class name: `::HQ::GraphQL::Enum.with_model(#{default_model_name}) {...}`
44
+ ERROR
45
+
46
+ if register
47
+ ::HQ::GraphQL.enums << klass
48
+ ::HQ::GraphQL::Types.register(klass, self)
49
+ end
50
+
51
+ lazy_load do
52
+ records = scope ? klass.instance_exec(&scope) : klass.all
53
+ records.each do |record|
54
+ value "#{prefix}#{record.send(value_method).gsub(strip, "")}", value: record
55
+ end
56
+ end
57
+ end
58
+
59
+ def lazy_load(&block)
60
+ @lazy_load ||= []
61
+ if block
62
+ ::HQ::GraphQL.lazy_load(self)
63
+ @lazy_load << block
64
+ end
65
+ @lazy_load
66
+ end
67
+
68
+ def lazy_load!
69
+ lazy_load.shift.call while lazy_load.length > 0
70
+ @lazy_load = []
71
+ end
72
+
73
+ def default_model_name
74
+ to_s.sub(/^((::)?\w+)::/, "")
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+
81
+ ::GraphQL::Schema::Enum.extend ::HQ::GraphQL::Ext::EnumExtensions