graphql_model_mapper 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +5 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +21 -0
- data/README.md +416 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/graphql_model_mapper.gemspec +37 -0
- data/lib/graphql_model_mapper/mapper_type.rb +340 -0
- data/lib/graphql_model_mapper/mutation.rb +125 -0
- data/lib/graphql_model_mapper/query.rb +107 -0
- data/lib/graphql_model_mapper/railtie.rb +8 -0
- data/lib/graphql_model_mapper/resolve.rb +221 -0
- data/lib/graphql_model_mapper/schema.rb +110 -0
- data/lib/graphql_model_mapper/schema_types.rb +0 -0
- data/lib/graphql_model_mapper/utility.rb +0 -0
- data/lib/graphql_model_mapper/version.rb +3 -0
- data/lib/graphql_model_mapper.rb +228 -0
- metadata +122 -0
@@ -0,0 +1,221 @@
|
|
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
|
+
implied_includes = GraphqlModelMapper::Resolve.get_implied_includes(obj_context, GraphqlModelMapper::Resolve.get_include_fields(ctx))
|
24
|
+
if !implied_includes.empty?
|
25
|
+
obj_context = obj_context.includes(implied_includes)
|
26
|
+
if Rails.version.split(".").first.to_i > 3
|
27
|
+
obj_context = obj_context.references(implied_includes)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
if select_args[:ids]
|
31
|
+
obj_context = obj_context.where(["#{obj_context.model_name.plural}.id in (?)", select_args[:ids]])
|
32
|
+
end
|
33
|
+
if select_args[:id]
|
34
|
+
obj_context = obj_context.where(["#{obj_context.model_name.plural}.id = ?", select_args[:id].to_i])
|
35
|
+
end
|
36
|
+
if select_args[:where]
|
37
|
+
obj_context = obj_context.where(select_args[:where])
|
38
|
+
end
|
39
|
+
if with_deleted_allowed
|
40
|
+
obj_context = obj_context.with_deleted
|
41
|
+
end
|
42
|
+
if scope_allowed
|
43
|
+
obj_context = obj_context.send(select_args[:scope].to_sym)
|
44
|
+
end
|
45
|
+
if !select_args[:limit].nil? && select_args[:limit].to_f > 0
|
46
|
+
obj_context = obj_context.limit(select_args[:limit])
|
47
|
+
end
|
48
|
+
if select_args[:offset]
|
49
|
+
obj_context = obj_context.offset(select_args[:offset])
|
50
|
+
end
|
51
|
+
if select_args[:order]
|
52
|
+
obj_context = obj_context.order(select_args[:order])
|
53
|
+
end
|
54
|
+
if select_args[:explain]
|
55
|
+
obj_context = obj_context.eager_load(implied_includes)
|
56
|
+
raise GraphQL::ExecutionError.new(obj_context.explain.split("\n").first.sub("EXPLAIN for: ", ""))
|
57
|
+
end
|
58
|
+
obj_context
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.update_resolver(obj, inputs, ctx, name)
|
62
|
+
item = GraphqlModelMapper::Resolve.nested_update(ctx, name, inputs)
|
63
|
+
item
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.delete_resolver(obj, inputs, ctx, model_name)
|
67
|
+
model = model_name.classify.constantize
|
68
|
+
items = self.query_resolver(obj, inputs, ctx, model_name)
|
69
|
+
ids = items.collect(&:id)
|
70
|
+
if !GraphqlModelMapper.authorized?(ctx, model_name, :update)
|
71
|
+
raise GraphQL::ExecutionError.new("error: unauthorized access: delete '#{model_name.classify}', transaction cancelled")
|
72
|
+
end
|
73
|
+
begin
|
74
|
+
deleted_items = model.delete(ids)
|
75
|
+
rescue => e
|
76
|
+
raise e #GraphQL::ExecutionError.new("error: delete")
|
77
|
+
end
|
78
|
+
if model.methods.include?(:with_deleted)
|
79
|
+
items.with_deleted
|
80
|
+
else
|
81
|
+
items
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.create_resolver(obj, inputs, ctx, model_name)
|
86
|
+
if !GraphqlModelMapper.authorized?(ctx, model_name, :create)
|
87
|
+
raise GraphQL::ExecutionError.new("error: unauthorized access: create '#{model_name.classify}'")
|
88
|
+
end
|
89
|
+
model = model_name.classify.constantize
|
90
|
+
item = model.new(inputs[model_name.downcase].to_h)
|
91
|
+
begin
|
92
|
+
if !item.valid?
|
93
|
+
raise GraphQL::ExecutionError.new(item.errors.full_messages.join("; "))
|
94
|
+
else
|
95
|
+
raise GraphQL::ExecutionError.new("error: WIP, item not saved but is a valid '#{model_name.classify}'")
|
96
|
+
#item.save!
|
97
|
+
end
|
98
|
+
end
|
99
|
+
item
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
# build includes list for associated tables in use in the query, skips [:nodes, :edges] and result entries while walking references
|
104
|
+
def self.get_implied_includes(model, field_names=nil, first=true, org_field_names=nil, resolve_fields=false)
|
105
|
+
if first
|
106
|
+
org_field_names = field_names
|
107
|
+
# associations fields that are on the model
|
108
|
+
a = field_names.select{|m| model.reflect_on_all_associations.map(&:name).include?(m[:name].to_sym)}.select{|m| field_names.map{|m| m[:parent_line]}.include?(m[:line])}
|
109
|
+
# base field names that have no parent, get the lowest number parent_line on the associated field names
|
110
|
+
a = a.select{|o| o[:parent_line] == a.map{|v| v[:parent_line]}.sort.first}
|
111
|
+
else
|
112
|
+
a = field_names
|
113
|
+
end
|
114
|
+
final_out = []
|
115
|
+
a.each do |b|
|
116
|
+
out = []
|
117
|
+
child_relations = org_field_names.select{|g| g[:parent_line] == b[:line]}
|
118
|
+
if !child_relations.empty?
|
119
|
+
children = GraphqlModelMapper::Resolve.get_implied_includes(nil, child_relations, false, org_field_names, resolve_fields)
|
120
|
+
if children.empty?
|
121
|
+
out << b[:name].to_sym if ![:edges, :node].include?(b[:name].to_sym)
|
122
|
+
else
|
123
|
+
if ![:edges, :node].include?(b[:name].to_sym)
|
124
|
+
out << { b[:name].to_sym => children.flatten }
|
125
|
+
else
|
126
|
+
out = children.flatten
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
if resolve_fields && out.empty?
|
131
|
+
out << b[:name].to_sym
|
132
|
+
end
|
133
|
+
final_out << out if !out.empty?
|
134
|
+
end
|
135
|
+
final_out
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.get_include_fields(ctx)
|
139
|
+
fieldnames = []
|
140
|
+
visitor = GraphQL::Language::Visitor.new(ctx.query.document)
|
141
|
+
visitor[GraphQL::Language::Nodes::Field] << ->(node, parent) { fieldnames << {:line=>node.line, :parent_line=>parent.line, :parent=>parent.name, :name=>node.name} }
|
142
|
+
visitor.visit
|
143
|
+
fieldnames
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
def self.nested_update(ctx, model_name, inputs, child_name=nil, child_id=nil, parent_name=nil, parent_id=nil, klass_name=nil)
|
148
|
+
model = model_name.classify.constantize
|
149
|
+
|
150
|
+
if !child_name.nil? && !child_id.nil? # has_many && has_one
|
151
|
+
inputs_root = inputs
|
152
|
+
#puts "inputs_root[:id] #{inputs_root[:id]} #{inputs_root}"
|
153
|
+
if model.public_methods.include?(:with_deleted)
|
154
|
+
item = model.with_deleted.where("id = ? and #{child_name.downcase}_id = ?", inputs_root[:id], child_id).first
|
155
|
+
else
|
156
|
+
item = model.where("id = ? and #{child_name.downcase}_id = ?", inputs_root[:id], child_id).first
|
157
|
+
end
|
158
|
+
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?
|
159
|
+
elsif !parent_name.nil? && !parent_id.nil? # belongs_to
|
160
|
+
inputs_root = inputs
|
161
|
+
#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}"
|
162
|
+
if model.public_methods.include?(:with_deleted)
|
163
|
+
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]
|
164
|
+
else
|
165
|
+
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]
|
166
|
+
end
|
167
|
+
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?
|
168
|
+
model_name = klass_name
|
169
|
+
model = klass_name.classify.constantize
|
170
|
+
else #root query always single record, need to offeset property for object_input_type
|
171
|
+
inputs_root = inputs[model_name.downcase]
|
172
|
+
#puts "inputs_root[:id] #{inputs_root[:id]} #{inputs_root}"
|
173
|
+
if model.public_methods.include?(:with_deleted)
|
174
|
+
item = model.with_deleted.find(inputs_root[:id])
|
175
|
+
else
|
176
|
+
item = model.find(inputs_root[:id])
|
177
|
+
end
|
178
|
+
raise GraphQL::ExecutionError.new("error: #{model.name} record not found for #{model.name}.id=#{inputs[model_name.downcase][:id]}") if item.nil?
|
179
|
+
end
|
180
|
+
if !GraphqlModelMapper.authorized?(ctx, model.name, :update)
|
181
|
+
raise GraphQL::ExecutionError.new("error: unauthorized access: #{:update} '#{model}', transaction cancelled")
|
182
|
+
end
|
183
|
+
|
184
|
+
item_associations = model.reflect_on_all_associations.select{|t| begin t.klass rescue next end}.select{|t| !t.options[:polymorphic]}
|
185
|
+
item_association_names = item_associations.map{|m| m.name.to_s}
|
186
|
+
input_association_names = item_association_names & inputs_root.to_h.keys
|
187
|
+
|
188
|
+
item.transaction do
|
189
|
+
#puts "***********item.update_attributes(#{inputs_root.to_h.except('id').except!(*item_association_names)})"
|
190
|
+
#puts "***********ctx[current_user.to_sym].is_admin?(#{ctx[:current_user].is_admin?})"
|
191
|
+
item.update_attributes(inputs_root.to_h.except('id').except!(*item_association_names))
|
192
|
+
input_association_names.each do |ia|
|
193
|
+
lclinput = inputs_root[ia]
|
194
|
+
ass = item_associations.select{|a| a.name.to_s == ia}.first
|
195
|
+
klass = ass.klass
|
196
|
+
is_collection = ass.collection?
|
197
|
+
belongs_to = ass.belongs_to?
|
198
|
+
#puts "#{ass.name} #{ass.collection?} #{ass.belongs_to?}"
|
199
|
+
#puts "#{ass.association_foreign_key} #{ass.association_primary_key} #{ass.active_record_primary_key}"
|
200
|
+
|
201
|
+
if is_collection
|
202
|
+
#puts "is_collection"
|
203
|
+
lclinput.each do |i|
|
204
|
+
#puts "#{klass.name} #{i.to_h} #{model_name.downcase} #{inputs_root[:id]}"
|
205
|
+
GraphqlModelMapper::Resolve.nested_update(ctx, klass.name, i, model_name.downcase, inputs_root[:id])
|
206
|
+
end
|
207
|
+
elsif !is_collection && belongs_to
|
208
|
+
#puts "belongs_to"
|
209
|
+
#puts "self.nested_update(#{ctx}, #{model.name}, #{lclinput.to_h}, nil, nil, #{ass.name}, #{inputs_root[:id]}, #{klass.name})"
|
210
|
+
GraphqlModelMapper::Resolve.nested_update(ctx, model.name, lclinput, nil, nil, ass.name, inputs_root[:id], klass.name)
|
211
|
+
elsif !is_collection && !belongs_to #has_one
|
212
|
+
#puts "has_one"
|
213
|
+
#puts "self.nested_update(#{ctx}, #{klass.name}, #{lclinput.to_h}, #{model_name.downcase}, #{inputs_root[:id]})"
|
214
|
+
GraphqlModelMapper::Resolve.nested_update(ctx, model.name, lclinput, nil, nil, ass.name, inputs_root[:id], klass.name)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
item
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module GraphqlModelMapper
|
2
|
+
def self.Schema(log_query_depth: false, log_query_complexity: false, use_backtrace: false, use_authorize: false, nesting_strategy: :shallow, type_case: :camelize)
|
3
|
+
|
4
|
+
return GraphqlModelMapper.get_constant("GraphqlModelMapperSchema".upcase) if GraphqlModelMapper.defined_constant?("GraphqlModelMapperSchema".upcase)
|
5
|
+
GraphqlModelMapper.use_authorize = use_authorize
|
6
|
+
GraphqlModelMapper.nesting_strategy = nesting_strategy
|
7
|
+
GraphqlModelMapper.type_case = type_case
|
8
|
+
|
9
|
+
if GraphqlModelMapper.use_authorize
|
10
|
+
metadata_definitions = {
|
11
|
+
authorized: ->(field, authorized_proc) { field.metadata[:authorized_proc] = authorized_proc },
|
12
|
+
model_name: GraphQL::Define.assign_metadata_key(:model_name),
|
13
|
+
access_type: GraphQL::Define.assign_metadata_key(:access_type)
|
14
|
+
}
|
15
|
+
GraphQL::Field.accepts_definitions(metadata_definitions)
|
16
|
+
GraphQL::Argument.accepts_definitions(metadata_definitions)
|
17
|
+
end
|
18
|
+
|
19
|
+
schema = GraphQL::Schema.define do
|
20
|
+
use GraphQL::Backtrace if use_backtrace
|
21
|
+
default_max_page_size 100
|
22
|
+
mutation GraphqlModelMapper.MutationType
|
23
|
+
query GraphqlModelMapper.QueryType
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
schema.query_analyzers << GraphQL::Analysis::QueryDepth.new { |query, depth| Rails.logger.info("[******GraphqlModelMapper Query Depth] #{depth}") } if log_query_depth
|
28
|
+
schema.query_analyzers << GraphQL::Analysis::QueryComplexity.new { |query, complexity| Rails.logger.info("[******GraphqlModelMapper Query Complexity] #{complexity}")} if log_query_complexity
|
29
|
+
|
30
|
+
GraphqlModelMapper.set_constant("GraphqlModelMapperSchema".upcase, schema)
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def self.QueryType
|
36
|
+
return GraphQL::ObjectType.define do
|
37
|
+
name 'Query'
|
38
|
+
# create queries for each AR model object
|
39
|
+
field :welcomeQuery, types.String, hash_key: :welcomeMutation do
|
40
|
+
resolve -> (obj, args, ctx){
|
41
|
+
{
|
42
|
+
welcomeQuery: "this is a placeholder mutation in case you do not have access to other queries"
|
43
|
+
}
|
44
|
+
}
|
45
|
+
end
|
46
|
+
GraphqlModelMapper.schema_queries.each do |f|
|
47
|
+
field f[:name], f[:field] do
|
48
|
+
if GraphqlModelMapper.use_authorize
|
49
|
+
authorized ->(ctx, model_name, access_type) { GraphqlModelMapper.authorized?(ctx, model_name, access_type.to_sym) }
|
50
|
+
model_name f[:model_name]
|
51
|
+
access_type f[:access_type].to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.MutationType
|
59
|
+
return GraphQL::ObjectType.define do
|
60
|
+
name 'Mutation'
|
61
|
+
|
62
|
+
field :welcomeMutation, types.String, hash_key: :welcomeMutation do
|
63
|
+
resolve -> (obj, args, ctx){
|
64
|
+
{
|
65
|
+
welcomeMutation: "this is a placeholder mutation in case you do not have access to other mutations"
|
66
|
+
}
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
GraphqlModelMapper.schema_mutations.each do |f|
|
71
|
+
field f[:name], f[:field] do
|
72
|
+
if GraphqlModelMapper.use_authorize
|
73
|
+
authorized ->(ctx, model_name, access_type) { GraphqlModelMapper.authorized?(ctx, model_name, access_type.to_sym) }
|
74
|
+
model_name f[:model_name]
|
75
|
+
access_type f[:access_type].to_s
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
GraphqlModelMapper::GEOMETRY_TYPE = GraphQL::ScalarType.define do
|
84
|
+
name "Geometry"
|
85
|
+
description "The Geometry scalar type enables the serialization of Geometry data"
|
86
|
+
|
87
|
+
coerce_input ->(value, ctx) do
|
88
|
+
begin
|
89
|
+
value.nil? ? nil : GeoRuby::SimpleFeatures::Geometry.from_geojson(value)
|
90
|
+
rescue ArgumentError
|
91
|
+
raise GraphQL::CoercionError, "cannot coerce `#{value.inspect}` to json"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
coerce_result ->(value, ctx) { value.nil? ? "" : value.to_json }
|
95
|
+
end
|
96
|
+
|
97
|
+
GraphqlModelMapper::DATE_TYPE = GraphQL::ScalarType.define do
|
98
|
+
name "Date"
|
99
|
+
description "The Date scalar type enables the serialization of date data to/from iso8601"
|
100
|
+
|
101
|
+
coerce_input ->(value, ctx) do
|
102
|
+
begin
|
103
|
+
value.nil? ? nil : Date.iso8601(value)
|
104
|
+
rescue ArgumentError
|
105
|
+
raise GraphQL::CoercionError, "cannot coerce `#{value.inspect}` to date"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
coerce_result ->(value, ctx) { value.nil? ? nil : value.iso8601 }
|
109
|
+
end
|
110
|
+
|
File without changes
|
File without changes
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require "graphql"
|
2
|
+
require "graphql_model_mapper/mapper_type"
|
3
|
+
require "graphql_model_mapper/mutation"
|
4
|
+
require "graphql_model_mapper/query"
|
5
|
+
require "graphql_model_mapper/resolve"
|
6
|
+
require "graphql_model_mapper/schema"
|
7
|
+
require "graphql_model_mapper/version"
|
8
|
+
require 'graphql_model_mapper/railtie' if defined?(Rails)
|
9
|
+
|
10
|
+
module GraphqlModelMapper
|
11
|
+
mattr_accessor :type_case
|
12
|
+
mattr_accessor :nesting_strategy
|
13
|
+
mattr_accessor :use_authorize
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_writer :logger
|
17
|
+
|
18
|
+
def logger
|
19
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
20
|
+
log.progname = self.name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
@@type_case = :camelize
|
26
|
+
@@nesting_strategy = :shallow
|
27
|
+
@@use_authorize = false
|
28
|
+
|
29
|
+
def self.included(klazz)
|
30
|
+
klazz.extend GraphqlModelMapper_Macros
|
31
|
+
end
|
32
|
+
|
33
|
+
module GraphqlModelMapper_Macros
|
34
|
+
protected
|
35
|
+
|
36
|
+
def graphql_types(name: self.name, query: {}, update: {}, delete: {}, create: {})
|
37
|
+
define_singleton_method(:graphql_types) do
|
38
|
+
GraphqlModelMapper::MapperType.graphql_types(name: name, query: query, update: update, delete: delete, create: create)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def graphql_update(name: self.name, description:"", resolver: -> (obj, inputs, ctx){
|
43
|
+
GraphqlModelMapper.logger.info "********************update"
|
44
|
+
item = GraphqlModelMapper::Resolve.update_resolver(obj, inputs, ctx, name)
|
45
|
+
{
|
46
|
+
item: item
|
47
|
+
}
|
48
|
+
})
|
49
|
+
define_singleton_method(:graphql_update) do
|
50
|
+
GraphqlModelMapper::Mutation.graphql_update(name: name, description: description, resolver: resolver)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def graphql_delete(name: self.name, description:"", resolver: -> (obj, inputs, ctx){
|
55
|
+
GraphqlModelMapper.logger.info "********************delete"
|
56
|
+
items = GraphqlModelMapper::Resolve.delete_resolver(obj, inputs, ctx, name)
|
57
|
+
{
|
58
|
+
total: items.length,
|
59
|
+
items: items
|
60
|
+
}
|
61
|
+
}, arguments: [], scope_methods: [])
|
62
|
+
define_singleton_method(:graphql_delete) do
|
63
|
+
GraphqlModelMapper::Mutation.graphql_delete(name: name, description: description, resolver: resolver, scope_methods: scope_methods)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def graphql_create(name: self.name, description:"", resolver: -> (obj, args, ctx){
|
68
|
+
GraphqlModelMapper.logger.info "********************create"
|
69
|
+
item = GraphqlModelMapper::Resolve.create_resolver(obj, args, ctx, name)
|
70
|
+
{
|
71
|
+
item: item
|
72
|
+
}
|
73
|
+
})
|
74
|
+
define_singleton_method(:graphql_create) do
|
75
|
+
GraphqlModelMapper::Mutation.graphql_create(name: name, description: description, resolver: resolver)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def graphql_query(name: self.name, description: "", resolver: -> (obj, args, ctx) {
|
80
|
+
items = GraphqlModelMapper::Resolve.query_resolver(obj, args, ctx, name)
|
81
|
+
{
|
82
|
+
items: items,
|
83
|
+
total: items.length
|
84
|
+
}
|
85
|
+
}, arguments: [], scope_methods: [])
|
86
|
+
define_singleton_method(:graphql_query) do
|
87
|
+
GraphqlModelMapper::Query.graphql_query(name: name, description: description, resolver: resolver, scope_methods: scope_methods)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.implementations
|
93
|
+
Rails.application.eager_load!
|
94
|
+
ActiveRecord::Base.descendants.each.select do |clz|
|
95
|
+
begin
|
96
|
+
clz.included_modules.include?(GraphqlModelMapper) && (clz.public_methods.include?(:graphql_query) || clz.public_methods.include?(:graphql_update) || clz.public_methods.include?(:graphql_delete) || clz.public_methods.include?(:graphql_create) || clz.public_methods.include?(:graphql_types))
|
97
|
+
rescue
|
98
|
+
# it is okay that this is empty - just covering the possibility
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.schema_queries
|
104
|
+
fields = []
|
105
|
+
GraphqlModelMapper.implementations.select{|t| t.public_methods.include?(:graphql_query)}.each { |t|
|
106
|
+
fields << { :name =>GraphqlModelMapper.get_type_case(t.name, false).to_sym, :field => t.graphql_query, :model_name=>t.name, :access_type=>:query }
|
107
|
+
}
|
108
|
+
fields
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.schema_mutations
|
112
|
+
fields = []
|
113
|
+
GraphqlModelMapper.implementations.select{|t| t.public_methods.include?(:graphql_create)}.each { |t|
|
114
|
+
fields << {:name => GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(t.name)}Create", false).to_sym, :field=> t.graphql_create, :model_name=>t.name, :access_type=>:create }
|
115
|
+
}
|
116
|
+
GraphqlModelMapper.implementations.select{|t| t.public_methods.include?(:graphql_update)}.each { |t|
|
117
|
+
fields << {:name =>GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(t.name)}Update", false).to_sym, :field=>t.graphql_update, :model_name=>t.name, :access_type=>:update }
|
118
|
+
}
|
119
|
+
GraphqlModelMapper.implementations.select{|t| t.public_methods.include?(:graphql_delete)}.each { |t|
|
120
|
+
fields << {:name =>GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(t.name)}Delete", false).to_sym, :field=>t.graphql_delete, :model_name=>t.name, :access_type=>:delete }
|
121
|
+
}
|
122
|
+
fields
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.select_list(model_name, classes=[])
|
126
|
+
model = model_name.classify.constantize
|
127
|
+
output = []
|
128
|
+
columns = model.columns_hash.keys.map{|m| "#{model.name.underscore.pluralize}.#{m}"}
|
129
|
+
relation_includes = model.reflect_on_all_associations.select{|t| begin t.klass rescue next end}.select{|t| !t.options[:polymorphic]}.map{|m| "#{model.name.underscore.pluralize}.#{m.name}"}
|
130
|
+
relations = model.reflect_on_all_associations.select{|t| begin t.klass rescue next end}.select{|t| !t.options[:polymorphic]}
|
131
|
+
relations.each do |a|
|
132
|
+
if !classes.include?(a.klass.name)
|
133
|
+
classes << a.klass.name
|
134
|
+
output = output + GraphqlModelMapper.select_list(a.klass.name, classes)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
output << relation_includes + columns
|
138
|
+
output.sort
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.authorized?(ctx, model_name, access, roles=nil)
|
142
|
+
model = model_name.classify.constantize
|
143
|
+
access = access.to_sym
|
144
|
+
#here it is checking to see if public methods are exposed on items based on the operation being performed
|
145
|
+
if (access && access == :read) || (access && access == :query)
|
146
|
+
access = :read
|
147
|
+
if !model.public_methods.include?(:graphql_query)
|
148
|
+
return false
|
149
|
+
end
|
150
|
+
elsif access && access == :create
|
151
|
+
if !model.public_methods.include?(:graphql_create)
|
152
|
+
return false
|
153
|
+
end
|
154
|
+
elsif access && access == :update
|
155
|
+
if !model.public_methods.include?(:graphql_update)
|
156
|
+
return false
|
157
|
+
end
|
158
|
+
elsif access && access == :delete
|
159
|
+
if !model.public_methods.include?(:graphql_delete)
|
160
|
+
return false
|
161
|
+
end
|
162
|
+
end
|
163
|
+
if roles && roles.length > 0
|
164
|
+
roles.each do |r|
|
165
|
+
if !ctx[:current_user].hash_role?(role)
|
166
|
+
return false
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
#implementation specific, here it is using an ability method on the user class plugged into cancan
|
171
|
+
if ctx[:current_user].public_methods.include?(:ability)
|
172
|
+
if !ctx[:current_user].ability.can? access, model
|
173
|
+
return false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
true
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.get_type_name(classname, lowercase_first_letter=false)
|
180
|
+
str = "#{classname.classify.demodulize}"
|
181
|
+
if lowercase_first_letter && str.length > 0
|
182
|
+
str = str[0].downcase + str[1..-1]
|
183
|
+
end
|
184
|
+
str
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.get_type_case(str, uppercase=true)
|
188
|
+
if @@type_case == :camelize
|
189
|
+
if uppercase
|
190
|
+
str.to_s.camelize(:upper)
|
191
|
+
else
|
192
|
+
str.to_s.camelize(:lower)
|
193
|
+
end
|
194
|
+
elsif @@type_case == :underscore
|
195
|
+
if uppercase
|
196
|
+
self.underscore(str)
|
197
|
+
else
|
198
|
+
str.underscore
|
199
|
+
end
|
200
|
+
elsif @@type_case == :classify
|
201
|
+
str
|
202
|
+
else
|
203
|
+
str
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.underscore(str, upcase=true)
|
208
|
+
if upcase
|
209
|
+
str.split('_').map {|w| w.capitalize}.join('_')
|
210
|
+
else
|
211
|
+
str.underscore
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.get_constant(type_name)
|
216
|
+
GraphqlModelMapper.const_get(type_name.upcase)
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.set_constant(type_name, type)
|
220
|
+
GraphqlModelMapper.const_set(type_name.upcase, type)
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.defined_constant?(type_name)
|
224
|
+
GraphqlModelMapper.const_defined?(type_name.upcase)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
ActiveRecord::Base.send(:include, GraphqlModelMapper) if defined?(ActiveRecord)
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graphql_model_mapper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gene Black
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: graphql
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.7.5
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.7.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.16'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.0'
|
69
|
+
description: This gem extends ActiveRecord::Base to add automatic generation of GraphQL
|
70
|
+
objects based on your models.
|
71
|
+
email:
|
72
|
+
- geblack@hotmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- ".travis.yml"
|
79
|
+
- Gemfile
|
80
|
+
- Gemfile.lock
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/console
|
85
|
+
- bin/setup
|
86
|
+
- graphql_model_mapper.gemspec
|
87
|
+
- lib/graphql_model_mapper.rb
|
88
|
+
- lib/graphql_model_mapper/mapper_type.rb
|
89
|
+
- lib/graphql_model_mapper/mutation.rb
|
90
|
+
- lib/graphql_model_mapper/query.rb
|
91
|
+
- lib/graphql_model_mapper/railtie.rb
|
92
|
+
- lib/graphql_model_mapper/resolve.rb
|
93
|
+
- lib/graphql_model_mapper/schema.rb
|
94
|
+
- lib/graphql_model_mapper/schema_types.rb
|
95
|
+
- lib/graphql_model_mapper/utility.rb
|
96
|
+
- lib/graphql_model_mapper/version.rb
|
97
|
+
homepage: https://github.com/geneblack/graphql_model_mapper
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
metadata:
|
101
|
+
allowed_push_host: https://rubygems.org
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.6.14
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Adds GraphQL object generation based on your ActiveRecord models.
|
122
|
+
test_files: []
|