graphql_model_mapper 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,8 +4,8 @@ module GraphqlModelMapper
4
4
  resolver: nil)
5
5
 
6
6
 
7
- input_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_key: :update, type_sub_key: :input_type)
8
- output_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_key: :update, type_sub_key: :output_type)
7
+ input_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_sub_key: :input_type)
8
+ output_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_sub_key: :output_type)
9
9
 
10
10
  self.get_mutation(name, description, "Update", resolver, input_type, output_type, name.downcase, "item")
11
11
  end
@@ -15,21 +15,22 @@ module GraphqlModelMapper
15
15
  arguments: [],
16
16
  scope_methods: [])
17
17
 
18
- input_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_key: :delete, type_sub_key: :input_type)
19
- output_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_key: :delete, type_sub_key: :output_type).to_list_type
18
+ input_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_sub_key: :input_type)
19
+ output_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_sub_key: :output_type).to_list_type
20
20
  self.get_delete_mutation(name, description, "Delete", resolver, arguments, scope_methods, input_type, output_type)
21
21
  end
22
22
 
23
23
  def self.graphql_create(name: "", description:"",
24
24
  resolver: nil)
25
25
 
26
- input_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_key: :create, type_sub_key: :input_type)
27
- output_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_key: :create, type_sub_key: :output_type)
26
+ input_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_sub_key: :input_type)
27
+ output_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_sub_key: :output_type)
28
28
 
29
29
  self.get_mutation(name, description, "Create", resolver, input_type, output_type, name.downcase, "item")
30
30
  end
31
31
 
32
32
  def self.get_mutation(name, description, operation_name, resolver, input_type, output_type, input_name, output_name)
33
+ model = name.classify.constantize
33
34
  mutation_type_name = GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(name)}#{operation_name}")
34
35
  return GraphqlModelMapper.get_constant(mutation_type_name) if GraphqlModelMapper.defined_constant?(mutation_type_name)
35
36
  mutation_type = GraphQL::Relay::Mutation.define do
@@ -38,7 +39,7 @@ module GraphqlModelMapper
38
39
  input_field input_name.to_sym, -> {input_type}
39
40
  return_field output_name.to_sym, -> {output_type}
40
41
 
41
- resolve resolver
42
+ resolve GraphqlModelMapper::Mutation.get_resolver(resolver, model, operation_name.downcase.to_sym)
42
43
  end
43
44
 
44
45
  GraphqlModelMapper.set_constant(mutation_type_name, mutation_type.field)
@@ -50,33 +51,50 @@ module GraphqlModelMapper
50
51
  return GraphqlModelMapper.get_constant(query_type_name) if GraphqlModelMapper.defined_constant?(query_type_name)
51
52
 
52
53
  model = name.classify.constantize
53
-
54
- default_arguments = self.get_default_select_arguments(model, scope_methods)
55
- select_input_type_name = GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(name)}SelectInput")
54
+ default_arguments = arguments ? (arguments.length > 0 ? arguments : self.get_default_select_arguments(model, scope_methods)) : []
55
+
56
+ select_input_type_name = GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(name)}SelectInput")
56
57
  if GraphqlModelMapper.defined_constant?(select_input_type_name)
57
- query_input_object_type = GraphqlModelMapper.get_constant(select_input_type_name)
58
+ query_input_object_type = GraphqlModelMapper.get_constant(select_input_type_name)
58
59
  else
59
- query_input_object_type = GraphQL::InputObjectType.define do
60
- name select_input_type_name
61
- default_arguments.each do |k|
62
- argument k[:name].to_sym, k[:type], k[:description], default_value: k[:default]
63
- end
64
- end
65
- GraphqlModelMapper.set_constant(select_input_type_name, query_input_object_type)
60
+ query_input_object_type = GraphQL::InputObjectType.define do
61
+ name select_input_type_name
62
+ default_arguments.each do |k|
63
+ argument k[:name].to_sym, k[:type], k[:description], default_value: k[:default] do
64
+ if k[:authorization] && GraphqlModelMapper.use_authorize
65
+ authorized ->(ctx, model_name, access_type) { GraphqlModelMapper.authorized?(ctx, model_name, access_type.to_sym) }
66
+ model_name name
67
+ access_type k[:authorization]
68
+ end
69
+ end
70
+ end
71
+ end
72
+ GraphqlModelMapper.set_constant(select_input_type_name, query_input_object_type)
66
73
  end
67
74
 
75
+ total_result_type_name = GraphqlModelMapper.get_type_case("TotalResult")
76
+ if GraphqlModelMapper.defined_constant?(total_result_type_name)
77
+ total_result_type = GraphqlModelMapper.get_constant(total_result_type_name)
78
+ else
79
+ total_result_type = GraphQL::InterfaceType.define do
80
+ name total_result_type_name
81
+ field :total, -> {GraphQL::INT_TYPE} do
82
+ resolve -> (obj, args, ctx) {
83
+ obj.items.length
84
+ }
85
+ end
86
+ end
87
+ GraphqlModelMapper.set_constant(total_result_type_name, total_result_type)
88
+ end
89
+
68
90
 
69
91
  ret_type = GraphQL::Relay::Mutation.define do
70
92
  name query_type_name
71
- #return_field :item, output_object_type
72
93
  return_field :items, output_type
73
- return_field :total, -> {GraphQL::INT_TYPE}
74
-
75
- #description description
76
- #input_field "input".to_sym, -> {input_object_type}
94
+ return_interfaces [total_result_type]
77
95
  input_field :select, -> {!query_input_object_type}
78
-
79
- resolve resolver
96
+
97
+ resolve GraphqlModelMapper::Mutation.get_resolver(resolver, model, :delete)
80
98
  end
81
99
  GraphqlModelMapper.set_constant(query_type_name, ret_type.field)
82
100
  GraphqlModelMapper.get_constant(query_type_name)
@@ -84,19 +102,25 @@ module GraphqlModelMapper
84
102
 
85
103
  def self.get_default_select_arguments(model, scope_methods)
86
104
  default_arguments = [
87
- {:name=>:explain, :type=>GraphQL::BOOLEAN_TYPE, :default=>nil},
88
- {:name=>:id, :type=>GraphQL::INT_TYPE, :default=>nil},
89
- {:name=>:ids, :type=>GraphQL::INT_TYPE.to_list_type, :default=>nil},
90
- {:name=>:limit, :type=>GraphQL::INT_TYPE, :default=>50},
91
- {:name=>:offset, :type=>GraphQL::INT_TYPE, :default=>nil},
92
- {:name=>:order, :type=>GraphQL::STRING_TYPE, :default=>nil},
93
- {:name=>:where, :type=>GraphQL::STRING_TYPE.to_list_type, :default=>nil}
105
+ {:name=>:id, :type=>GraphQL::ID_TYPE, :default=>nil},
106
+ {:name=>:ids, :type=>GraphQL::ID_TYPE.to_list_type, :default=>nil},
94
107
  ]
108
+
109
+ default_arguments = default_arguments + [
110
+ {:name=>:item_id, :type=>GraphQL::INT_TYPE, :default=>nil},
111
+ {:name=>:item_ids, :type=>GraphQL::INT_TYPE.to_list_type, :default=>nil}
112
+ ] if GraphqlModelMapper::MapperType.get_type_params(model.name, type_sub_key: :input_type)[:primary_keys]
95
113
 
114
+ default_arguments = default_arguments + [
115
+ {:name=>:explain, :type=>GraphQL::BOOLEAN_TYPE, :default=>nil, :authorization=>:manage},
116
+ {:name=>:order, :type=>GraphQL::STRING_TYPE, :default=>nil, :authorization=>:manage},
117
+ {:name=>:where, :type=>GraphQL::STRING_TYPE.to_list_type, :default=>nil, :authorization=>:manage }
118
+ ]
119
+
96
120
  scope_methods = scope_methods.map(&:to_sym)
97
121
  #.select{|m| model.method(m.to_sym).arity == 0}
98
122
  if (model.public_methods - model.instance_methods - Object.methods - ActiveRecord::Base.methods).include?(:with_deleted)
99
- default_arguments << {:name=>:with_deleted, :type=>GraphQL::BOOLEAN_TYPE, :default=>false}
123
+ default_arguments << {:name=>:with_deleted, :type=>GraphQL::BOOLEAN_TYPE, :default=>false, :authorization=>:manage}
100
124
  end
101
125
  allowed_scope_methods = []
102
126
  if scope_methods.count > 0
@@ -116,10 +140,29 @@ module GraphqlModelMapper
116
140
  end
117
141
  GraphqlModelMapper.set_constant typename, enum_type
118
142
  end
119
- default_arguments << {:name=>:scope, :type=>GraphqlModelMapper.get_constant(typename), :default=>nil}
143
+ default_arguments << {:name=>:scope, :type=>GraphqlModelMapper.get_constant(typename), :default=>nil, :authorization=>:manage}
120
144
  end
121
145
  end
122
146
  default_arguments
123
- end
147
+ end
148
+
149
+ def self.get_resolver(resolver, model, operation)
150
+ if model.public_methods.include?("graphql_#{operation}_resolver".to_sym)
151
+ case operation
152
+ when :create
153
+ resolver = -> (obj, args, ctx) {model.graphql_create_resolver(obj,args,ctx) } if model.public_methods.include?(:graphql_create_resolver)
154
+ when :delete
155
+ resolver = -> (obj, args, ctx) {model.graphql_delete_resolver(obj,args,ctx) } if model.public_methods.include?(:graphql_delete_resolver)
156
+ when :update
157
+ resolver = -> (obj, args, ctx) {model.graphql_update_resolver(obj,args,ctx) } if model.public_methods.include?(:graphql_update_resolver)
158
+ end
159
+ end
160
+ if GraphqlModelMapper.mutation_resolve_wrapper && GraphqlModelMapper.mutation_resolve_wrapper < GraphqlModelMapper::Resolve::ResolveWrapper
161
+ return GraphqlModelMapper.mutation_resolve_wrapper.new(resolver)
162
+ else
163
+ return GraphqlModelMapper::Resolve::ResolveWrapper.new(resolver)
164
+ end
165
+ end
166
+
124
167
  end
125
168
  end
@@ -7,26 +7,39 @@ module GraphqlModelMapper
7
7
  scope_methods: []
8
8
  )
9
9
 
10
- input_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_key: :query, type_sub_key: :input_type)
11
- output_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_key: :query, type_sub_key: :output_type)
10
+ input_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_sub_key: :input_type)
11
+ output_type = GraphqlModelMapper::MapperType.get_ar_object_with_params(name, type_sub_key: :output_type)
12
12
  self.get_query(name, description, "Query", resolver, arguments, scope_methods, input_type, output_type)
13
13
  end
14
14
 
15
15
  def self.get_default_select_arguments(model, scope_methods)
16
16
  default_arguments = [
17
- {:name=>:explain, :type=>GraphQL::BOOLEAN_TYPE, :default=>nil},
18
- {:name=>:id, :type=>GraphQL::INT_TYPE, :default=>nil},
19
- {:name=>:ids, :type=>GraphQL::INT_TYPE.to_list_type, :default=>nil},
20
- {:name=>:limit, :type=>GraphQL::INT_TYPE, :default=>50},
21
- {:name=>:offset, :type=>GraphQL::INT_TYPE, :default=>nil},
22
- {:name=>:order, :type=>GraphQL::STRING_TYPE, :default=>nil},
23
- {:name=>:where, :type=>GraphQL::STRING_TYPE.to_list_type, :default=>nil}
17
+ {:name=>:id, :type=>GraphQL::ID_TYPE, :default=>nil},
18
+ {:name=>:ids, :type=>GraphQL::ID_TYPE.to_list_type, :default=>nil},
24
19
  ]
25
-
20
+
21
+ default_arguments = default_arguments + [
22
+ {:name=>:item_id, :type=>GraphQL::INT_TYPE, :default=>nil},
23
+ {:name=>:item_ids, :type=>GraphQL::INT_TYPE.to_list_type, :default=>nil}
24
+ ] if GraphqlModelMapper::MapperType.get_type_params(model.name, type_sub_key: :input_type)[:primary_keys]
25
+
26
+
27
+ default_arguments = default_arguments + [
28
+ {:name=>:explain, :type=>GraphQL::BOOLEAN_TYPE, :default=>nil, :authorization=>:manage},
29
+ {:name=>:order, :type=>GraphQL::STRING_TYPE, :default=>nil, :authorization=>:manage},
30
+ {:name=>:where, :type=>GraphQL::STRING_TYPE.to_list_type, :default=>nil, :authorization=>:manage }
31
+ ]
32
+ =begin
33
+ default_arguments = default_arguments + [
34
+ # {:name=>:limit, :type=>GraphQL::INT_TYPE, :default=>GraphqlModelMapper.max_page_size},
35
+ {:name=>:first, :type=>GraphQL::INT_TYPE, :default=>nil},
36
+ {:name=>:last, :type=>GraphQL::INT_TYPE, :default=>nil},
37
+ {:name=>:offset, :type=>GraphQL::INT_TYPE, :default=>nil}] if ![:deep, :shallow].include?(GraphqlModelMapper.nesting_strategy)
38
+ =end
26
39
  scope_methods = scope_methods.map(&:to_sym)
27
40
  #.select{|m| model.method(m.to_sym).arity == 0}
28
41
  if (model.public_methods - model.instance_methods - Object.methods - ActiveRecord::Base.methods).include?(:with_deleted)
29
- default_arguments << {:name=>:with_deleted, :type=>GraphQL::BOOLEAN_TYPE, :default=>false}
42
+ default_arguments << {:name=>:with_deleted, :type=>GraphQL::BOOLEAN_TYPE, :default=>false, :authorization=>:manage}
30
43
  end
31
44
  allowed_scope_methods = []
32
45
  if scope_methods.count > 0
@@ -46,7 +59,7 @@ module GraphqlModelMapper
46
59
  end
47
60
  GraphqlModelMapper.set_constant typename, enum_type
48
61
  end
49
- default_arguments << {:name=>:scope, :type=>GraphqlModelMapper.get_constant(typename), :default=>nil}
62
+ default_arguments << {:name=>:scope, :type=>GraphqlModelMapper.get_constant(typename), :default=>nil, :authorization=>:manage}
50
63
  end
51
64
  end
52
65
  default_arguments
@@ -59,8 +72,12 @@ module GraphqlModelMapper
59
72
 
60
73
  model = name.classify.constantize
61
74
 
62
- default_arguments = self.get_default_select_arguments(model, scope_methods)
75
+ default_arguments = arguments ? (arguments.length > 0 ? arguments : self.get_default_select_arguments(model, scope_methods)) : []
76
+
77
+
78
+ =begin
63
79
  select_input_type_name = "#{GraphqlModelMapper.get_type_case(GraphqlModelMapper.get_type_name(name))}QueryInput"
80
+
64
81
  if GraphqlModelMapper.defined_constant?(select_input_type_name)
65
82
  select_input_type = GraphqlModelMapper.get_constant(select_input_type_name)
66
83
  else
@@ -72,7 +89,43 @@ module GraphqlModelMapper
72
89
  end
73
90
  GraphqlModelMapper.set_constant(select_input_type_name, select_input_type)
74
91
  end
75
-
92
+ =end
93
+ =begin
94
+ page_info_type_name = "FlatPageInfo"
95
+
96
+ if GraphqlModelMapper.defined_constant?(page_info_type_name)
97
+ page_info_type = GraphqlModelMapper.get_constant(page_info_type_name)
98
+ else
99
+ page_info_type = GraphQL::ObjectType.define do
100
+ name("FlatPageInfo")
101
+ description("Information about pagination in a query.")
102
+ field :hasNextPage, !types.Boolean, "When paginating forwards, are there more items?", property: :has_next_page
103
+ field :hasPreviousPage, !types.Boolean, "When paginating backwards, are there more items?", property: :has_previous_page
104
+ field :startCursor, types.String, "When paginating backwards, the cursor to continue.", property: :start_cursor
105
+ field :endCursor, types.String, "When paginating forwards, the cursor to continue.", property: :end_cursor
106
+ end
107
+ GraphqlModelMapper.set_constant(page_info_type_name, page_info_type)
108
+ end
109
+ =end
110
+
111
+ if [:deep, :shallow].include?(GraphqlModelMapper.nesting_strategy)
112
+ connection_type_name = "#{GraphqlModelMapper.get_type_case(GraphqlModelMapper.get_type_name(name))}ConnectionType"
113
+ if GraphqlModelMapper.defined_constant?(connection_type_name)
114
+ connection_type = GraphqlModelMapper.get_constant(connection_type_name)
115
+ else
116
+ connection_type = output_type.define_connection do
117
+ name connection_type_name
118
+ field :total, hash_key: :total do
119
+ type types.Int
120
+ resolve ->(obj, args, ctx) {
121
+ obj.nodes.count
122
+ }
123
+ end
124
+ end
125
+ GraphqlModelMapper.set_constant(connection_type_name, connection_type)
126
+ end
127
+ end
128
+
76
129
  total_output_type_name = "#{GraphqlModelMapper.get_type_name(name)}QueryPayload"
77
130
  if GraphqlModelMapper.defined_constant?(total_output_type_name)
78
131
  total_output_type = GraphqlModelMapper.get_constant(total_output_type_name)
@@ -80,11 +133,46 @@ module GraphqlModelMapper
80
133
  total_output_type = GraphQL::ObjectType.define do
81
134
  name total_output_type_name
82
135
  if [:deep, :shallow].include?(GraphqlModelMapper.nesting_strategy)
83
- connection :items, -> {output_type.connection_type}, hash_key: :items
136
+ connection :items, -> {connection_type}, max_page_size: GraphqlModelMapper.max_page_size do
137
+ resolve -> (obj, args, ctx) {
138
+ limit = GraphqlModelMapper.max_page_size
139
+ raise GraphQL::ExecutionError.new("you have exceeded the maximum requested page size #{limit}") if args[:first].to_i > limit || args[:last].to_i > limit
140
+
141
+ obj
142
+ }
143
+ end
84
144
  else
85
- field :items, -> {output_type.to_list_type}, hash_key: :items
145
+ field :items, -> {output_type.to_list_type}, hash_key: :items do
146
+ argument :per_page, GraphQL::INT_TYPE
147
+ argument :page, GraphQL::INT_TYPE
148
+ resolve->(obj, args, ctx){
149
+ first_rec = nil
150
+ last_rec = nil
151
+ limit = GraphqlModelMapper.max_page_size.to_i
152
+
153
+ if args[:per_page]
154
+ per_page = args[:per_page].to_i
155
+ raise GraphQL::ExecutionError.new("per_page must be greater than 0") if per_page < 1
156
+ raise GraphQL::ExecutionError.new("you have exceeded the maximum requested page size #{limit}") if per_page > limit
157
+ limit = [per_page,limit].min
158
+ end
159
+ if args[:page]
160
+ page = args[:page].to_i
161
+ raise GraphQL::ExecutionError.new("page must be greater than 0") if page < 1
162
+ max_page = (obj.count/per_page).floor + 1
163
+ raise GraphQL::ExecutionError.new("you requested page #{page} which is greater than the max number of pages #{max_page}") if page > max_page
164
+ obj = obj.offset((page-1)*limit)
165
+ end
166
+ obj = obj.limit(limit)
167
+ obj
168
+ }
169
+ end
170
+ field :total, -> {GraphQL::INT_TYPE}, hash_key: :total do
171
+ resolve->(obj,args, ctx){
172
+ obj.limit(nil).count
173
+ }
174
+ end
86
175
  end
87
- field :total, -> {GraphQL::INT_TYPE}, hash_key: :total
88
176
  end
89
177
  GraphqlModelMapper.set_constant(total_output_type_name, total_output_type)
90
178
  end
@@ -93,15 +181,31 @@ module GraphqlModelMapper
93
181
  ret_type = GraphQL::Field.define do
94
182
  name query_type_name
95
183
  type total_output_type
96
- #argument :select, -> {!select_input_type}
97
184
  default_arguments.each do |k|
98
- argument k[:name].to_sym, k[:type], k[:description], default_value: k[:default]
185
+ argument k[:name].to_sym, k[:type], k[:description], default_value: k[:default] do
186
+ if k[:authorization] && GraphqlModelMapper.use_authorize
187
+ authorized ->(ctx, model_name, access_type) { GraphqlModelMapper.authorized?(ctx, model_name, access_type.to_sym) }
188
+ model_name name
189
+ access_type k[:authorization]
190
+ end
191
+ end
99
192
  end
100
-
101
- resolve resolver
102
- end
193
+ resolve GraphqlModelMapper::Query.get_resolver(resolver, model)
194
+ end
103
195
  GraphqlModelMapper.set_constant(query_type_name, ret_type)
104
196
  GraphqlModelMapper.get_constant(query_type_name)
105
197
  end
198
+
199
+
200
+ def self.get_resolver(resolver, model)
201
+
202
+ resolver = -> (obj,args,ctx){ model.graphql_query_resolver(obj, args, ctx) } if model.public_methods.include?(:graphql_query_resolver)
203
+
204
+ if GraphqlModelMapper.query_resolve_wrapper && GraphqlModelMapper.query_resolve_wrapper < GraphqlModelMapper::Resolve::ResolveWrapper
205
+ return GraphqlModelMapper.query_resolve_wrapper.new(resolver)
206
+ else
207
+ return GraphqlModelMapper::Resolve::ResolveWrapper.new(resolver)
208
+ end
209
+ end
106
210
  end
107
211
  end
@@ -1,239 +1,285 @@
1
1
  module GraphqlModelMapper
2
- module Resolve
3
- def self.query_resolver(obj, args, ctx, name)
4
- obj_context = name.classify.constantize
5
- select_args = args[:select] || args
6
-
7
- if !GraphqlModelMapper.authorized?(ctx, obj_context.name, :query)
8
- raise GraphQL::ExecutionError.new("error: unauthorized access: #{:query} '#{obj_context.class_name.classify}'")
9
- end
10
- classmethods = []
11
- scope_allowed = false
12
- with_deleted_allowed = false
13
- if select_args[:scope]
14
- classmethods = obj_context.methods - Object.methods
15
- scope_allowed = classmethods.include?(select_args[:scope].to_sym)
16
- raise GraphQL::ExecutionError.new("error: invalid scope '#{select_args[:scope]}' specified, '#{select_args[:scope]}' method does not exist on '#{ctx.field.name.classify}'") unless scope_allowed
17
- end
18
- if select_args[:with_deleted]
19
- classmethods = obj_context.methods - Object.methods
20
- with_deleted_allowed = classmethods.include?(:with_deleted)
21
- raise GraphQL::ExecutionError.new("error: invalid usage of 'with_deleted', 'with_deleted' method does not exist on '#{ctx.field.name.classify}'") unless with_deleted_allowed
22
- end
23
-
24
- implied_includes = self.get_implied_includes(name.classify.constantize, ctx.ast_node)
2
+ module Resolve
3
+ def self.query_resolver(obj, args, ctx, name)
4
+ #binding.pry
5
+ obj_context = obj || name.classify.constantize
6
+ select_args = args[:select] || args
7
+
8
+ if !GraphqlModelMapper.authorized?(ctx, obj_context.name, :query)
9
+ raise GraphQL::ExecutionError.new("error: unauthorized access: #{:query} '#{obj_context.class_name.classify}'")
10
+ end
11
+ classmethods = []
12
+ scope_allowed = false
13
+ with_deleted_allowed = false
14
+ if select_args[:scope]
15
+ classmethods = obj_context.methods - Object.methods
16
+ scope_allowed = classmethods.include?(select_args[:scope].to_sym)
17
+ raise GraphQL::ExecutionError.new("error: invalid scope '#{select_args[:scope]}' specified, '#{select_args[:scope]}' method does not exist on '#{obj_context.class_name.classify}'") unless scope_allowed
18
+ end
19
+ if select_args[:with_deleted]
20
+ classmethods = obj_context.methods - Object.methods
21
+ with_deleted_allowed = classmethods.include?(:with_deleted)
22
+ raise GraphQL::ExecutionError.new("error: invalid usage of 'with_deleted', 'with_deleted' method does not exist on '#{obj_context.class_name.classify}'") unless with_deleted_allowed
23
+ end
25
24
 
26
- if !implied_includes.empty?
27
- obj_context = obj_context.includes(implied_includes)
28
- if Rails.version.split(".").first.to_i > 3
29
- obj_context = obj_context.references(implied_includes)
30
- end
31
- end
32
- if select_args[:ids]
33
- obj_context = obj_context.where(["#{obj_context.model_name.plural}.id in (?)", select_args[:ids]])
34
- end
35
- if select_args[:id]
36
- obj_context = obj_context.where(["#{obj_context.model_name.plural}.id = ?", select_args[:id].to_i])
37
- end
38
- if select_args[:where]
39
- obj_context = obj_context.where(select_args[:where])
40
- end
41
- if with_deleted_allowed
42
- obj_context = obj_context.with_deleted
43
- end
44
- if scope_allowed
45
- obj_context = obj_context.send(select_args[:scope].to_sym)
46
- end
47
- if !select_args[:limit].nil? && select_args[:limit].to_f > 0
48
- obj_context = obj_context.limit(select_args[:limit])
49
- end
50
- if select_args[:offset]
51
- obj_context = obj_context.offset(select_args[:offset])
52
- end
53
- if select_args[:order]
54
- obj_context = obj_context.order(select_args[:order])
55
- end
56
- if select_args[:explain]
57
- obj_context = obj_context.eager_load(implied_includes)
58
- raise GraphQL::ExecutionError.new(obj_context.explain.split("\n").first.sub("EXPLAIN for: ", ""))
59
- end
60
- obj_context
25
+ implied_includes = self.get_implied_includes(obj_context.name.classify.constantize, ctx.ast_node)
26
+ if !implied_includes.empty?
27
+ obj_context = obj_context.includes(implied_includes)
28
+ if Rails.version.split(".").first.to_i > 3
29
+ obj_context = obj_context.references(implied_includes)
30
+ end
61
31
  end
32
+ if select_args[:id]
62
33
 
63
- def self.update_resolver(obj, inputs, ctx, name)
64
- item = GraphqlModelMapper::Resolve.nested_update(ctx, name, inputs)
65
- item
34
+ type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(select_args[:id])
35
+ raise GraphQL::ExecutionError.new("incorrect global id: unable to resolve type for id:'#{select_args[:id]}'") if type_name.nil?
36
+ model_name = GraphqlModelMapper.get_constant(type_name.upcase).metadata[:model_name].to_s.classify
37
+ raise GraphQL::ExecutionError.new("incorrect global id '#{select_args[:id]}': expected global id for '#{name}', received global id for '#{model_name}'") if model_name != name
38
+ obj_context = obj_context.where(["#{obj_context.model_name.plural}.id = ?", item_id.to_i])
66
39
  end
67
-
68
- def self.delete_resolver(obj, inputs, ctx, model_name)
69
- model = model_name.classify.constantize
70
- items = self.query_resolver(obj, inputs, ctx, model_name)
71
- ids = items.collect(&:id)
72
- if !GraphqlModelMapper.authorized?(ctx, model_name, :update)
73
- raise GraphQL::ExecutionError.new("error: unauthorized access: delete '#{model_name.classify}', transaction cancelled")
74
- end
75
- begin
76
- deleted_items = model.delete(ids)
77
- rescue => e
78
- raise e #GraphQL::ExecutionError.new("error: delete")
40
+ if select_args[:ids]
41
+ finder_array = []
42
+ errors = []
43
+ select_args[:ids].each do |id|
44
+ type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
45
+ if type_name.nil?
46
+ errors << "incorrect global id: unable to resolve type for id:'#{id}'"
47
+ next
79
48
  end
80
- if model.methods.include?(:with_deleted)
81
- items.with_deleted
82
- else
83
- items
49
+ model_name = GraphqlModelMapper.get_constant(type_name.upcase).metadata[:model_name].to_s.classify
50
+ if model_name != name
51
+ errors << "incorrect global id '#{id}': expected global id for '#{name}', received global id for '#{model_name}'"
52
+ next
84
53
  end
54
+ finder_array << item_id.to_i
55
+ end
56
+ if errors.length > 0
57
+ raise GraphQL::ExecutionError.new(errors.join(";"))
58
+ end
59
+ obj_context = obj_context.where(["`#{obj_context.model_name.plural}`.id in (?)", finder_array])
60
+ end
61
+ if select_args[:item_ids]
62
+ obj_context = obj_context.where(["`#{obj_context.model_name.plural}`.id in (?)", select_args[:item_ids]])
63
+ end
64
+ if select_args[:item_id]
65
+ obj_context = obj_context.where(["`#{obj_context.model_name.plural}`.id = ?", select_args[:item_id].to_i])
66
+ end
67
+ if select_args[:where]
68
+ obj_context = obj_context.where(select_args[:where])
69
+ end
70
+ if with_deleted_allowed
71
+ obj_context = obj_context.with_deleted
72
+ end
73
+ if scope_allowed
74
+ obj_context = obj_context.send(select_args[:scope].to_sym)
85
75
  end
76
+ if select_args[:order]
77
+ obj_context = obj_context.order(select_args[:order])
78
+ end
79
+ if select_args[:explain]
80
+ obj_context = obj_context.limit(1)
81
+ obj_context = obj_context.eager_load(implied_includes)
82
+ raise GraphQL::ExecutionError.new(obj_context.explain.split("\n").first.sub("EXPLAIN for: ", "").sub(" LIMIT 1", !select_args[:limit].nil? && select_args[:limit].to_f > 0 ? "LIMIT #{select_args[:limit]}" : ""))
83
+ end
84
+ #if select_args[:limit].nil?
85
+ # obj_context = obj_context.limit(100)
86
+ #end
87
+ obj_context = obj_context.where("1=1")
88
+ obj_context
89
+ end
86
90
 
87
- def self.create_resolver(obj, inputs, ctx, model_name)
88
- if !GraphqlModelMapper.authorized?(ctx, model_name, :create)
89
- raise GraphQL::ExecutionError.new("error: unauthorized access: create '#{model_name.classify}'")
90
- end
91
- model = model_name.classify.constantize
92
- item = model.new(inputs[model_name.downcase].to_h)
93
- begin
94
- if !item.valid?
95
- raise GraphQL::ExecutionError.new(item.errors.full_messages.join("; "))
96
- else
97
- raise GraphQL::ExecutionError.new("error: WIP, item not saved but is a valid '#{model_name.classify}'")
98
- #item.save!
99
- end
100
- end
101
- item
91
+ def self.update_resolver(obj, inputs, ctx, name)
92
+ item = GraphqlModelMapper::Resolve.nested_update(ctx, name, inputs)
93
+ item
94
+ end
95
+
96
+ def self.delete_resolver(obj, inputs, ctx, model_name)
97
+ model = model_name.classify.constantize
98
+ items = self.query_resolver(obj, inputs, ctx, model_name)
99
+ ids = items.collect(&:id)
100
+ if !GraphqlModelMapper.authorized?(ctx, model_name, :update)
101
+ raise GraphQL::ExecutionError.new("error: unauthorized access: delete '#{model_name.classify}', transaction cancelled")
102
+ end
103
+ begin
104
+ deleted_items = model.delete(ids)
105
+ rescue => e
106
+ raise e #GraphQL::ExecutionError.new("error: delete")
107
+ end
108
+ if model.methods.include?(:with_deleted)
109
+ items.with_deleted
110
+ else
111
+ items
102
112
  end
113
+ end
103
114
 
104
- def self.using_relay_pagination?(selection)
105
- selection.name == 'edges'
115
+ def self.create_resolver(obj, inputs, ctx, model_name)
116
+ if !GraphqlModelMapper.authorized?(ctx, model_name, :create)
117
+ raise GraphQL::ExecutionError.new("error: unauthorized access: create '#{model_name.classify}'")
106
118
  end
107
-
108
- def self.using_is_items_collection?(selection)
109
- selection.name == 'items'
119
+ model = model_name.classify.constantize
120
+ item = model.new(inputs[model_name.downcase].to_h)
121
+ begin
122
+ if !item.valid?
123
+ raise GraphQL::ExecutionError.new(item.errors.full_messages.join("; "))
124
+ else
125
+ raise GraphQL::ExecutionError.new("error: WIP, item not saved but is a valid '#{model_name.classify}'")
126
+ #item.save!
127
+ end
110
128
  end
129
+ item
130
+ end
131
+
132
+ def self.using_relay_pagination?(selection)
133
+ selection.name == 'edges'
134
+ end
135
+
136
+ def self.using_is_items_collection?(selection)
137
+ selection.name == 'items'
138
+ end
139
+
140
+ def self.using_nodes_pagination?(selection)
141
+ selection.name == 'nodes'
142
+ end
143
+
144
+ def self.has_reflection_with_name?(class_name, selection_name)
145
+ class_name.reflect_on_all_associations.select{|m|m.name == selection_name.to_sym}.present?
146
+ end
147
+
148
+ def self.map_relay_pagination_depencies(class_name, selection, dependencies)
149
+ node_selection = selection.selections.find { |sel| sel.name == 'node' }
150
+
151
+ if node_selection.present?
152
+ get_implied_includes(class_name, node_selection, dependencies)
153
+ else
154
+ dependencies
155
+ end
156
+ end
157
+
158
+ def self.get_implied_includes(class_name, ast_node, dependencies={})
159
+ ast_node.selections.each do |selection|
160
+ name = selection.name
111
161
 
112
- def self.using_nodes_pagination?(selection)
113
- selection.name == 'nodes'
162
+ if using_relay_pagination?(selection)
163
+ map_relay_pagination_depencies(class_name, selection, dependencies)
164
+ next
114
165
  end
115
166
 
116
- def self.has_reflection_with_name?(class_name, selection_name)
117
- class_name.reflect_on_all_associations.select{|m|m.name == selection_name.to_sym}.present?
167
+ if using_nodes_pagination?(selection)
168
+ get_implied_includes(class_name, selection, dependencies)
169
+ next
118
170
  end
119
171
 
120
- def self.map_relay_pagination_depencies(class_name, selection, dependencies)
121
- node_selection = selection.selections.find { |sel| sel.name == 'node' }
122
-
123
- if node_selection.present?
124
- get_implied_includes(class_name, node_selection, dependencies)
125
- else
126
- dependencies
127
- end
172
+ if using_is_items_collection?(selection)
173
+ get_implied_includes(class_name, selection, dependencies)
174
+ next
128
175
  end
129
176
 
130
- def self.get_implied_includes(class_name, ast_node, dependencies={})
131
- ast_node.selections.each do |selection|
132
- name = selection.name
133
-
134
- if using_relay_pagination?(selection)
135
- map_relay_pagination_depencies(class_name, selection, dependencies)
136
- next
137
- end
138
-
139
- if using_nodes_pagination?(selection)
140
- get_implied_includes(class_name, selection, dependencies)
141
- next
177
+ if has_reflection_with_name?(class_name, name)
178
+ begin
179
+ current_class_name = selection.name.singularize.classify.constantize
180
+ dependencies[name] = get_implied_includes(current_class_name, selection)
181
+ rescue NameError
182
+ selection_name = class_name.reflections.with_indifferent_access[selection.name].class_name
183
+ begin
184
+ current_class_name = selection_name.singularize.classify.constantize
185
+ dependencies[selection.name.to_sym] = get_implied_includes(current_class_name, selection)
186
+ rescue
187
+ # this will occur if the relation is polymorphic, since polymorphic associations do not have a class_name
188
+ GraphqlModelMapper.logger.info "implied_includes: #{class_name} could not resolve a class for relation #{selection.name}"
142
189
  end
190
+ next
191
+ end
192
+ end
193
+ end
194
+ dependencies
195
+ end
143
196
 
144
- if using_is_items_collection?(selection)
145
- get_implied_includes(class_name, selection, dependencies)
146
- next
147
- end
148
-
149
- if has_reflection_with_name?(class_name, name)
150
- begin
151
- current_class_name = selection.name.singularize.classify.constantize
152
- dependencies[name] = get_implied_includes(current_class_name, selection)
153
- rescue NameError
154
- selection_name = class_name.reflections.with_indifferent_access[selection.name].options[:class_name]
155
- current_class_name = selection_name.singularize.classify.constantize
156
- dependencies[selection.name.to_sym] = get_implied_includes(current_class_name, selection)
157
- next
158
- end
197
+
198
+ def self.nested_update(ctx, model_name, inputs, child_name=nil, child_id=nil, parent_name=nil, parent_id=nil, klass_name=nil)
199
+ model = model_name.classify.constantize
200
+
201
+ if !child_name.nil? && !child_id.nil? # has_many && has_one
202
+ inputs_root = inputs
203
+ #puts "inputs_root[:item_id] #{inputs_root[:item_id]} #{inputs_root}"
204
+ if model.public_methods.include?(:with_deleted)
205
+ item = model.with_deleted.where("id = ? and #{child_name.downcase}_id = ?", inputs_root[:item_id], child_id).first
206
+ else
207
+ item = model.where("id = ? and #{child_name.downcase}_id = ?", inputs_root[:item_id], child_id).first
208
+ end
209
+ raise GraphQL::ExecutionError.new("error: #{model.name} record not found for #{model.name}.id = #{inputs_root[:item_id]} and #{model.name}.#{child_name.downcase}_id = #{child_id}") if item.nil?
210
+ elsif !parent_name.nil? && !parent_id.nil? # belongs_to
211
+ inputs_root = inputs
212
+ #puts "parent_id #{parent_id} parent_name #{parent_name} #{model_name} model.with_deleted.find(#{parent_id}).send(#{parent_name}.to_sym).id} inputs_root[:item_id] #{inputs_root[:item_id]} #{inputs_root}"
213
+ if model.public_methods.include?(:with_deleted)
214
+ item = model.with_deleted.find(parent_id).public_send(parent_name.to_sym) if model.with_deleted.find(parent_id).public_send(parent_name.to_sym) && model.with_deleted.find(parent_id).public_send(parent_name.to_sym).id == inputs_root[:item_id]
215
+ else
216
+ item = model.find(parent_id).public_send(parent_name.to_sym) if model.find(parent_id).public_send(parent_name.to_sym) && model.with_deleted.find(parent_id).public_send(parent_name.to_sym).id == inputs_root[:item_id]
217
+ end
218
+ raise GraphQL::ExecutionError.new("error: #{model.name}.#{parent_name} record not found for #{model.name}.with_deleted.find(#{parent_id}).#{parent_name}_id = #{inputs_root[:item_id]}") if item.nil?
219
+ model_name = klass_name
220
+ model = klass_name.classify.constantize
221
+ else #root query always single record, need to offeset property for object_input_type
222
+ inputs_root = inputs[model_name.downcase]
223
+ #puts "inputs_root[:item_id] #{inputs_root[:item_id]} #{inputs_root}"
224
+ if model.public_methods.include?(:with_deleted)
225
+ item = model.with_deleted.find(inputs_root[:item_id])
226
+ else
227
+ item = model.find(inputs_root[:item_id])
228
+ end
229
+ raise GraphQL::ExecutionError.new("error: #{model.name} record not found for #{model.name}.id=#{inputs[model_name.downcase][:item_id]}") if item.nil?
230
+ end
231
+ if !GraphqlModelMapper.authorized?(ctx, model.name, :update)
232
+ raise GraphQL::ExecutionError.new("error: unauthorized access: #{:update} '#{model}', transaction cancelled")
233
+ end
234
+
235
+ item_associations = model.reflect_on_all_associations.select{|t| begin t.klass rescue next end}.select{|t| !t.options[:polymorphic]}
236
+ item_association_names = item_associations.map{|m| m.name.to_s}
237
+ input_association_names = item_association_names & inputs_root.to_h.keys
238
+
239
+ item.transaction do
240
+ #puts "***********item.update_attributes(#{inputs_root.to_h.except('id').except!(*item_association_names)})"
241
+ #puts "***********ctx[current_user.to_sym].is_admin?(#{ctx[:current_user].is_admin?})"
242
+ item.update_attributes(inputs_root.to_h.except('id').except('item_id').except!(*item_association_names))
243
+ input_association_names.each do |ia|
244
+ lclinput = inputs_root[ia]
245
+ ass = item_associations.select{|a| a.name.to_s == ia}.first
246
+ klass = ass.klass
247
+ is_collection = ass.collection?
248
+ belongs_to = ass.belongs_to?
249
+ #puts "#{ass.name} #{ass.collection?} #{ass.belongs_to?}"
250
+ #puts "#{ass.association_foreign_key} #{ass.association_primary_key} #{ass.active_record_primary_key}"
251
+
252
+ if is_collection
253
+ #puts "is_collection"
254
+ lclinput.each do |i|
255
+ #puts "#{klass.name} #{i.to_h} #{model_name.downcase} #{inputs_root[:item_id]}"
256
+ GraphqlModelMapper::Resolve.nested_update(ctx, klass.name, i, model_name.downcase, inputs_root[:item_id])
159
257
  end
258
+ elsif !is_collection && belongs_to
259
+ #puts "belongs_to"
260
+ #puts "self.nested_update(#{ctx}, #{model.name}, #{lclinput.to_h}, nil, nil, #{ass.name}, #{inputs_root[:item_id]}, #{klass.name})"
261
+ GraphqlModelMapper::Resolve.nested_update(ctx, model.name, lclinput, nil, nil, ass.name, inputs_root[:item_id], klass.name)
262
+ elsif !is_collection && !belongs_to #has_one
263
+ #puts "has_one"
264
+ #puts "self.nested_update(#{ctx}, #{klass.name}, #{lclinput.to_h}, #{model_name.downcase}, #{inputs_root[:item_id]})"
265
+ GraphqlModelMapper::Resolve.nested_update(ctx, model.name, lclinput, nil, nil, ass.name, inputs_root[:item_id], klass.name)
160
266
  end
161
- dependencies
162
267
  end
268
+ end
269
+ item
270
+ end
163
271
 
164
-
165
- def self.nested_update(ctx, model_name, inputs, child_name=nil, child_id=nil, parent_name=nil, parent_id=nil, klass_name=nil)
166
- model = model_name.classify.constantize
167
-
168
- if !child_name.nil? && !child_id.nil? # has_many && has_one
169
- inputs_root = inputs
170
- #puts "inputs_root[:id] #{inputs_root[:id]} #{inputs_root}"
171
- if model.public_methods.include?(:with_deleted)
172
- item = model.with_deleted.where("id = ? and #{child_name.downcase}_id = ?", inputs_root[:id], child_id).first
173
- else
174
- item = model.where("id = ? and #{child_name.downcase}_id = ?", inputs_root[:id], child_id).first
175
- end
176
- raise GraphQL::ExecutionError.new("error: #{model.name} record not found for #{model.name}.id = #{inputs_root[:id]} and #{model.name}.#{child_name.downcase}_id = #{child_id}") if item.nil?
177
- elsif !parent_name.nil? && !parent_id.nil? # belongs_to
178
- inputs_root = inputs
179
- #puts "parent_id #{parent_id} parent_name #{parent_name} #{model_name} model.with_deleted.find(#{parent_id}).send(#{parent_name}.to_sym).id} inputs_root[:id] #{inputs_root[:id]} #{inputs_root}"
180
- if model.public_methods.include?(:with_deleted)
181
- item = model.with_deleted.find(parent_id).public_send(parent_name.to_sym) if model.with_deleted.find(parent_id).public_send(parent_name.to_sym) && model.with_deleted.find(parent_id).public_send(parent_name.to_sym).id == inputs_root[:id]
182
- else
183
- item = model.find(parent_id).public_send(parent_name.to_sym) if model.find(parent_id).public_send(parent_name.to_sym) && model.with_deleted.find(parent_id).public_send(parent_name.to_sym).id == inputs_root[:id]
184
- end
185
- raise GraphQL::ExecutionError.new("error: #{model.name}.#{parent_name} record not found for #{model.name}.with_deleted.find(#{parent_id}).#{parent_name}_id = #{inputs_root[:id]}") if item.nil?
186
- model_name = klass_name
187
- model = klass_name.classify.constantize
188
- else #root query always single record, need to offeset property for object_input_type
189
- inputs_root = inputs[model_name.downcase]
190
- #puts "inputs_root[:id] #{inputs_root[:id]} #{inputs_root}"
191
- if model.public_methods.include?(:with_deleted)
192
- item = model.with_deleted.find(inputs_root[:id])
193
- else
194
- item = model.find(inputs_root[:id])
195
- end
196
- raise GraphQL::ExecutionError.new("error: #{model.name} record not found for #{model.name}.id=#{inputs[model_name.downcase][:id]}") if item.nil?
197
- end
198
- if !GraphqlModelMapper.authorized?(ctx, model.name, :update)
199
- raise GraphQL::ExecutionError.new("error: unauthorized access: #{:update} '#{model}', transaction cancelled")
200
- end
272
+ class ResolveWrapper
273
+ def initialize(resolve_func)
274
+ @resolve_func = resolve_func
275
+ end
276
+
277
+ def call(obj, args, ctx)
278
+ begin
279
+ @resolve_func.call(obj, args, ctx)
201
280
 
202
- item_associations = model.reflect_on_all_associations.select{|t| begin t.klass rescue next end}.select{|t| !t.options[:polymorphic]}
203
- item_association_names = item_associations.map{|m| m.name.to_s}
204
- input_association_names = item_association_names & inputs_root.to_h.keys
205
-
206
- item.transaction do
207
- #puts "***********item.update_attributes(#{inputs_root.to_h.except('id').except!(*item_association_names)})"
208
- #puts "***********ctx[current_user.to_sym].is_admin?(#{ctx[:current_user].is_admin?})"
209
- item.update_attributes(inputs_root.to_h.except('id').except!(*item_association_names))
210
- input_association_names.each do |ia|
211
- lclinput = inputs_root[ia]
212
- ass = item_associations.select{|a| a.name.to_s == ia}.first
213
- klass = ass.klass
214
- is_collection = ass.collection?
215
- belongs_to = ass.belongs_to?
216
- #puts "#{ass.name} #{ass.collection?} #{ass.belongs_to?}"
217
- #puts "#{ass.association_foreign_key} #{ass.association_primary_key} #{ass.active_record_primary_key}"
218
-
219
- if is_collection
220
- #puts "is_collection"
221
- lclinput.each do |i|
222
- #puts "#{klass.name} #{i.to_h} #{model_name.downcase} #{inputs_root[:id]}"
223
- GraphqlModelMapper::Resolve.nested_update(ctx, klass.name, i, model_name.downcase, inputs_root[:id])
224
- end
225
- elsif !is_collection && belongs_to
226
- #puts "belongs_to"
227
- #puts "self.nested_update(#{ctx}, #{model.name}, #{lclinput.to_h}, nil, nil, #{ass.name}, #{inputs_root[:id]}, #{klass.name})"
228
- GraphqlModelMapper::Resolve.nested_update(ctx, model.name, lclinput, nil, nil, ass.name, inputs_root[:id], klass.name)
229
- elsif !is_collection && !belongs_to #has_one
230
- #puts "has_one"
231
- #puts "self.nested_update(#{ctx}, #{klass.name}, #{lclinput.to_h}, #{model_name.downcase}, #{inputs_root[:id]})"
232
- GraphqlModelMapper::Resolve.nested_update(ctx, model.name, lclinput, nil, nil, ass.name, inputs_root[:id], klass.name)
233
- end
234
- end
235
- end
236
- item
237
- end
281
+ end
282
+ end
238
283
  end
284
+ end
239
285
  end