graphql_model_mapper 0.0.6 → 0.1.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.
@@ -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