hq-graphql 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: be9de36cb5b0f12696e98d85156404d89acdc6d5
4
- data.tar.gz: 0e887aabc0d14374ad192e464a8b1e3e4ac4ea8f
3
+ metadata.gz: b25a73fce273a48a00a185766cae51a9ec2e3150
4
+ data.tar.gz: 7c325eaef387c340778954fb83dbb3d0ccac9c03
5
5
  SHA512:
6
- metadata.gz: 7ec2755f98cd6045e93da317edec985a3a148c4baef5a7e30d24c1568020327f455d4a7efb14b117d482c0b51106e69ad67f0db33c6051c8b42d750f951086c8
7
- data.tar.gz: aae1f46e971c88db6a4e87d919dc354e0235931041bf485172df0109520892f1e0294a10b99c81943ca71132b92ed1bfd56f76fdda5d2cb0e3b9e9323bc56665
6
+ metadata.gz: da27072503a7751c1eefa68ebdf6a0fcb77b28a6213fb8fb6a4d7fdec32d9f0bd14dfb585d0196f4d1119cd2d5ecb8d33e54c7c1e902051612e3fb7d9e2ab815
7
+ data.tar.gz: c5b52d4d4d8af7fe53b1c1aa50ad28b62eb36b66c2f39de2e6b4a51add6505c110b3e7d6182f0efcb302d53ca70c78ccf1d5817206eef413635d033832cc10af
data/README.md CHANGED
@@ -7,20 +7,106 @@ OneHQ GraphQL interface to [Ruby Graphql](https://github.com/rmosolgo/graphql-ru
7
7
 
8
8
  ## Configuration
9
9
 
10
- You can pass configuration options as a block to `::HQ::GraphQL.configure`.
10
+ ### Default Scope
11
+ Define a global default scope.
11
12
 
12
13
  ```ruby
13
- # The gem assumes that if your model is called `MyModel`, the corresponding type is `MyModelType`.
14
- # You can override that convention.
15
- # Default is: -> (model_class) { "#{model_class.name.demodulize}Type" }
16
14
  ::HQ::GraphQL.config do |config|
17
- config.model_to_graphql_type = -> (model_class) { "::CustomNameSpace::#{model_class.name}Type" }
15
+ config.default_scope = ->(scope, context) do
16
+ scope.where(organization_id: context[:organization_id])
17
+ end
18
+ end
19
+ ```
20
+
21
+ ## GraphQL Resource
22
+ Connect to ActiveRecord to auto generate queries and mutations.
23
+
24
+ ### Queries
25
+ Include `::HQ::GraphQL::Resource` and set `self.model_name` to start using queries.
26
+ Fields will be created for all active record columns. Association fields will be created if the association
27
+ is also a GraphQL Resource.
28
+
29
+ ```ruby
30
+ class AdvisorResource
31
+ include ::HQ::GraphQL::Resource
32
+
33
+ # ActiveRecord Model Name
34
+ self.model_name = "Advisor"
35
+ end
36
+ ```
37
+
38
+ #### Customize
39
+ ```ruby
40
+ class AdvisorResource
41
+ include ::HQ::GraphQL::Resource
42
+ self.model_name = "Advisor"
43
+
44
+ # Turn off fields for active record associations
45
+ query attributes: true, associations: false do
46
+ # Create field for addresses
47
+ add_association :addresses
48
+
49
+ # add a custom field
50
+ field :custom_field, String, null: false
51
+
52
+ def custom_field
53
+ "Fizz"
54
+ end
55
+ end
56
+ end
57
+ ```
58
+
59
+ ### Mutations
60
+ Mutations will not be created by default. Add `mutations` to a resource to build mutations for create, update, and destroy.
61
+
62
+ ```ruby
63
+ class AdvisorResource
64
+ include ::HQ::GraphQL::Resource
65
+ self.model_name = "Advisor"
66
+
67
+ # Builds the following mutations:
68
+ # createAdvisor
69
+ # updateAdvisor
70
+ # destroyAdvisor
71
+ mutations create: true, update: true, destroy: true
72
+
73
+ # Turn off fields for active record associations
74
+ inputs attributes: true, associations: false do
75
+ # Create input argument for addresses
76
+ add_association :addresses
77
+ end
18
78
  end
19
79
  ```
20
80
 
21
- ## Usage
81
+ ### Root Mutations
82
+ Add mutations to your schema
83
+
84
+ ```ruby
85
+ class RootMutation < ::HQ::GraphQL::RootMutation; end
86
+
87
+ class Schema < ::GraphQL::Schema
88
+ mutation(RootMutation)
89
+ end
90
+ ```
91
+
92
+ ### Root Queries
93
+ Create a root query:
94
+ ```ruby
95
+ class AdvisorResource
96
+ include ::HQ::GraphQL::Resource
97
+ self.model_name = "Advisor"
98
+
99
+ root_query
100
+ end
101
+
102
+ class RootQuery < ::HQ::GraphQL::RootQuery; end
103
+
104
+ class Schema < ::GraphQL::Schema
105
+ mutation(RootQuery)
106
+ end
107
+ ```
22
108
 
23
- Create a new ::HQ::GraphQL::Object
109
+ ## Create a new ::HQ::GraphQL::Object
24
110
  ```ruby
25
111
  class AdvisorType < ::HQ::GraphQL::Object
26
112
  # Supports graphql-ruby functionality
@@ -0,0 +1,125 @@
1
+ module HQ
2
+ module GraphQL
3
+ module ActiveRecordExtensions
4
+ class Error < StandardError
5
+ MISSING_MODEL_MSG = "Your GraphQL object must be connected to a model: `::HQ::GraphQL::Object.with_model 'User'`".freeze
6
+ MISSING_ATTR_MSG = "Can't find attr %{model}.%{attr}'`".freeze
7
+ MISSING_ASSOC_MSG = "Can't find association %{model}.%{assoc}'`".freeze
8
+ end
9
+
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ module ClassMethods
15
+ attr_accessor :model_name,
16
+ :auto_load_attributes,
17
+ :auto_load_associations
18
+
19
+ def lazy_load(&block)
20
+ @lazy_load ||= []
21
+ @lazy_load << block if block
22
+ @lazy_load
23
+ end
24
+
25
+ def lazy_load!
26
+ lazy_load.map(&:call)
27
+ @lazy_load = []
28
+ end
29
+
30
+ def model_columns
31
+ model_columns =
32
+ if auto_load_attributes
33
+ model_klass.columns
34
+ else
35
+ added_attributes.map { |attr| column_from_model(attr) }
36
+ end
37
+
38
+ # validate removed_attributes exist
39
+ removed_attributes.each { |attr| column_from_model(attr) }
40
+
41
+ model_columns.reject { |c| removed_attributes.include?(c.name.to_sym) }
42
+ end
43
+
44
+ def model_associations
45
+ model_associations =
46
+ if auto_load_associations
47
+ model_klass.reflect_on_all_associations
48
+ else
49
+ added_associations.map { |association| association_from_model(association) }
50
+ end
51
+
52
+ # validate removed_associations exist
53
+ removed_associations.each { |association| association_from_model(association) }
54
+
55
+ model_associations.reject { |a| removed_associations.include?(a.name.to_sym) }
56
+ end
57
+
58
+ private
59
+
60
+ def add_attributes(*attrs)
61
+ validate_model!
62
+ added_attributes.concat attrs.map(&:to_sym)
63
+ end
64
+ alias_method :add_attribute, :add_attributes
65
+ alias_method :add_attrs, :add_attributes
66
+ alias_method :add_attr, :add_attributes
67
+
68
+ def remove_attributes(*attrs)
69
+ validate_model!
70
+ removed_attributes.concat attrs.map(&:to_sym)
71
+ end
72
+ alias_method :remove_attribute, :remove_attributes
73
+ alias_method :remove_attrs, :remove_attributes
74
+ alias_method :remove_attr, :remove_attributes
75
+
76
+ def add_associations(*associations)
77
+ validate_model!
78
+ added_associations.concat associations.map(&:to_sym)
79
+ end
80
+ alias_method :add_association, :add_associations
81
+
82
+ def remove_associations(*associations)
83
+ validate_model!
84
+ removed_associations.concat associations.map(&:to_sym)
85
+ end
86
+ alias_method :remove_association, :remove_associations
87
+
88
+ def model_klass
89
+ @model_klass ||= model_name.constantize
90
+ end
91
+
92
+ def column_from_model(attr)
93
+ model_klass.columns_hash[attr.to_s] || raise(Error, Error::MISSING_ATTR_MSG % { model: model_name, attr: attr })
94
+ end
95
+
96
+ def association_from_model(association)
97
+ model_klass.reflect_on_association(association) || raise(Error, Error::MISSING_ASSOC_MSG % { model: model_name, assoc: association })
98
+ end
99
+
100
+ def added_attributes
101
+ @added_attributes ||= []
102
+ end
103
+
104
+ def removed_attributes
105
+ @removed_attributes ||= []
106
+ end
107
+
108
+ def added_associations
109
+ @added_associations ||= []
110
+ end
111
+
112
+ def removed_associations
113
+ @removed_associations ||= []
114
+ end
115
+
116
+ def validate_model!
117
+ lazy_load do
118
+ model_name || raise(Error, Error::MISSING_MODEL_MSG)
119
+ end
120
+ end
121
+
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,56 @@
1
+ module HQ
2
+ module GraphQL
3
+ module InputExtensions
4
+
5
+ def self.included(base)
6
+ base.include Scalars
7
+ base.include ::HQ::GraphQL::ActiveRecordExtensions
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def with_model(model_name, attributes: true, associations: false)
14
+ self.model_name = model_name
15
+ self.auto_load_attributes = attributes
16
+ self.auto_load_associations = associations
17
+
18
+ lazy_load do
19
+ model_columns.each do |column|
20
+ argument_from_column(column)
21
+ end
22
+
23
+ model_associations.each do |association|
24
+ argument_from_association association
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def argument_from_association(association)
32
+ input = ::HQ::GraphQL::Inputs[association.klass]
33
+ name = association.name
34
+ name_attributes = "#{name}_attributes"
35
+ case association.macro
36
+ when :has_many
37
+ argument name_attributes, [input], required: false
38
+ else
39
+ argument name_attributes, input, required: false
40
+ end
41
+
42
+ if !model_klass.nested_attributes_options.keys.include?(name.to_sym)
43
+ model_klass.accepts_nested_attributes_for name, allow_destroy: true
44
+ end
45
+ rescue ::HQ::GraphQL::Inputs::Error
46
+ nil
47
+ end
48
+
49
+ def argument_from_column(column)
50
+ argument column.name, ::HQ::GraphQL::Types.type_from_column(column), required: false
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,17 @@
1
+ module HQ
2
+ module GraphQL
3
+ class InputObject < ::GraphQL::Schema::InputObject
4
+ include ::HQ::GraphQL::InputExtensions
5
+
6
+ def self.to_graphql
7
+ lazy_load!
8
+ super
9
+ end
10
+
11
+ def with_indifferent_access
12
+ to_h.with_indifferent_access
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ module HQ
2
+ module GraphQL
3
+ module Inputs
4
+ class Error < StandardError
5
+ MISSING_TYPE_MSG = "The GraphQL type for `%{klass}` is missing.".freeze
6
+ end
7
+
8
+ def self.[](key)
9
+ @inputs ||= Hash.new do |hash, klass|
10
+ hash[klass] = klass_for(klass)
11
+ end
12
+ @inputs[key]
13
+ end
14
+
15
+ # Only being used in testing
16
+ def self.reset!
17
+ @inputs = nil
18
+ end
19
+
20
+ class << self
21
+ private
22
+
23
+ def klass_for(klass_or_string)
24
+ klass = klass_or_string.is_a?(String) ? klass_or_string.constantize : klass_or_string
25
+ ::HQ::GraphQL.types.detect { |t| t.model_klass == klass }&.input_klass ||
26
+ raise(Error, Error::MISSING_TYPE_MSG % { klass: klass.name })
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ module HQ
2
+ module GraphQL
3
+ class Mutation < ::GraphQL::Schema::Mutation
4
+ include ::HQ::GraphQL::InputExtensions
5
+
6
+ def self.generate_payload_type
7
+ lazy_load!
8
+ super
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -1,58 +1,25 @@
1
1
  module HQ
2
2
  module GraphQL
3
3
  class Object < ::GraphQL::Schema::Object
4
- class Error < StandardError
5
- MISSING_MODEL_MSG = "Can't perform %{action} without connecting to a model: `::HQ::GraphQL::Object.with_model 'User'`".freeze
6
- MISSING_ATTR_MSG = "Can't find attr %{model}.%{attr}'`".freeze
7
- MISSING_ASSOC_MSG = "Can't find association %{model}.%{assoc}'`".freeze
8
- end
9
-
10
- def self.lazy_load(&block)
11
- @lazy_load ||= []
12
- @lazy_load << block if block
13
- @lazy_load
14
- end
4
+ include Scalars
5
+ include ::HQ::GraphQL::ActiveRecordExtensions
15
6
 
16
7
  def self.with_model(model_name, attributes: true, associations: true)
17
8
  self.model_name = model_name
9
+ self.auto_load_attributes = attributes
10
+ self.auto_load_associations = associations
18
11
 
19
12
  lazy_load do
20
- if attributes
21
- model_klass.columns.reject { |c| removed_attrs.include?(c.name.to_sym) }.each do |column|
22
- field_from_column column
23
- end
13
+ model_columns.each do |column|
14
+ field_from_column(column)
24
15
  end
25
16
 
26
- if associations
27
- model_klass.reflect_on_all_associations.reject { |a| removed_associations.include?(a.name.to_sym) }.each do |association|
28
- field_from_association association
29
- end
17
+ model_associations.each do |association|
18
+ field_from_association association
30
19
  end
31
20
  end
32
21
  end
33
22
 
34
- def self.add_attr(attr)
35
- lazy_load do
36
- validate_model!(:add_attr)
37
- field_from_column model_column(attr)
38
- end
39
- end
40
-
41
- def self.remove_attrs(*attrs)
42
- removed_attrs.concat attrs.map(&:to_sym)
43
- end
44
-
45
- def self.remove_associations(*associations)
46
- removed_associations.concat associations.map(&:to_sym)
47
- end
48
-
49
- def self.add_association(association)
50
- lazy_load do
51
- validate_model!(:add_association)
52
- field_from_association model_association(association)
53
- end
54
- end
55
-
56
23
  def self.to_graphql
57
24
  lazy_load!
58
25
  super
@@ -61,55 +28,22 @@ module HQ
61
28
  class << self
62
29
  private
63
30
 
64
- attr_accessor :model_name
65
- attr_writer :removed_attrs, :removed_associations
66
-
67
- def lazy_load!
68
- lazy_load.map(&:call)
69
- @lazy_load = []
70
- end
71
-
72
- def model_klass
73
- @model_klass ||= model_name&.constantize
74
- end
75
-
76
- def model_column(attr)
77
- model_klass.columns_hash[attr.to_s] || raise(Error, Error::MISSING_ATTR_MSG % { model: model_name, attr: attr })
78
- end
79
-
80
- def model_association(association)
81
- model_klass.reflect_on_association(association) || raise(Error, Error::MISSING_ASSOC_MSG % { model: model_name, assoc: association })
82
- end
83
-
84
- def removed_attrs
85
- @removed_attrs ||= []
86
- end
87
-
88
- def removed_associations
89
- @removed_associations ||= []
90
- end
91
-
92
- def validate_model!(action)
93
- unless model_name
94
- raise Error, Error::MISSING_MODEL_MSG % { action: action }
95
- end
96
- end
97
-
98
31
  def field_from_association(association)
99
- name = association.name
100
32
  type = ::HQ::GraphQL::Types[association.klass]
33
+ name = association.name
101
34
  case association.macro
102
35
  when :has_many
103
36
  field name, [type], null: false
104
37
  else
105
38
  field name, type, null: true
106
39
  end
40
+ rescue ::HQ::GraphQL::Types::Error
41
+ nil
107
42
  end
108
43
 
109
44
  def field_from_column(column)
110
45
  field column.name, ::HQ::GraphQL::Types.type_from_column(column), null: column.null
111
46
  end
112
-
113
47
  end
114
48
 
115
49
  end
@@ -0,0 +1,32 @@
1
+ module HQ
2
+ module GraphQL
3
+ module Resource
4
+ module Mutation
5
+
6
+ def self.build(model_name, graphql_name:, require_primary_key: false, &block)
7
+ Class.new(::HQ::GraphQL::Mutation) do
8
+ graphql_name graphql_name
9
+
10
+ lazy_load do
11
+ field :errors, [String], null: false
12
+ field :resource, ::HQ::GraphQL::Types[model_name], null: true
13
+ end
14
+
15
+ instance_eval(&block)
16
+
17
+ if require_primary_key
18
+ lazy_load do
19
+ klass = model_name.constantize
20
+ primary_key = klass.primary_key
21
+ pk_column = klass.columns.detect { |c| c.name == primary_key.to_s }
22
+
23
+ argument primary_key, ::HQ::GraphQL::Types.type_from_column(pk_column), required: true
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end