autographql 0.0.2 → 0.0.3
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 +4 -4
- data/lib/autographql.rb +2 -1
- data/lib/autographql/autographql.rb +41 -29
- data/lib/autographql/object_type.rb +4 -57
- data/lib/autographql/query_builder.rb +38 -0
- data/lib/autographql/type_builder.rb +125 -0
- data/lib/autographql/types/date.rb +16 -0
- data/lib/autographql/types/decimal.rb +17 -0
- data/lib/autographql/types/json.rb +16 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a344ec93114c212ee1fd878531ebac02deb094b
|
4
|
+
data.tar.gz: f4ada4a255109550cb3f35a56ec7197419a0b2e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f786b1fee44164b78d3528fd43d36c982c59988022b160a1a7e6e3a851bb1c8c33ac878347a5ab0d158d7bfc5e03a799db078180cb1f016142a8135e32f3b67
|
7
|
+
data.tar.gz: 9e55f2c354eb9093eb338f7c7ad3e8cba4b33915ade4d5f9948f78fece6ee9d637e78a8a5fc7a627e04cf1c91ab143589910504d90d14b2e88c854370b2f6c0c
|
data/lib/autographql.rb
CHANGED
@@ -1,49 +1,61 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require_relative 'object_type'
|
1
|
+
require_relative 'query_builder'
|
4
2
|
|
5
3
|
|
6
4
|
module AutoGraphQL
|
7
5
|
extend self
|
8
6
|
|
9
|
-
@@models =
|
7
|
+
@@models = {}
|
10
8
|
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
super unless [ :QueryType, :ObjectTypes ].include? const
|
10
|
+
def register model, options = {}
|
11
|
+
# sanitize options
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
13
|
+
name = options.fetch(:name, model.name)
|
14
|
+
name.gsub! /:/, '_'
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
exclude = options.fetch(:exclude, []).map(&:to_sym)
|
17
|
+
|
18
|
+
# add `id' column by default
|
19
|
+
fields = [ :id ]
|
24
20
|
|
25
|
-
|
26
|
-
|
21
|
+
# either use user specified fields or default to all
|
22
|
+
fields += options.fetch(:fields) do
|
23
|
+
res = model.columns_hash.keys
|
27
24
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
# add relationships
|
26
|
+
res += [ :has_one, :has_many, :belongs_to ].map do |type|
|
27
|
+
model.reflect_on_all_associations(type).map &:name
|
28
|
+
end.flatten
|
29
|
+
end.map(&:to_sym) - exclude
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
model_methods = options.fetch(:methods, {})
|
32
|
+
# ensure methods actually exist
|
33
|
+
model_methods.each do |name, type|
|
34
|
+
unless model.method_defined? name
|
35
|
+
raise NoMethodError.new(
|
36
|
+
"undefined method `#{name}' for #{model}"
|
37
|
+
)
|
37
38
|
end
|
38
39
|
end
|
39
|
-
end
|
40
40
|
|
41
|
+
@@models[model] = {
|
42
|
+
name: name,
|
43
|
+
description: options.fetch(:description, ''),
|
44
|
+
fields: fields,
|
45
|
+
methods: model_methods,
|
46
|
+
}
|
47
|
+
end
|
41
48
|
|
42
|
-
## private
|
43
49
|
|
44
|
-
|
45
|
-
|
50
|
+
# dynamically generate ::QueryType
|
51
|
+
def const_missing const
|
52
|
+
case const
|
53
|
+
when :QueryType
|
54
|
+
AutoGraphQL::QueryBuilder.build @@models
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
46
58
|
end
|
47
|
-
|
59
|
+
|
48
60
|
|
49
61
|
end
|
@@ -6,65 +6,12 @@ module AutoGraphQL
|
|
6
6
|
module ObjectType
|
7
7
|
protected
|
8
8
|
|
9
|
-
def graphql
|
10
|
-
|
11
|
-
|
12
|
-
# has_many = reflect_on_all_associations(:has_many)
|
9
|
+
def graphql options = {}
|
10
|
+
# register Active Record model. delay schema generation
|
11
|
+
# until class and associations have been fully defined
|
13
12
|
|
14
|
-
|
15
|
-
fields = Set.new(
|
16
|
-
(fields.empty? ? columns.keys : fields).map(&:to_sym)
|
17
|
-
)
|
18
|
-
|
19
|
-
# remove blacklisted fields
|
20
|
-
fields -= exclude.map(&:to_sym)
|
21
|
-
|
22
|
-
# remove relationships for now
|
23
|
-
fields -= belongs_to.map(&:association_foreign_key).map(&:to_sym)
|
24
|
-
# exclude += belongs_to.map(&:association_primary_key).map(&:to_sym)
|
25
|
-
|
26
|
-
|
27
|
-
gql_obj = GraphQL::ObjectType.define do
|
28
|
-
name name
|
29
|
-
description description
|
30
|
-
|
31
|
-
fields.each do |f|
|
32
|
-
type = AutoGraphQL::ObjectType.send :convert_type, columns[f].type
|
33
|
-
field f, type
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# redefine method to return result
|
38
|
-
define_singleton_method(:graphql) do
|
39
|
-
gql_obj
|
40
|
-
end
|
41
|
-
|
42
|
-
# make publically available
|
43
|
-
singleton_class.send :public, :graphql
|
44
|
-
|
45
|
-
# register new GraphQL::ObjectType
|
46
|
-
AutoGraphQL.send :register, self
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
## private
|
51
|
-
|
52
|
-
def self.convert_type type
|
53
|
-
# convert Active Record type to GraphQL type
|
54
|
-
case type
|
55
|
-
when :boolean
|
56
|
-
GraphQL::BOOLEAN_TYPE
|
57
|
-
when :integer
|
58
|
-
GraphQL::INT_TYPE
|
59
|
-
when :float
|
60
|
-
GraphQL::FLOAT_TYPE
|
61
|
-
when :string
|
62
|
-
GraphQL::STRING_TYPE
|
63
|
-
else
|
64
|
-
raise TypeError.new "unsupported type: #{type}"
|
65
|
-
end
|
13
|
+
AutoGraphQL.register self, options
|
66
14
|
end
|
67
|
-
private_class_method :convert_type
|
68
15
|
|
69
16
|
end
|
70
17
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'graphql'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
require_relative 'type_builder.rb'
|
5
|
+
|
6
|
+
|
7
|
+
module AutoGraphQL
|
8
|
+
module QueryBuilder
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def build models_and_opts
|
13
|
+
# dynamically create a QueryType subclass
|
14
|
+
Class.new GraphQL::Schema::Object do
|
15
|
+
def self.name
|
16
|
+
'AutoGraphQL::QueryType'
|
17
|
+
end
|
18
|
+
|
19
|
+
type_map = AutoGraphQL::TypeBuilder.build(models_and_opts)
|
20
|
+
|
21
|
+
type_map.each do |model, type|
|
22
|
+
# define field for this type
|
23
|
+
field type.name.downcase, type, null: true do
|
24
|
+
argument :id, GraphQL::Types::ID, required: true
|
25
|
+
end
|
26
|
+
|
27
|
+
# create loader
|
28
|
+
define_method(type.name.downcase) do |id:|
|
29
|
+
model.find id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'graphql'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
require_relative 'types/date'
|
5
|
+
require_relative 'types/decimal'
|
6
|
+
require_relative 'types/json'
|
7
|
+
|
8
|
+
|
9
|
+
module AutoGraphQL
|
10
|
+
module TypeBuilder
|
11
|
+
extend self
|
12
|
+
|
13
|
+
|
14
|
+
def build models_and_opts
|
15
|
+
# first build all objects
|
16
|
+
type_map = {}
|
17
|
+
|
18
|
+
models_and_opts.each do |model, opts|
|
19
|
+
type_map[model] = build_type model, opts
|
20
|
+
end
|
21
|
+
|
22
|
+
models_and_opts.each do |model, opts|
|
23
|
+
build_type_methods type_map[model], opts[:methods], type_map
|
24
|
+
end
|
25
|
+
|
26
|
+
# build relationships between objects
|
27
|
+
type_map.each do |model, type|
|
28
|
+
relate type, models_and_opts[model][:fields], type_map
|
29
|
+
end
|
30
|
+
|
31
|
+
type_map
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def build_type model, opts
|
38
|
+
column_types = Hash[model.columns_hash.map do |name, column|
|
39
|
+
next nil unless opts[:fields].include? name.to_sym
|
40
|
+
[ name.to_sym, convert_type(model, column) ]
|
41
|
+
end.compact]
|
42
|
+
|
43
|
+
# create type
|
44
|
+
GraphQL::ObjectType.define do
|
45
|
+
name opts[:name]
|
46
|
+
description opts[:description]
|
47
|
+
|
48
|
+
opts[:fields].each do |f|
|
49
|
+
# skip relationships
|
50
|
+
next unless column_types[f]
|
51
|
+
|
52
|
+
field f, column_types[f]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def build_type_methods gql_type, methods, type_map
|
59
|
+
methods.each do |name, type|
|
60
|
+
name = name.to_s
|
61
|
+
|
62
|
+
if type_map.include? type
|
63
|
+
type = type_map[type]
|
64
|
+
end
|
65
|
+
|
66
|
+
field = GraphQL::Field.define do
|
67
|
+
name name
|
68
|
+
type type
|
69
|
+
end
|
70
|
+
|
71
|
+
gql_type.fields[name] = field
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def relate type, fields, type_map
|
77
|
+
model = type_map.key type
|
78
|
+
|
79
|
+
belongs_to = model.reflect_on_all_associations(:belongs_to)
|
80
|
+
has_one = model.reflect_on_all_associations(:has_one)
|
81
|
+
has_many = model.reflect_on_all_associations(:has_many)
|
82
|
+
|
83
|
+
(belongs_to + has_one + has_many).each do |field|
|
84
|
+
next unless fields.include? field.name.to_sym
|
85
|
+
next unless type_map[field.klass]
|
86
|
+
|
87
|
+
field_type = type_map[field.klass]
|
88
|
+
if has_many.include? field
|
89
|
+
# make into a list
|
90
|
+
field_type = field_type.to_list_type.to_non_null_type
|
91
|
+
end
|
92
|
+
|
93
|
+
# create relationship field
|
94
|
+
gql_field = GraphQL::Field.define do
|
95
|
+
name field.name.to_s
|
96
|
+
type field_type
|
97
|
+
end
|
98
|
+
|
99
|
+
type.fields[field.name.to_s] = gql_field
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
# convert Active Record type to GraphQL type
|
105
|
+
def convert_type model, column
|
106
|
+
{
|
107
|
+
boolean: GraphQL::BOOLEAN_TYPE,
|
108
|
+
date: GraphQL::Types::DATE,
|
109
|
+
datetime: GraphQL::Types::ISO8601DateTime,
|
110
|
+
decimal: GraphQL::Types::DECIMAL,
|
111
|
+
float: GraphQL::FLOAT_TYPE,
|
112
|
+
integer: GraphQL::INT_TYPE,
|
113
|
+
json: GraphQL::Types::JSON,
|
114
|
+
string: GraphQL::STRING_TYPE,
|
115
|
+
text: GraphQL::STRING_TYPE,
|
116
|
+
}.fetch column.type do
|
117
|
+
raise TypeError.new(
|
118
|
+
"unsupported type: '#{column.type}' for #{model.name}::#{column.name}"
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
GraphQL::Types::DATE = GraphQL::ScalarType.define do
|
2
|
+
name 'DATE'
|
3
|
+
description 'Date formatted as YYYY-MM-DD (ISO 8601)'
|
4
|
+
|
5
|
+
coerce_result ->(value, ctx) do
|
6
|
+
value.to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
coerce_input ->(value, ctx) do
|
10
|
+
begin
|
11
|
+
Date.parse(value)
|
12
|
+
rescue ArgumentError => e
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
GraphQL::Types::DECIMAL = GraphQL::ScalarType.define do
|
2
|
+
name 'DECIMAL'
|
3
|
+
|
4
|
+
coerce_result ->(value, ctx) do
|
5
|
+
value.to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
coerce_input ->(value, ctx) do
|
9
|
+
begin
|
10
|
+
BigDecimal(value)
|
11
|
+
rescue TypeError => e
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: include precision
|
@@ -0,0 +1,16 @@
|
|
1
|
+
GraphQL::Types::JSON = GraphQL::ScalarType.define do
|
2
|
+
name 'JSON'
|
3
|
+
|
4
|
+
coerce_result ->(value, ctx) do
|
5
|
+
# TODO: handle nil case, which doesn't call coerce
|
6
|
+
JSON.dump value
|
7
|
+
end
|
8
|
+
|
9
|
+
coerce_input ->(value, ctx) do
|
10
|
+
begin
|
11
|
+
JSON.parse value
|
12
|
+
rescue JSON::ParserError => e
|
13
|
+
raise GraphQL::CoercionError, e.message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: autographql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Pepper
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -103,6 +103,11 @@ files:
|
|
103
103
|
- lib/autographql.rb
|
104
104
|
- lib/autographql/autographql.rb
|
105
105
|
- lib/autographql/object_type.rb
|
106
|
+
- lib/autographql/query_builder.rb
|
107
|
+
- lib/autographql/type_builder.rb
|
108
|
+
- lib/autographql/types/date.rb
|
109
|
+
- lib/autographql/types/decimal.rb
|
110
|
+
- lib/autographql/types/json.rb
|
106
111
|
- test/test_auto.rb
|
107
112
|
- test/test_basic_graphql.rb
|
108
113
|
- test/test_models.rb
|