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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b3d3b5ac363dd52787ecb2b37909b9d4b54d3220
4
- data.tar.gz: b99a0e6d238f0fd26d35e306ca85cbb3cbd88845
3
+ metadata.gz: 2a344ec93114c212ee1fd878531ebac02deb094b
4
+ data.tar.gz: f4ada4a255109550cb3f35a56ec7197419a0b2e5
5
5
  SHA512:
6
- metadata.gz: 69216651bc19b711674130fc03bdbb245b14a6326c5d9a7f0406e401d842d9deebdfd9f1a83df53d614249a980b09f988f402493b7ed5f6a8f8464625b9f1b32
7
- data.tar.gz: 24683a0bb35dc5e79e76feaf15cc50a9e448524984bcc86b95690376864f4cdcf2fc69c88230e470b6d0430cf8b83d5d98cc9af867d787ce523f5fba4293edeb
6
+ metadata.gz: 6f786b1fee44164b78d3528fd43d36c982c59988022b160a1a7e6e3a851bb1c8c33ac878347a5ab0d158d7bfc5e03a799db078180cb1f016142a8135e32f3b67
7
+ data.tar.gz: 9e55f2c354eb9093eb338f7c7ad3e8cba4b33915ade4d5f9948f78fece6ee9d637e78a8a5fc7a627e04cf1c91ab143589910504d90d14b2e88c854370b2f6c0c
data/lib/autographql.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  require 'active_record'
2
2
 
3
3
  require_relative 'autographql/autographql'
4
+ require_relative 'autographql/object_type'
4
5
 
5
6
 
6
7
  module AutoGraphQL
7
- VERSION = '0.0.2'
8
+ VERSION = '0.0.3'
8
9
  end
9
10
 
10
11
 
@@ -1,49 +1,61 @@
1
- require 'set'
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 = Set.new
7
+ @@models = {}
10
8
 
11
9
 
12
- # dynamically generate ::QueryType and ::ObjectTypes
13
- def const_missing const
14
- super unless [ :QueryType, :ObjectTypes ].include? const
10
+ def register model, options = {}
11
+ # sanitize options
15
12
 
16
- if :ObjectTypes == const
17
- return @@models.map(&:graphql)
18
- end
13
+ name = options.fetch(:name, model.name)
14
+ name.gsub! /:/, '_'
19
15
 
20
- Class.new GraphQL::Schema::Object do
21
- def self.name
22
- 'AutoGraphQL::QueryType'
23
- end
16
+ exclude = options.fetch(:exclude, []).map(&:to_sym)
17
+
18
+ # add `id' column by default
19
+ fields = [ :id ]
24
20
 
25
- @@models.each do |model|
26
- type = model.graphql
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
- # define field for this type
29
- field type.name.downcase, type, null: true do
30
- argument :id, GraphQL::Types::ID, required: true
31
- end
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
- # create loader
34
- define_method(type.name.downcase) do |id:|
35
- model.find id
36
- end
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
- def register model
45
- @@models << model
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
- private_class_method :register
59
+
48
60
 
49
61
  end
@@ -6,65 +6,12 @@ module AutoGraphQL
6
6
  module ObjectType
7
7
  protected
8
8
 
9
- def graphql name: self.name, description: '', fields: [], exclude: []
10
- columns = Hash[columns_hash.map {|k,v| [k.to_sym, v] }]
11
- belongs_to = reflect_on_all_associations(:belongs_to)
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
- # figure out which active record fields to expose
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.2
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-03 00:00:00.000000000 Z
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