hq-graphql 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/MIT-LICENSE +20 -0
- data/README.md +55 -0
- data/Rakefile +17 -0
- data/lib/hq-graphql.rb +1 -0
- data/lib/hq/graphql.rb +32 -0
- data/lib/hq/graphql/engine.rb +6 -0
- data/lib/hq/graphql/object.rb +117 -0
- data/lib/hq/graphql/types.rb +55 -0
- data/lib/hq/graphql/types/uuid.rb +33 -0
- data/lib/hq/graphql/version.rb +5 -0
- data/spec/factories/advisors.rb +6 -0
- data/spec/factories/organizations.rb +5 -0
- data/spec/factories/users.rb +6 -0
- data/spec/internal/app/graphql/query.rb +18 -0
- data/spec/internal/app/graphql/schema.rb +3 -0
- data/spec/internal/app/graphql/user_type.rb +3 -0
- data/spec/internal/app/models/advisor.rb +3 -0
- data/spec/internal/app/models/organization.rb +3 -0
- data/spec/internal/app/models/user.rb +3 -0
- data/spec/internal/config/database.circleci.yml +24 -0
- data/spec/internal/config/database.yml +8 -0
- data/spec/internal/db/schema.rb +21 -0
- data/spec/lib/object_spec.rb +198 -0
- data/spec/lib/types_spec.rb +60 -0
- data/spec/rails_helper.rb +41 -0
- data/spec/spec_helper.rb +22 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: be9de36cb5b0f12696e98d85156404d89acdc6d5
|
4
|
+
data.tar.gz: 0e887aabc0d14374ad192e464a8b1e3e4ac4ea8f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7ec2755f98cd6045e93da317edec985a3a148c4baef5a7e30d24c1568020327f455d4a7efb14b117d482c0b51106e69ad67f0db33c6051c8b42d750f951086c8
|
7
|
+
data.tar.gz: aae1f46e971c88db6a4e87d919dc354e0235931041bf485172df0109520892f1e0294a10b99c81943ca71132b92ed1bfd56f76fdda5d2cb0e3b9e9323bc56665
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 Danny Jones
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# HQ::GraphQL
|
2
|
+
|
3
|
+
OneHQ GraphQL interface to [Ruby Graphql](https://github.com/rmosolgo/graphql-ruby).
|
4
|
+
|
5
|
+
[](https://circleci.com/gh/OneHQ/hq-graphql/tree/master)
|
6
|
+
[](https://github.com/OneHQ/hq-graphql)
|
7
|
+
|
8
|
+
## Configuration
|
9
|
+
|
10
|
+
You can pass configuration options as a block to `::HQ::GraphQL.configure`.
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
# The gem assumes that if your model is called `MyModel`, the corresponding type is `MyModelType`.
|
14
|
+
# You can override that convention.
|
15
|
+
# Default is: -> (model_class) { "#{model_class.name.demodulize}Type" }
|
16
|
+
::HQ::GraphQL.config do |config|
|
17
|
+
config.model_to_graphql_type = -> (model_class) { "::CustomNameSpace::#{model_class.name}Type" }
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Create a new ::HQ::GraphQL::Object
|
24
|
+
```ruby
|
25
|
+
class AdvisorType < ::HQ::GraphQL::Object
|
26
|
+
# Supports graphql-ruby functionality
|
27
|
+
field :id, Int, null: false
|
28
|
+
|
29
|
+
# Lazy Loading
|
30
|
+
# Useful for loading data from the database to generate a schema
|
31
|
+
lazy_load do
|
32
|
+
load_data_from_db.each do |name|
|
33
|
+
field name, String, null: false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Attach the GraphQL object to an ActiveRecord Model
|
38
|
+
# First argument is the string form of your ActiveRecord model.
|
39
|
+
#
|
40
|
+
# attributes:
|
41
|
+
# Set it to false if you don't want to auto-include your model's attributes.
|
42
|
+
# Defaults to true.
|
43
|
+
#
|
44
|
+
# associations:
|
45
|
+
# Set it to false if you don't want to auto-include your model's associations.
|
46
|
+
# Defaults to true.
|
47
|
+
with_model "Advisor", attributes: true, associations: false
|
48
|
+
|
49
|
+
# Remove attributes that were included by `with_model`
|
50
|
+
remove_attrs :id, :created_at, :updated_at
|
51
|
+
|
52
|
+
# Remove associations that were included by `with_model`
|
53
|
+
remove_associations :organization, :created_by
|
54
|
+
end
|
55
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'HQ::GraphQL'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'bundler/gem_tasks'
|
data/lib/hq-graphql.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "hq/graphql"
|
data/lib/hq/graphql.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "graphql"
|
2
|
+
|
3
|
+
module HQ
|
4
|
+
module GraphQL
|
5
|
+
def self.config
|
6
|
+
@config ||= ::ActiveSupport::OrderedOptions.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.configure(&block)
|
10
|
+
config.instance_eval(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# The gem assumes that if your model is called `MyModel`, the corresponding type is `MyModelType`.
|
14
|
+
# You can override that convention.
|
15
|
+
#
|
16
|
+
# ::HQ::GraphQL.config do |config|
|
17
|
+
# config.model_to_graphql_type = -> (model_class) { "::CustomNameSpace::#{model_class.name}Type" }
|
18
|
+
# end
|
19
|
+
def self.model_to_graphql_type
|
20
|
+
config.model_to_graphql_type ||
|
21
|
+
@model_to_graphql_type ||= -> (model_class) { "#{model_class.name.demodulize}Type" }
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.graphql_type_from_model(model_class)
|
25
|
+
model_to_graphql_type.call(model_class)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require "hq/graphql/object"
|
31
|
+
require "hq/graphql/types"
|
32
|
+
require "hq/graphql/engine"
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module HQ
|
2
|
+
module GraphQL
|
3
|
+
class Object < ::GraphQL::Schema::Object
|
4
|
+
class Error < StandardError
|
5
|
+
MISSING_MODEL_MSG = "Can't perform %{action} without connecting to a model: `::HQ::GraphQL::Object.with_model 'User'`".freeze
|
6
|
+
MISSING_ATTR_MSG = "Can't find attr %{model}.%{attr}'`".freeze
|
7
|
+
MISSING_ASSOC_MSG = "Can't find association %{model}.%{assoc}'`".freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.lazy_load(&block)
|
11
|
+
@lazy_load ||= []
|
12
|
+
@lazy_load << block if block
|
13
|
+
@lazy_load
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.with_model(model_name, attributes: true, associations: true)
|
17
|
+
self.model_name = model_name
|
18
|
+
|
19
|
+
lazy_load do
|
20
|
+
if attributes
|
21
|
+
model_klass.columns.reject { |c| removed_attrs.include?(c.name.to_sym) }.each do |column|
|
22
|
+
field_from_column column
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if associations
|
27
|
+
model_klass.reflect_on_all_associations.reject { |a| removed_associations.include?(a.name.to_sym) }.each do |association|
|
28
|
+
field_from_association association
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.add_attr(attr)
|
35
|
+
lazy_load do
|
36
|
+
validate_model!(:add_attr)
|
37
|
+
field_from_column model_column(attr)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.remove_attrs(*attrs)
|
42
|
+
removed_attrs.concat attrs.map(&:to_sym)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.remove_associations(*associations)
|
46
|
+
removed_associations.concat associations.map(&:to_sym)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.add_association(association)
|
50
|
+
lazy_load do
|
51
|
+
validate_model!(:add_association)
|
52
|
+
field_from_association model_association(association)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.to_graphql
|
57
|
+
lazy_load!
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
class << self
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_accessor :model_name
|
65
|
+
attr_writer :removed_attrs, :removed_associations
|
66
|
+
|
67
|
+
def lazy_load!
|
68
|
+
lazy_load.map(&:call)
|
69
|
+
@lazy_load = []
|
70
|
+
end
|
71
|
+
|
72
|
+
def model_klass
|
73
|
+
@model_klass ||= model_name&.constantize
|
74
|
+
end
|
75
|
+
|
76
|
+
def model_column(attr)
|
77
|
+
model_klass.columns_hash[attr.to_s] || raise(Error, Error::MISSING_ATTR_MSG % { model: model_name, attr: attr })
|
78
|
+
end
|
79
|
+
|
80
|
+
def model_association(association)
|
81
|
+
model_klass.reflect_on_association(association) || raise(Error, Error::MISSING_ASSOC_MSG % { model: model_name, assoc: association })
|
82
|
+
end
|
83
|
+
|
84
|
+
def removed_attrs
|
85
|
+
@removed_attrs ||= []
|
86
|
+
end
|
87
|
+
|
88
|
+
def removed_associations
|
89
|
+
@removed_associations ||= []
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_model!(action)
|
93
|
+
unless model_name
|
94
|
+
raise Error, Error::MISSING_MODEL_MSG % { action: action }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def field_from_association(association)
|
99
|
+
name = association.name
|
100
|
+
type = ::HQ::GraphQL::Types[association.klass]
|
101
|
+
case association.macro
|
102
|
+
when :has_many
|
103
|
+
field name, [type], null: false
|
104
|
+
else
|
105
|
+
field name, type, null: true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def field_from_column(column)
|
110
|
+
field column.name, ::HQ::GraphQL::Types.type_from_column(column), null: column.null
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "hq/graphql/types/uuid"
|
2
|
+
|
3
|
+
module HQ
|
4
|
+
module GraphQL
|
5
|
+
module Types
|
6
|
+
|
7
|
+
def self.[](key)
|
8
|
+
@schema_objects ||= Hash.new do |hash, klass|
|
9
|
+
hash[klass] = klass_for(klass)
|
10
|
+
end
|
11
|
+
@schema_objects[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.type_from_column(column)
|
15
|
+
case column&.cast_type&.type
|
16
|
+
when :uuid
|
17
|
+
::HQ::GraphQL::Types::UUID
|
18
|
+
when :integer
|
19
|
+
::GraphQL::INT_TYPE
|
20
|
+
when :decimal
|
21
|
+
::GraphQL::FLOAT_TYPE
|
22
|
+
when :boolean
|
23
|
+
::GraphQL::BOOLEAN_TYPE
|
24
|
+
else
|
25
|
+
::GraphQL::STRING_TYPE
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Only being used in testing
|
30
|
+
def self.reset!
|
31
|
+
@schema_objects = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
private
|
36
|
+
|
37
|
+
def klass_for(klass)
|
38
|
+
hql_klass_name = ::HQ::GraphQL.graphql_type_from_model(klass)
|
39
|
+
hql_klass = hql_klass_name.safe_constantize
|
40
|
+
return hql_klass if hql_klass
|
41
|
+
|
42
|
+
module_name = hql_klass_name.deconstantize.presence
|
43
|
+
hql_module = module_name ? (module_name.safe_constantize || ::Object.const_set(module_name, Module.new)) : ::Object
|
44
|
+
|
45
|
+
hql_klass = Class.new(::HQ::GraphQL::Object) do
|
46
|
+
with_model klass.name
|
47
|
+
end
|
48
|
+
|
49
|
+
hql_module.const_set(hql_klass_name.demodulize, hql_klass)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module HQ
|
2
|
+
module GraphQL
|
3
|
+
module Types
|
4
|
+
class UUID < ::GraphQL::Schema::Scalar
|
5
|
+
description "UUID"
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def self.coerce_input(value, context)
|
9
|
+
validate_and_return_uuid(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.coerce_result(value, context)
|
13
|
+
validate_and_return_uuid(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def validate_and_return_uuid(value)
|
19
|
+
if validate_uuid(value)
|
20
|
+
value
|
21
|
+
else
|
22
|
+
raise ::GraphQL::CoercionError, "#{value.inspect} is not a valid UUID"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_uuid(value)
|
27
|
+
!!value.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Query < GraphQL::Schema::Object
|
2
|
+
graphql_name "Query"
|
3
|
+
|
4
|
+
field :users, [HQ::GraphQL::Types[User]], null: false
|
5
|
+
field :advisors, [HQ::GraphQL::Types[Advisor]], null: false
|
6
|
+
|
7
|
+
def users
|
8
|
+
User.all
|
9
|
+
end
|
10
|
+
|
11
|
+
def users_custom
|
12
|
+
users
|
13
|
+
end
|
14
|
+
|
15
|
+
def advisors
|
16
|
+
Advisor.all
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
common: &common
|
2
|
+
adapter: postgresql
|
3
|
+
encoding: utf8
|
4
|
+
host: <%= ENV["DBHOST"] || "127.0.0.1" %>
|
5
|
+
port: <%= ENV["DBPORT"] || "5432" %>
|
6
|
+
database: <%= ENV["DBNAME"] || "hq-graphql" %>
|
7
|
+
username: <%= ENV["DBUSER"] || "postgres" %>
|
8
|
+
password: <%= ENV["DBPASSWORD"] || "" %>
|
9
|
+
pool: 5
|
10
|
+
reconnect: true
|
11
|
+
timeout: 5000
|
12
|
+
|
13
|
+
development:
|
14
|
+
<<: *common
|
15
|
+
|
16
|
+
test:
|
17
|
+
<<: *common
|
18
|
+
|
19
|
+
staging:
|
20
|
+
<<: *common
|
21
|
+
|
22
|
+
production:
|
23
|
+
<<: *common
|
24
|
+
pool: 10
|
@@ -0,0 +1,21 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
enable_extension "uuid-ossp"
|
3
|
+
|
4
|
+
create_table "organizations", force: true, id: :uuid do |t|
|
5
|
+
t.string :name, limit: 63, null: false
|
6
|
+
t.timestamps null: false
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table "users", force: true do |t|
|
10
|
+
t.belongs_to :organization, null: false, index: true, foreign_key: true, type: :uuid
|
11
|
+
t.string :name, null: false
|
12
|
+
t.timestamps null: false
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table "advisors", force: true do |t|
|
16
|
+
t.references :organization, null: false, index: true, foreign_key: true, type: :uuid
|
17
|
+
t.string :name, null: false
|
18
|
+
t.timestamps null: false
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe ::HQ::GraphQL::Object do
|
4
|
+
|
5
|
+
describe ".lazy_load" do
|
6
|
+
let(:lazy_load_class) do
|
7
|
+
Class.new(described_class) do
|
8
|
+
graphql_name "LazyLoadQuery"
|
9
|
+
|
10
|
+
@counter = 0
|
11
|
+
|
12
|
+
lazy_load do
|
13
|
+
@counter += 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.counter
|
17
|
+
@counter
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "lazy loads once" do
|
23
|
+
# First time it works
|
24
|
+
expect { lazy_load_class.to_graphql }.to change { lazy_load_class.counter }.by(1)
|
25
|
+
# Second time it does nothing
|
26
|
+
expect { lazy_load_class.to_graphql }.to change { lazy_load_class.counter }.by(0)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ".with_model" do
|
31
|
+
let(:hql_object_klass) do
|
32
|
+
Class.new(described_class) do
|
33
|
+
graphql_name "TestQuery"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "adds everything by default" do
|
38
|
+
hql_object_klass.class_eval do
|
39
|
+
with_model "Advisor"
|
40
|
+
end
|
41
|
+
|
42
|
+
expect(hql_object_klass.fields.keys).to be_empty
|
43
|
+
hql_object_klass.to_graphql
|
44
|
+
expected = ["createdAt", "id", "name", "organization", "organizationId", "updatedAt"]
|
45
|
+
expect(hql_object_klass.fields.keys).to contain_exactly(*expected)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "removes an attribute" do
|
49
|
+
hql_object_klass.class_eval do
|
50
|
+
remove_attrs :created_at, :id, :organization_id
|
51
|
+
with_model "Advisor"
|
52
|
+
end
|
53
|
+
|
54
|
+
expect(hql_object_klass.fields.keys).to be_empty
|
55
|
+
hql_object_klass.to_graphql
|
56
|
+
expected = ["name", "organization", "updatedAt"]
|
57
|
+
expect(hql_object_klass.fields.keys).to contain_exactly(*expected)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "removes an association" do
|
61
|
+
hql_object_klass.class_eval do
|
62
|
+
remove_associations :organization, :doesntexist
|
63
|
+
with_model "Advisor"
|
64
|
+
end
|
65
|
+
|
66
|
+
expect(hql_object_klass.fields.keys).to be_empty
|
67
|
+
hql_object_klass.to_graphql
|
68
|
+
expected = ["createdAt", "id", "name", "organizationId", "updatedAt"]
|
69
|
+
expect(hql_object_klass.fields.keys).to contain_exactly(*expected)
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with attributes and associations turned off" do
|
73
|
+
it "doesn't have any fields by default" do
|
74
|
+
hql_object_klass.to_graphql
|
75
|
+
expect(hql_object_klass.fields.keys).to be_empty
|
76
|
+
end
|
77
|
+
|
78
|
+
it "doesn't have any fields when disabling model attrs/associations" do
|
79
|
+
hql_object_klass.class_eval do
|
80
|
+
with_model "Advisor", attributes: false, associations: false
|
81
|
+
end
|
82
|
+
hql_object_klass.to_graphql
|
83
|
+
expect(hql_object_klass.fields.keys).to be_empty
|
84
|
+
end
|
85
|
+
|
86
|
+
it "blows up when adding an attribute to an object without a model" do
|
87
|
+
hql_object_klass.class_eval do
|
88
|
+
add_attr :name
|
89
|
+
end
|
90
|
+
|
91
|
+
expect { hql_object_klass.to_graphql }.to raise_error(described_class::Error)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "blows up when adding an attribute that doesn't exist" do
|
95
|
+
hql_object_klass.class_eval do
|
96
|
+
add_attr :doesnt_exist
|
97
|
+
|
98
|
+
with_model "Advisor", attributes: false, associations: false
|
99
|
+
end
|
100
|
+
|
101
|
+
expect { hql_object_klass.to_graphql }.to raise_error(described_class::Error)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "adds attributes once connected to a model" do
|
105
|
+
hql_object_klass.class_eval do
|
106
|
+
# Order shouldn't matter....but let's test it anyway
|
107
|
+
|
108
|
+
# First
|
109
|
+
add_attr :name
|
110
|
+
|
111
|
+
# Second
|
112
|
+
with_model "Advisor", attributes: false, associations: false
|
113
|
+
end
|
114
|
+
|
115
|
+
expect(hql_object_klass.fields.keys).to be_empty
|
116
|
+
hql_object_klass.to_graphql
|
117
|
+
expect(hql_object_klass.fields.keys).to contain_exactly("name")
|
118
|
+
end
|
119
|
+
|
120
|
+
it "blows up when adding an association to an object without a model" do
|
121
|
+
hql_object_klass.class_eval do
|
122
|
+
add_association :organization
|
123
|
+
end
|
124
|
+
|
125
|
+
expect { hql_object_klass.to_graphql }.to raise_error(described_class::Error)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "blows up when adding an association that doesn't exist" do
|
129
|
+
hql_object_klass.class_eval do
|
130
|
+
add_association :doesnt_exist
|
131
|
+
|
132
|
+
with_model "Advisor", attributes: false, associations: false
|
133
|
+
end
|
134
|
+
|
135
|
+
expect { hql_object_klass.to_graphql }.to raise_error(described_class::Error)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "adds associations once connected to a model" do
|
139
|
+
hql_object_klass.class_eval do
|
140
|
+
# Order shouldn't matter....but let's test it anyway
|
141
|
+
|
142
|
+
# First
|
143
|
+
add_association :organization
|
144
|
+
|
145
|
+
# Second
|
146
|
+
with_model "Advisor", attributes: false, associations: false
|
147
|
+
end
|
148
|
+
|
149
|
+
expect(hql_object_klass.fields.keys).to be_empty
|
150
|
+
hql_object_klass.to_graphql
|
151
|
+
expect(hql_object_klass.fields.keys).to contain_exactly("organization")
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
context "with a schema" do
|
157
|
+
let(:user_1) { FactoryBot.create(:user) }
|
158
|
+
let(:user_2) { FactoryBot.create(:user, organization: user_1.organization) }
|
159
|
+
|
160
|
+
before(:each) do
|
161
|
+
user_1
|
162
|
+
user_2
|
163
|
+
end
|
164
|
+
|
165
|
+
it "executes graphql" do
|
166
|
+
query_str = <<-GRAPHQ
|
167
|
+
query {
|
168
|
+
users {
|
169
|
+
name
|
170
|
+
organizationId
|
171
|
+
|
172
|
+
organization {
|
173
|
+
name
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
GRAPHQ
|
178
|
+
|
179
|
+
result = ::Schema.execute(query_str)
|
180
|
+
|
181
|
+
aggregate_failures do
|
182
|
+
user_1_data, user_2_data = result["data"]["users"]
|
183
|
+
# User 1
|
184
|
+
expect(user_1_data["name"]).to eql(user_1.name)
|
185
|
+
expect(user_1_data["organizationId"]).to eql(user_1.organization_id)
|
186
|
+
expect(user_1_data["organization"]["name"]).to eql(user_1.organization.name)
|
187
|
+
|
188
|
+
# User 2
|
189
|
+
expect(user_2_data["name"]).to eql(user_2.name)
|
190
|
+
expect(user_2_data["organizationId"]).to eql(user_2.organization_id)
|
191
|
+
expect(user_2_data["organization"]["name"]).to eql(user_2.organization.name)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe ::HQ::GraphQL::Types do
|
4
|
+
|
5
|
+
describe ".[]" do
|
6
|
+
before(:each) do
|
7
|
+
described_class.reset!
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:each) do
|
11
|
+
described_class.reset!
|
12
|
+
end
|
13
|
+
|
14
|
+
it "generates a new class with columns & associations" do
|
15
|
+
# Unique Classes
|
16
|
+
allow(::HQ::GraphQL).to receive(:model_to_graphql_type) { -> (model_class) { "#{model_class.name.demodulize}TypeTest" } }
|
17
|
+
|
18
|
+
advisor_klass = described_class[Advisor]
|
19
|
+
user_klass = described_class[User]
|
20
|
+
advisor_user_fields = ["id", "organizationId", "name", "createdAt", "updatedAt", "organization"]
|
21
|
+
|
22
|
+
#### Advisor Schema ####
|
23
|
+
expect(advisor_klass.name).to eql(::HQ::GraphQL.graphql_type_from_model(Advisor))
|
24
|
+
expect(advisor_klass.fields.keys).to be_empty
|
25
|
+
### Build GraphQL schema
|
26
|
+
advisor_klass.to_graphql
|
27
|
+
expect(advisor_klass.fields.keys).to contain_exactly(*advisor_user_fields)
|
28
|
+
|
29
|
+
#### User Schema ####
|
30
|
+
expect(user_klass.name).to eql(::HQ::GraphQL.graphql_type_from_model(User))
|
31
|
+
expect(user_klass.fields.keys).to be_empty
|
32
|
+
user_klass.to_graphql
|
33
|
+
expect(user_klass.fields.keys).to contain_exactly(*advisor_user_fields)
|
34
|
+
|
35
|
+
organization_klass = ::HQ::GraphQL.graphql_type_from_model(Organization).constantize
|
36
|
+
expect(organization_klass.fields.keys).to be_empty
|
37
|
+
organization_klass.to_graphql
|
38
|
+
organization_fields = ["id", "name", "createdAt", "updatedAt", "users"]
|
39
|
+
expect(organization_klass.fields.keys).to contain_exactly(*organization_fields)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "users the existing class if one exists" do
|
43
|
+
allow(::HQ::GraphQL).to receive(:model_to_graphql_type) { -> (model_class) { "#{model_class.name.demodulize}TypeTestTwo" } }
|
44
|
+
|
45
|
+
klass = Class.new(::HQ::GraphQL::Object) do
|
46
|
+
graphql_name "TestQuery"
|
47
|
+
|
48
|
+
field :custom_field, String, null: false
|
49
|
+
end
|
50
|
+
advisor_klass = stub_const(::HQ::GraphQL.graphql_type_from_model(Advisor), klass)
|
51
|
+
|
52
|
+
expect(advisor_klass).to eql(described_class[Advisor])
|
53
|
+
expect(advisor_klass.fields.keys).to contain_exactly("customField")
|
54
|
+
advisor_klass.to_graphql
|
55
|
+
expect(advisor_klass.fields.keys).to contain_exactly("customField")
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "testhq/coverage" # Needs to be require at the top of this file!
|
2
|
+
# Coverage must also be enabled in the environment by setting
|
3
|
+
# environment variables, e.g. `COVERAGE=true` or `CODECLIMATE_REPO_TOKEN=...`.
|
4
|
+
# See https://github.com/OneHQ/testhq#code-coverage.
|
5
|
+
|
6
|
+
require "bundler/setup"
|
7
|
+
require "combustion"
|
8
|
+
|
9
|
+
silence_stream(STDOUT) do # Hides a lot of output from Combustion init such as schema loading.
|
10
|
+
Combustion.initialize! :all do
|
11
|
+
# Disable strong parameters
|
12
|
+
config.action_controller.permit_all_parameters = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require "byebug"
|
17
|
+
require "rspec/rails"
|
18
|
+
require "testhq"
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
config.expect_with :rspec do |c|
|
22
|
+
c.syntax = [:expect]
|
23
|
+
end
|
24
|
+
|
25
|
+
config.use_transactional_fixtures = false
|
26
|
+
|
27
|
+
config.include FactoryBot::Syntax::Methods
|
28
|
+
|
29
|
+
config.before(:suite) do
|
30
|
+
DatabaseCleaner.strategy = :transaction
|
31
|
+
DatabaseCleaner.clean_with :truncation # Clear everything
|
32
|
+
end
|
33
|
+
|
34
|
+
config.before(:each) do
|
35
|
+
DatabaseCleaner.start
|
36
|
+
end
|
37
|
+
|
38
|
+
config.after(:each) do
|
39
|
+
DatabaseCleaner.clean
|
40
|
+
end
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.expect_with :rspec do |expectations|
|
3
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
4
|
+
# and `failure_message` of custom matchers include text for helper methods
|
5
|
+
# defined using `chain`, e.g.:
|
6
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
7
|
+
# # => "be bigger than 2 and smaller than 4"
|
8
|
+
# ...rather than:
|
9
|
+
# # => "be bigger than 2"
|
10
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
11
|
+
end
|
12
|
+
|
13
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
14
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
15
|
+
config.mock_with :rspec do |mocks|
|
16
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
17
|
+
# a real object. This is generally recommended, and will default to
|
18
|
+
# `true` in RSpec 4.
|
19
|
+
mocks.verify_partial_doubles = true
|
20
|
+
end
|
21
|
+
config.order = :random
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hq-graphql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Danny Jones
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: graphql
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 1.8.7
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.0'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.8.7
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec_junit_formatter
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.3'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.3.0
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.3'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.3.0
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: testhq
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '1.0'
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.0.0
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '1.0'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 1.0.0
|
87
|
+
description: OneHQ GraphQL Library
|
88
|
+
email:
|
89
|
+
- dpjones09@gmail.com
|
90
|
+
executables: []
|
91
|
+
extensions: []
|
92
|
+
extra_rdoc_files: []
|
93
|
+
files:
|
94
|
+
- MIT-LICENSE
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- lib/hq-graphql.rb
|
98
|
+
- lib/hq/graphql.rb
|
99
|
+
- lib/hq/graphql/engine.rb
|
100
|
+
- lib/hq/graphql/object.rb
|
101
|
+
- lib/hq/graphql/types.rb
|
102
|
+
- lib/hq/graphql/types/uuid.rb
|
103
|
+
- lib/hq/graphql/version.rb
|
104
|
+
- spec/factories/advisors.rb
|
105
|
+
- spec/factories/organizations.rb
|
106
|
+
- spec/factories/users.rb
|
107
|
+
- spec/internal/app/graphql/query.rb
|
108
|
+
- spec/internal/app/graphql/schema.rb
|
109
|
+
- spec/internal/app/graphql/user_type.rb
|
110
|
+
- spec/internal/app/models/advisor.rb
|
111
|
+
- spec/internal/app/models/organization.rb
|
112
|
+
- spec/internal/app/models/user.rb
|
113
|
+
- spec/internal/config/database.circleci.yml
|
114
|
+
- spec/internal/config/database.yml
|
115
|
+
- spec/internal/db/schema.rb
|
116
|
+
- spec/lib/object_spec.rb
|
117
|
+
- spec/lib/types_spec.rb
|
118
|
+
- spec/rails_helper.rb
|
119
|
+
- spec/spec_helper.rb
|
120
|
+
homepage: https://github.com/OneHQ/hq-graphql
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.5.2.2
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: OneHQ GraphQL Library
|
144
|
+
test_files:
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
- spec/internal/app/models/advisor.rb
|
147
|
+
- spec/internal/app/models/organization.rb
|
148
|
+
- spec/internal/app/models/user.rb
|
149
|
+
- spec/internal/app/graphql/schema.rb
|
150
|
+
- spec/internal/app/graphql/query.rb
|
151
|
+
- spec/internal/app/graphql/user_type.rb
|
152
|
+
- spec/internal/config/database.circleci.yml
|
153
|
+
- spec/internal/config/database.yml
|
154
|
+
- spec/internal/db/schema.rb
|
155
|
+
- spec/factories/organizations.rb
|
156
|
+
- spec/factories/advisors.rb
|
157
|
+
- spec/factories/users.rb
|
158
|
+
- spec/lib/object_spec.rb
|
159
|
+
- spec/lib/types_spec.rb
|
160
|
+
- spec/rails_helper.rb
|