graphoid 0.0.2 → 0.0.4
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/README.md +46 -32
- data/Rakefile +2 -6
- data/lib/graphoid/argument.rb +3 -0
- data/lib/graphoid/config.rb +2 -0
- data/lib/graphoid/definitions/filters.rb +7 -8
- data/lib/graphoid/definitions/inputs.rb +8 -6
- data/lib/graphoid/definitions/orders.rb +5 -5
- data/lib/graphoid/definitions/types.rb +46 -40
- data/lib/graphoid/drivers/active_record.rb +37 -37
- data/lib/graphoid/drivers/mongoid.rb +75 -53
- data/lib/graphoid/graphield.rb +6 -6
- data/lib/graphoid/grapho.rb +2 -0
- data/lib/graphoid/main.rb +6 -4
- data/lib/graphoid/mapper.rb +5 -2
- data/lib/graphoid/mutations/create.rb +12 -14
- data/lib/graphoid/mutations/delete.rb +12 -14
- data/lib/graphoid/mutations/processor.rb +3 -1
- data/lib/graphoid/mutations/structure.rb +2 -0
- data/lib/graphoid/mutations/update.rb +2 -0
- data/lib/graphoid/operators/attribute.rb +6 -5
- data/lib/graphoid/operators/inherited/belongs_to.rb +10 -8
- data/lib/graphoid/operators/inherited/embeds_many.rb +17 -3
- data/lib/graphoid/operators/inherited/embeds_one.rb +17 -3
- data/lib/graphoid/operators/inherited/has_many.rb +4 -2
- data/lib/graphoid/operators/inherited/has_one.rb +9 -7
- data/lib/graphoid/operators/inherited/many_to_many.rb +4 -2
- data/lib/graphoid/operators/relation.rb +8 -9
- data/lib/graphoid/queries/operation.rb +2 -0
- data/lib/graphoid/queries/processor.rb +7 -5
- data/lib/graphoid/queries/queries.rb +16 -17
- data/lib/graphoid/scalars.rb +28 -26
- data/lib/graphoid/utils.rb +6 -4
- data/lib/graphoid/version.rb +3 -1
- data/lib/graphoid.rb +2 -0
- metadata +15 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 991348c4876e83ff75d3416a84f61cb1cd7d12773efb2cdeff71866ca16174ed
|
4
|
+
data.tar.gz: 95337e84c31b8e0feded9ab67fc7291d8e0269590602975c37473015aa19f182
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cdf65fa4c22ac0a01ec2671152886c4335940aeebf729a60bb83fc0445af18902f1c1c9e79cf5bfb0bb097778b38baeac09aa2c1ae2dc8429ea2bc8f8efd3f1
|
7
|
+
data.tar.gz: 9522be43b81979348c9ebfc2a319d62d7fc7343b2c66db8bccc04ee7549fa6fa309aa1b63317d7ccfff0bc81634dddf1a3dc826777cc05f6c5b938202c02c7c0
|
data/README.md
CHANGED
@@ -1,62 +1,76 @@
|
|
1
1
|
# Graphoid
|
2
|
-
This gem is used to generate a full GraphQL
|
2
|
+
This gem is used to generate a full GraphQL API using introspection of Mongoid or ActiveRecord models.
|
3
|
+
After installing it, you will have create, update, delete, and query actions on any rails models you want.
|
3
4
|
|
4
5
|
## Dependency
|
5
6
|
This gem depends on the graphql gem for rails https://github.com/rmosolgo/graphql-ruby
|
6
|
-
So
|
7
|
-
```bash
|
8
|
-
rails generate graphql:install
|
9
|
-
```
|
7
|
+
So please install that gem first before continuing
|
10
8
|
|
11
|
-
##
|
12
|
-
|
9
|
+
## Installation
|
10
|
+
Add this line to your Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'graphoid'
|
14
|
+
```
|
13
15
|
|
14
|
-
|
16
|
+
```bash
|
17
|
+
$ bundle install
|
18
|
+
```
|
15
19
|
|
16
|
-
|
20
|
+
## Configuration
|
21
|
+
Create the file `config/initializers/graphoid.rb`
|
22
|
+
And configure the database you want to use in it.
|
17
23
|
|
18
24
|
```ruby
|
19
25
|
Graphoid.configure do |config|
|
20
26
|
config.driver = :mongoid
|
27
|
+
# or
|
21
28
|
config.driver = :active_record
|
22
29
|
end
|
23
|
-
Graphoid.initialize
|
24
30
|
```
|
25
31
|
|
26
|
-
##
|
27
|
-
|
32
|
+
## Usage
|
33
|
+
You can determine which models will be visible in the API by including the Graphoid Queries and Mutations
|
28
34
|
|
29
35
|
```ruby
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
```bash
|
35
|
-
$ bundle
|
36
|
-
```
|
37
|
-
|
38
|
-
Or install it yourself as:
|
39
|
-
```bash
|
40
|
-
$ gem install graphoid
|
36
|
+
class Person
|
37
|
+
include Graphoid::Queries
|
38
|
+
include Graphoid::Mutations
|
39
|
+
end
|
41
40
|
```
|
42
41
|
|
43
|
-
|
44
|
-
|
42
|
+
You can also include a special concern that will let you create virtual fields and forbid access to existing fields
|
45
43
|
```ruby
|
46
|
-
|
47
|
-
include Graphoid::
|
48
|
-
```
|
44
|
+
class Person
|
45
|
+
include Graphoid::Graphield
|
49
46
|
|
50
|
-
|
47
|
+
graphield :full_name, String # virtual fields need to resolve as a method
|
48
|
+
graphorbid :balance # attribute balance will not be exposed in the API
|
51
49
|
|
52
|
-
|
53
|
-
|
50
|
+
def full_name
|
51
|
+
"#{first_name} #{last_name}"
|
52
|
+
end
|
53
|
+
end
|
54
54
|
```
|
55
55
|
|
56
56
|
## Contributing
|
57
|
-
Figure out the driver
|
58
57
|
Functionality to sort top level models by association values
|
59
58
|
Filter by Array or Hash => The cases are failing, implementation correction needed.
|
59
|
+
Revise TODOs in the code
|
60
|
+
Make a tutorial video
|
61
|
+
Fix Rubocop errors
|
62
|
+
Live Reload
|
63
|
+
AR eager load
|
64
|
+
Relation with aliases tests
|
65
|
+
Aggregations
|
66
|
+
Remove config / auto-setup AR-Mongo
|
67
|
+
Write division for "every" in Mongoid and AR
|
68
|
+
Permissions on fields
|
69
|
+
has_one_through implementation
|
70
|
+
has_many_selves (employee) tests
|
71
|
+
has_and_belongs_to_many_selves (followers) tests
|
72
|
+
Embedded::Many filtering implementation
|
73
|
+
Embedded::One filtering with OR/AND
|
60
74
|
|
61
75
|
## License
|
62
76
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'bundler/setup'
|
3
5
|
rescue LoadError
|
@@ -14,11 +16,6 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
14
16
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
19
|
require 'bundler/gem_tasks'
|
23
20
|
|
24
21
|
require 'rake/testtask'
|
@@ -29,5 +26,4 @@ Rake::TestTask.new(:test) do |t|
|
|
29
26
|
t.verbose = false
|
30
27
|
end
|
31
28
|
|
32
|
-
|
33
29
|
task default: :test
|
data/lib/graphoid/argument.rb
CHANGED
data/lib/graphoid/config.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Graphoid
|
2
4
|
module Filters
|
3
|
-
|
4
5
|
LIST = {}
|
5
6
|
|
6
7
|
class << self
|
@@ -19,14 +20,14 @@ module Graphoid
|
|
19
20
|
argument(:OR, -> { types[m] })
|
20
21
|
argument(:AND, -> { types[m] })
|
21
22
|
|
22
|
-
operators = [
|
23
|
-
operators.push(
|
23
|
+
operators = %w[lt lte gt gte contains not]
|
24
|
+
operators.push('regex') if Graphoid.configuration.driver == :mongoid
|
24
25
|
|
25
26
|
operators.each do |suffix|
|
26
27
|
argument "#{name}_#{suffix}", type
|
27
28
|
end
|
28
29
|
|
29
|
-
[
|
30
|
+
%w[in nin].each do |suffix|
|
30
31
|
argument "#{name}_#{suffix}", types[type]
|
31
32
|
end
|
32
33
|
end
|
@@ -41,17 +42,15 @@ module Graphoid
|
|
41
42
|
relation_name = Utils.camelize(name)
|
42
43
|
|
43
44
|
if Relation.new(relation).many?
|
44
|
-
[
|
45
|
+
%w[some none every].each do |suffix|
|
45
46
|
argument "#{relation_name}_#{suffix}", relation_filter
|
46
47
|
end
|
47
48
|
else
|
48
|
-
argument
|
49
|
+
argument relation_name.to_s, relation_filter
|
49
50
|
end
|
50
51
|
end
|
51
|
-
|
52
52
|
end
|
53
53
|
end
|
54
|
-
|
55
54
|
end
|
56
55
|
end
|
57
56
|
end
|
@@ -1,21 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Graphoid
|
2
4
|
module Inputs
|
3
5
|
LIST = {}
|
4
6
|
|
5
7
|
class << self
|
6
|
-
def generate
|
8
|
+
def generate(model)
|
7
9
|
LIST[model] ||= GraphQL::InputObjectType.define do
|
8
10
|
name = Utils.graphqlize(model.name)
|
9
11
|
name("#{name}Input")
|
10
12
|
description("Generated model input for #{name}")
|
11
13
|
|
12
14
|
Attribute.fields_of(model).each do |field|
|
13
|
-
|
14
|
-
type = Graphoid::Mapper.convert(field)
|
15
|
-
name = Utils.camelize(field.name)
|
15
|
+
next if field.name.start_with?('_')
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
type = Graphoid::Mapper.convert(field)
|
18
|
+
name = Utils.camelize(field.name)
|
19
|
+
|
20
|
+
argument(name, type)
|
19
21
|
end
|
20
22
|
|
21
23
|
Relation.relations_of(model).each do |name, relation|
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Graphoid
|
2
4
|
module Orders
|
3
|
-
|
4
5
|
LIST = {}
|
5
6
|
@@enum_type = nil
|
6
7
|
|
@@ -31,13 +32,12 @@ module Graphoid
|
|
31
32
|
|
32
33
|
def enum_type
|
33
34
|
@@enum_type ||= GraphQL::EnumType.define do
|
34
|
-
name
|
35
|
+
name 'OrderType'
|
35
36
|
|
36
|
-
value
|
37
|
-
value
|
37
|
+
value 'ASC', 'Ascendent'
|
38
|
+
value 'DESC', 'Descendent'
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|
41
42
|
end
|
42
|
-
|
43
43
|
end
|
@@ -1,15 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Graphoid
|
2
4
|
module Types
|
3
|
-
|
4
5
|
LIST = {}
|
5
6
|
ENUMS = {}
|
6
7
|
|
7
8
|
class << self
|
8
9
|
def generate(model)
|
9
10
|
Graphoid::Types::Meta ||= GraphQL::ObjectType.define do
|
10
|
-
name(
|
11
|
-
description(
|
12
|
-
field(
|
11
|
+
name('Meta')
|
12
|
+
description('Meta Type')
|
13
|
+
field('count', types.Int)
|
13
14
|
end
|
14
15
|
|
15
16
|
LIST[model] ||= GraphQL::ObjectType.define do
|
@@ -26,24 +27,24 @@ module Graphoid
|
|
26
27
|
if _field.name.include?('_')
|
27
28
|
define_method :"#{Utils.camelize(_field.name)}" do
|
28
29
|
method_name = _field.name.to_s
|
29
|
-
self[method_name] ||
|
30
|
+
self[method_name] || send(method_name)
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
35
|
-
Relation.relations_of(model).each do |
|
36
|
+
Relation.relations_of(model).each do |_, relation|
|
36
37
|
relation_class = relation.class_name.safe_constantize
|
37
38
|
|
38
39
|
message = "in model #{model.name}: skipping relation #{relation.class_name}"
|
39
40
|
unless relation_class
|
40
|
-
|
41
|
+
warn "Graphoid: warning: #{message} because the model name is not valid" if ENV['DEBUG']
|
41
42
|
next
|
42
43
|
end
|
43
44
|
|
44
45
|
relation_type = LIST[relation_class]
|
45
46
|
unless relation_type
|
46
|
-
|
47
|
+
warn "Graphoid: warning: #{message} because it was not found as a model" if ENV['DEBUG']
|
47
48
|
next
|
48
49
|
end
|
49
50
|
|
@@ -52,32 +53,32 @@ module Graphoid
|
|
52
53
|
model.class_eval do
|
53
54
|
if relation.name.to_s.include?('_')
|
54
55
|
define_method :"#{name}" do
|
55
|
-
|
56
|
+
send(relation.name)
|
56
57
|
end
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
60
|
-
|
61
|
-
filter = Graphoid::Filters::LIST[relation_class]
|
62
|
-
order = Graphoid::Orders::LIST[relation_class]
|
61
|
+
next unless relation_type
|
63
62
|
|
64
|
-
|
65
|
-
|
63
|
+
filter = Graphoid::Filters::LIST[relation_class]
|
64
|
+
order = Graphoid::Orders::LIST[relation_class]
|
66
65
|
|
67
|
-
|
68
|
-
|
69
|
-
Graphoid::Types.resolve_many(self, relation_class, relation)
|
70
|
-
end
|
66
|
+
if Relation.new(relation).many?
|
67
|
+
plural_name = name.pluralize
|
71
68
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
69
|
+
field plural_name, types[relation_type] do
|
70
|
+
Graphoid::Argument.query_many(self, filter, order)
|
71
|
+
Graphoid::Types.resolve_many(self, relation_class, relation)
|
72
|
+
end
|
73
|
+
|
74
|
+
field "_#{plural_name}_meta", Graphoid::Types::Meta do
|
75
|
+
Graphoid::Argument.query_many(self, filter, order)
|
76
|
+
Graphoid::Types.resolve_many(self, relation_class, relation)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
field name, relation_type do
|
80
|
+
argument :where, filter
|
81
|
+
Graphoid::Types.resolve_one(self, relation_class, relation)
|
81
82
|
end
|
82
83
|
end
|
83
84
|
end
|
@@ -85,26 +86,31 @@ module Graphoid
|
|
85
86
|
end
|
86
87
|
|
87
88
|
def resolve_one(field, model, association)
|
88
|
-
field.resolve
|
89
|
-
filter = args[
|
89
|
+
field.resolve lambda { |obj, args, _ctx|
|
90
|
+
filter = args['where'].to_h
|
90
91
|
result = obj.send(association.name)
|
91
|
-
|
92
|
+
processor = Graphoid::Queries::Processor
|
93
|
+
if filter.present? && result
|
94
|
+
result = processor.execute(model.where(id: result.id), filter).first
|
95
|
+
end
|
92
96
|
result
|
93
|
-
|
97
|
+
}
|
94
98
|
end
|
95
99
|
|
96
|
-
def resolve_many(field,
|
97
|
-
field.resolve
|
98
|
-
filter = args[
|
99
|
-
order = args[
|
100
|
-
limit = args[
|
101
|
-
skip = args[
|
100
|
+
def resolve_many(field, _model, association)
|
101
|
+
field.resolve lambda { |obj, args, _ctx|
|
102
|
+
filter = args['where'].to_h
|
103
|
+
order = args['order'].to_h
|
104
|
+
limit = args['limit']
|
105
|
+
skip = args['skip']
|
106
|
+
|
107
|
+
processor = Graphoid::Queries::Processor
|
102
108
|
|
103
109
|
result = obj.send(association.name)
|
104
|
-
result =
|
110
|
+
result = processor.execute(result, filter) if filter.present?
|
105
111
|
|
106
112
|
if order.present?
|
107
|
-
order =
|
113
|
+
order = processor.parse_order(obj.send(association.name), order)
|
108
114
|
result = result.order(order)
|
109
115
|
end
|
110
116
|
|
@@ -112,7 +118,7 @@ module Graphoid
|
|
112
118
|
result = result.skip(skip) if skip.present?
|
113
119
|
|
114
120
|
result
|
115
|
-
|
121
|
+
}
|
116
122
|
end
|
117
123
|
end
|
118
124
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Graphoid
|
2
4
|
module ActiveRecordDriver
|
3
5
|
class << self
|
@@ -21,33 +23,33 @@ module Graphoid
|
|
21
23
|
type == ActiveRecord::Reflection::HasOneReflection
|
22
24
|
end
|
23
25
|
|
24
|
-
def embeds_one?(
|
26
|
+
def embeds_one?(_type)
|
25
27
|
false
|
26
28
|
end
|
27
29
|
|
28
|
-
def embeds_many?(
|
30
|
+
def embeds_many?(_type)
|
29
31
|
false
|
30
32
|
end
|
31
33
|
|
32
|
-
def embedded_in?(
|
34
|
+
def embedded_in?(_type)
|
33
35
|
false
|
34
36
|
end
|
35
37
|
|
36
38
|
def types_map
|
37
39
|
{
|
38
|
-
binary:
|
40
|
+
binary: GraphQL::Types::Boolean,
|
39
41
|
boolean: GraphQL::Types::Boolean,
|
40
|
-
float:
|
42
|
+
float: GraphQL::Types::Float,
|
41
43
|
integer: GraphQL::Types::Int,
|
42
|
-
string:
|
43
|
-
|
44
|
-
datetime:
|
45
|
-
date:
|
46
|
-
time:
|
47
|
-
timestamp:
|
48
|
-
text:
|
49
|
-
bigint:
|
50
|
-
decimal:
|
44
|
+
string: GraphQL::Types::String,
|
45
|
+
|
46
|
+
datetime: Graphoid::Scalars::DateTime,
|
47
|
+
date: Graphoid::Scalars::DateTime,
|
48
|
+
time: Graphoid::Scalars::DateTime,
|
49
|
+
timestamp: Graphoid::Scalars::DateTime,
|
50
|
+
text: Graphoid::Scalars::Text,
|
51
|
+
bigint: Graphoid::Scalars::BigInt,
|
52
|
+
decimal: Graphoid::Scalars::Decimal
|
51
53
|
}
|
52
54
|
end
|
53
55
|
|
@@ -99,17 +101,17 @@ module Graphoid
|
|
99
101
|
def parse(attribute, value, operator)
|
100
102
|
field = attribute.name
|
101
103
|
case operator
|
102
|
-
when
|
103
|
-
parsed =
|
104
|
-
parsed =
|
105
|
-
parsed =
|
106
|
-
when
|
107
|
-
parsed =
|
108
|
-
when
|
109
|
-
operator = { gt:
|
110
|
-
parsed =
|
104
|
+
when 'not'
|
105
|
+
parsed = ["#{field} != ?", value]
|
106
|
+
parsed = ["#{field} not like ?", value.to_s] if attribute.type == :string
|
107
|
+
parsed = ["#{field} is not null"] if value.nil?
|
108
|
+
when 'contains', 'regex'
|
109
|
+
parsed = ["#{field} like ?", "%#{value}%"]
|
110
|
+
when 'gt', 'gte', 'lt', 'lte', 'not', 'in', 'nin'
|
111
|
+
operator = { gt: '>', gte: '>=', lt: '<', lte: '<=', in: 'in', nin: 'not in' }[operator.to_sym]
|
112
|
+
parsed = ["#{field} #{operator} (?)", value]
|
111
113
|
else
|
112
|
-
parsed =
|
114
|
+
parsed = ["#{field} = ?", value]
|
113
115
|
end
|
114
116
|
parsed
|
115
117
|
end
|
@@ -131,19 +133,19 @@ module Graphoid
|
|
131
133
|
target = Graphoid::Queries::Processor.execute(relation.klass, value).to_a
|
132
134
|
|
133
135
|
if relation.many_to_many?
|
134
|
-
field_name = field_name.to_s.singularize +
|
135
|
-
ids = target.map(&
|
136
|
+
field_name = field_name.to_s.singularize + '_ids'
|
137
|
+
ids = target.map(&field_name.to_sym)
|
136
138
|
ids.flatten!.uniq!
|
137
139
|
else
|
138
140
|
field_name = :"#{field_name}_id"
|
139
|
-
ids = target.map(&
|
141
|
+
ids = target.map(&field_name)
|
140
142
|
end
|
141
143
|
|
142
|
-
if operator ==
|
143
|
-
parsed =
|
144
|
-
elsif operator ==
|
145
|
-
parsed =
|
146
|
-
elsif operator ==
|
144
|
+
if operator == 'none'
|
145
|
+
parsed = ['id not in (?)', ids] if ids.present?
|
146
|
+
elsif operator == 'some'
|
147
|
+
parsed = ['id in (?)', ids]
|
148
|
+
elsif operator == 'every'
|
147
149
|
|
148
150
|
# the following process is a SQL division
|
149
151
|
# the amount of queries it executes is on per row
|
@@ -157,7 +159,7 @@ module Graphoid
|
|
157
159
|
operation = Operation.new(relation.klass, _key, _value)
|
158
160
|
parsed = parse(operation.operand, operation.value, operation.operator)
|
159
161
|
val = parsed.last.is_a?(String) ? "'#{parsed.last}'" : parsed.last
|
160
|
-
parsed = parsed.first.sub(
|
162
|
+
parsed = parsed.first.sub('?', val)
|
161
163
|
" AND #{parsed}"
|
162
164
|
end.join
|
163
165
|
|
@@ -173,15 +175,13 @@ module Graphoid
|
|
173
175
|
)
|
174
176
|
"
|
175
177
|
result = ActiveRecord::Base.connection.execute(query)
|
176
|
-
ids = result.map{ |row| row[
|
178
|
+
ids = result.map { |row| row[field_name.to_s] }
|
177
179
|
|
178
|
-
parsed =
|
180
|
+
parsed = ['id in (?)', ids]
|
179
181
|
end
|
180
182
|
|
181
183
|
parsed
|
182
184
|
end
|
183
185
|
end
|
184
|
-
|
185
186
|
end
|
186
|
-
|
187
187
|
end
|