autographql 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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