graphql_scaffold 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ff7d05ae97ea0004f7c28ce3e5b14b697348fc750e14b97009ea8186bde9f5d9
4
+ data.tar.gz: 135d80c2385192dff79b61d9ae53491160392ca8abc1b79bdc9c4eede3963cea
5
+ SHA512:
6
+ metadata.gz: c2349f064107e60f664edc85501e55a91a36ad998e15973d9790d5d994d0ad0036cab218eb73dde7437ba2782a2bb4386631226bc587aef3e8e15adde5827ea3
7
+ data.tar.gz: 9b43c9c482d02f8abc4f21bdb80b62ee59bff69efb80944aa6b59386c78939d6a9548176a734f37b5248703ef1ff475c70982bb5cdffa05ae6b1e2ee3ef29522
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Arthur Molina
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlScaffoldCommonMethods
4
+ private
5
+
6
+ def model_exists?
7
+ ActiveRecord::Base.connection.tables.include?(plural_name)
8
+ end
9
+
10
+ def singular_name
11
+ name.underscore.singularize
12
+ end
13
+
14
+ def singular_name_snaked
15
+ singular_name.gsub(' ', '_')
16
+ end
17
+
18
+ def singular_name_camelized
19
+ singular_name_snaked.camelcase
20
+ end
21
+
22
+ def plural_name
23
+ name.underscore.pluralize
24
+ end
25
+
26
+ def plural_name_snaked
27
+ plural_name.gsub(' ', '_')
28
+ end
29
+
30
+ def plural_name_camelized
31
+ plural_name_snaked.camelcase
32
+ end
33
+
34
+ def list_many
35
+ "all_#{plural_name_snaked}"
36
+ end
37
+
38
+ def list_one
39
+ singular_name_snaked
40
+ end
41
+
42
+ def create_one
43
+ "create_#{singular_name_snaked}"
44
+ end
45
+
46
+ def change_one
47
+ "change_#{singular_name_snaked}"
48
+ end
49
+
50
+ def destroy_one
51
+ "destroy_#{singular_name_snaked}"
52
+ end
53
+
54
+ def model_name
55
+ name.gsub('_', ' ').titlecase.gsub(' ', '')
56
+ end
57
+
58
+ def model
59
+ (eval model_name)
60
+ end
61
+
62
+ def primary_key
63
+ if model_exists?
64
+ model.primary_key
65
+ else
66
+ 'id'
67
+ end
68
+ end
69
+
70
+ def columns_type_without_primary_key
71
+ columns_types.select{|c| !c[:name].in?([primary_key, 'created_at', 'updated_at']) }
72
+ end
73
+
74
+ def columns_types
75
+ if model_exists?
76
+ if myattributes.present?
77
+ columns_model.select{ |elem|
78
+ columns_myattributes.select{ |el| elem[:name] == el[:name]}.present?
79
+ }
80
+ else
81
+ columns_model
82
+ end
83
+ else
84
+ columns_myattributes
85
+ end
86
+ end
87
+
88
+ def columns_model
89
+ model.columns_hash.map{|k,v| {name: k, type: type(v.type), null: v.null, sample: sample(v.type)}}
90
+ end
91
+
92
+ def columns_myattributes
93
+ res = [{name: 'id', type: type(:integer), null: false}]
94
+ res = res + myattributes.map{|element|
95
+ div = element.split(':')
96
+ type_defined = div[1].present? ? div[1].split('{')[0] : :string
97
+ {name: div[0], type: type(type_defined), null: false, sample: sample(type_defined)}
98
+ }
99
+ res << {name: 'created_at', type: type(:datetime), null: false, sample: sample(:datetime)}
100
+ res << {name: 'updated_at', type: type(:datetime), null: false, sample: sample(:datetime)}
101
+ end
102
+
103
+ def type(the_type)
104
+ case the_type
105
+ when :datetime
106
+ 'Types::DateTimeType'
107
+ when 'references'
108
+ 'Integer'
109
+ else
110
+ the_type.to_s.titlecase
111
+ end
112
+ end
113
+
114
+ def sample(the_type)
115
+ case the_type.to_s.downcase
116
+ when 'datetime'
117
+ Time.now.strftime("%d/%m/%Y %H:%M:%S")
118
+ when 'references', 'integer', 'float'
119
+ rand(1..10).to_s
120
+ else
121
+ the_type.to_s.titlecase
122
+ end
123
+ end
124
+
125
+ def require_gems
126
+ gems = {
127
+ 'graphql' => '1.9.15',
128
+ 'search_object' => '1.2.0',
129
+ 'search_object_graphql' => '0.1'
130
+ }
131
+
132
+ if !Dir.glob('./*.gemspec').empty?
133
+ puts "============> Engine : You must add gems to your main app \n #{gems.to_a.map{ |a| "gem '#{a[0]}'#{(a[1].nil? ? '' : ", '#{a[1]}'")} " }.join("\n")}"
134
+ end
135
+
136
+ gems.each{ |gem_to_add, version|
137
+ gem(gem_to_add, version)
138
+ }
139
+ end
140
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GraphqlScaffoldGenerator < Rails::Generators::Base
4
+ require_relative 'graphql_scaffold_common_methods'
5
+ include GraphqlScaffoldCommonMethods
6
+
7
+ desc 'This generator scaffolds a Graphql from a table.'
8
+
9
+ source_root File.expand_path('../templates', __FILE__)
10
+
11
+ argument :name, type: :string, desc: 'Name of model (singular)'
12
+ argument :myattributes, type: :array, default: [], banner: 'field:type field:type'
13
+
14
+ class_option :namespace, default: nil
15
+ class_option :donttouchgem, default: nil
16
+ class_option :mountable_engine, default: true
17
+
18
+ def check_model_existence
19
+ unless model_exists?
20
+ if myattributes.present?
21
+ puts 'Generating model...'
22
+ generate('model', "#{name} #{myattributes.join(' ')}")
23
+ else
24
+ puts "The model #{name} wasn't found. You can add attributes and generate the model with Graphql Scaffold."
25
+ exit
26
+ end
27
+ end
28
+ end
29
+
30
+ def install_gems
31
+ return true
32
+ require_gems if options[:donttouchgem].blank?
33
+
34
+ Bundler.with_clean_env do
35
+ run 'bundle install'
36
+ end
37
+
38
+ if options[:donttouchgem].blank?
39
+ generate('graphql:install')
40
+ run 'bundle install'
41
+ end
42
+ end
43
+
44
+ def copy_files
45
+ copy_file 'app/graphql/resolvers/base_search_resolver.rb', 'app/graphql/resolvers/base_search_resolver.rb'
46
+ copy_file 'app/graphql/types/enums/operator.rb', 'app/graphql/types/enums/operator.rb'
47
+ copy_file 'app/graphql/types/enums/sort_dir.rb', 'app/graphql/types/enums/sort_dir.rb'
48
+ copy_file 'app/graphql/types/date_time_type.rb', 'app/graphql/types/date_time_type.rb'
49
+ copy_file 'app/graphql/types/base_field.rb', 'app/graphql/types/base_field.rb'
50
+ copy_file 'app/graphql/mutations/base_mutation.rb', 'app/graphql/mutations/base_mutation.rb'
51
+ copy_file 'test/integration/graphql_1st_test.rb', 'test/integration/graphql_1st_test.rb'
52
+ end
53
+
54
+ def generate_graphql_query
55
+ template 'app/graphql/types/enums/table_field.rb', "app/graphql/types/enums/#{plural_name_snaked}_field.rb"
56
+ template 'app/graphql/types/table_type.rb', "app/graphql/types/#{singular_name_snaked}_type.rb"
57
+ template 'app/graphql/resolvers/table_search.rb', "app/graphql/resolvers/#{plural_name_snaked}_search.rb"
58
+ template 'app/graphql/mutations/change_table.rb', "app/graphql/mutations/change_#{singular_name_snaked}.rb"
59
+ template 'app/graphql/mutations/create_table.rb', "app/graphql/mutations/create_#{singular_name_snaked}.rb"
60
+ template 'app/graphql/mutations/destroy_table.rb', "app/graphql/mutations/destroy_#{singular_name_snaked}.rb"
61
+ template 'test/integration/graphql_table_test.rb', "test/integration/graphql_#{singular_name_snaked}_test.rb"
62
+ end
63
+
64
+ def add_in_query_type
65
+ inject_into_file(
66
+ 'app/graphql/types/query_type.rb',
67
+ " field :#{list_many}, function: Resolvers::#{plural_name_camelized}Search\n",
68
+ after: "class QueryType < Types::BaseObject\n")
69
+
70
+ inject_into_file(
71
+ 'app/graphql/types/mutation_type.rb',
72
+ "\n field :#{create_one}, mutation: Mutations::Create#{singular_name_camelized}" +
73
+ "\n field :#{change_one}, mutation: Mutations::Change#{singular_name_camelized}" +
74
+ "\n field :#{destroy_one}, mutation: Mutations::Destroy#{singular_name_camelized}\n",
75
+ after: "class MutationType < Types::BaseObject\n")
76
+ end
77
+
78
+ end
@@ -0,0 +1,11 @@
1
+ class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation
2
+ # Add your custom classes if you have them:
3
+ # This is used for generating payload types
4
+ object_class Types::BaseObject
5
+
6
+ # This is used for return fields on the mutation's payload
7
+ field_class Types::BaseField
8
+
9
+ # This is used for generating the `input: { ... }` object type
10
+ input_object_class Types::BaseInputObject
11
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Mutations::Change<%= singular_name_camelized %> < Mutations::BaseMutation
4
+ description 'Change a <%= singular_name_camelized %> record'
5
+ #graphql_name 'Change<%= singular_name_camelized %>'
6
+
7
+ <% for attribute in columns_types -%>
8
+ argument :<%= attribute[:name] %>, <%= attribute[:name] == primary_key ? 'ID' : attribute[:type] %>, required: <%= attribute[:name] == primary_key ? 'true' : 'false' %>
9
+ <% end -%>
10
+
11
+ field :<%= singular_name_snaked %>, Types::<%= singular_name_camelized %>Type, null: true
12
+ field :errors, [String], null: false
13
+
14
+ def resolve(**args)
15
+ <%= singular_name_snaked %> = <%= singular_name_camelized %>.find(args[:id])
16
+ <%= singular_name_snaked %>.update(args)
17
+ return {
18
+ <%= singular_name_snaked %>: <%= singular_name_snaked %>,
19
+ errors: <%= singular_name_snaked %>.errors.full_messages
20
+ }
21
+ rescue ActiveRecord::RecordInvalid => e
22
+ GraphQL::ExecutionError.new("Invalid input: #{e.record.errors.full_messages.join(', ')}")
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Mutations::Create<%= singular_name_camelized %> < Mutations::BaseMutation
4
+ null true
5
+ description 'Create a <%= singular_name_camelized %> record'
6
+ #graphql_name 'Create<%= singular_name_camelized %>'
7
+
8
+ <% for attribute in columns_types
9
+ next if attribute[:name] == primary_key
10
+ -%>
11
+ argument :<%= attribute[:name] %>, <%= attribute[:type] %>, required: false
12
+ <% end -%>
13
+
14
+ field :<%= singular_name_snaked %>, Types::<%= singular_name_camelized %>Type, null: true
15
+ field :errors, [String], null: false
16
+
17
+ def resolve(**args)
18
+ <%= singular_name_snaked %> = <%= singular_name_camelized %>.new(args)
19
+ if <%= singular_name_snaked %>.save
20
+ return {
21
+ <%= singular_name_snaked %>: <%= singular_name_snaked %>,
22
+ errors: []
23
+ }
24
+ else
25
+ return {
26
+ <%= singular_name_snaked %>: nil,
27
+ errors: <%= singular_name_snaked %>.errors.full_messages
28
+ }
29
+ end
30
+ rescue ActiveRecord::RecordInvalid => e
31
+ GraphQL::ExecutionError.new("Invalid input: #{e.record.errors.full_messages.join(', ')}")
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Mutations::Destroy<%= singular_name_camelized %> < Mutations::BaseMutation
4
+ description 'Destroy a <%= singular_name_camelized %> record'
5
+ #graphql_name 'Destroy<%= singular_name_camelized %>'
6
+
7
+ argument :<%= primary_key %>, ID, required: true
8
+
9
+ field :<%= singular_name_snaked %>, Types::<%= singular_name_camelized %>Type, null: true
10
+ field :errors, [String], null: false
11
+
12
+ def resolve(**args)
13
+ <%= singular_name_snaked %> = <%= singular_name_camelized %>.find(args[:<%= primary_key %>])
14
+ <%= singular_name_snaked %>.destroy
15
+ return {
16
+ <%= singular_name_snaked %>: <%= singular_name_snaked %>,
17
+ errors: <%= singular_name_snaked %>.errors.full_messages
18
+ }
19
+ rescue ActiveRecord::RecordInvalid => e
20
+ GraphQL::ExecutionError.new("Invalid input: #{e.record.errors.full_messages.join(', ')}")
21
+ end
22
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resolvers
4
+ class BaseSearchResolver
5
+ include SearchObject.module(:graphql)
6
+ include SearchObject.module(:enum)
7
+
8
+ def escape_search_term(term)
9
+ "%#{term.gsub(/\s+/, '%')}%"
10
+ end
11
+
12
+ def query_where(value)
13
+ redefine_values(transform_element(arguments_to_h(value)))
14
+ end
15
+
16
+ def arguments_to_h(value)
17
+ if value.is_a? Array
18
+ value.map{ |v| arguments_to_h(v) }
19
+ else
20
+ value.to_h
21
+ end
22
+ end
23
+
24
+ def has_comparison?(values)
25
+ values[:field].present? and values[:operator].present? and values[:value].present?
26
+ end
27
+
28
+ def transform_element(v, and_or_check = true)
29
+ and_or = and_or_check ? ' and ' : ' or '
30
+ values = v.clone
31
+ return '' if values.nil?
32
+ if values.is_a? Array
33
+ return "(#{values.map{ |v| transform_element(v) }.join(and_or)})"
34
+ elsif values.is_a? Hash
35
+ if values[:_or].present?
36
+ values[:_or].push({field: values[:field], operator: values[:operator], value: values[:value]}) if has_comparison?(values)
37
+ return transform_element(values[:_or], false)
38
+ elsif values[:_and].present?
39
+ values[:_and].push({field: values[:field], operator: values[:operator], value: values[:value]}) if has_comparison?(values)
40
+ return transform_element(values[:_and], true)
41
+ elsif values[:_not].present?
42
+ addon = has_comparison?(values) ? convert_element(values) + and_or : ''
43
+ res = transform_element(values[:_not], and_or_check)
44
+ return "#{addon}not (#{res.is_a?(String) ? res : res.join(and_or)})"
45
+ else
46
+ return convert_element values
47
+ end
48
+ else
49
+ return values
50
+ end
51
+ end
52
+
53
+ def convert_element(values)
54
+ return '' if not has_comparison?(values)
55
+
56
+ values[:field] + case values[:operator]
57
+ when 'equal', 'in'
58
+ " in (<var!>#{values[:value].to_json}</var!>)"
59
+ when 'greater_than'
60
+ " > <var!>#{values[:value].to_json}</var!>"
61
+ when 'greater_than_or_equal_to'
62
+ " >= <var!>#{values[:value].to_json}</var!>"
63
+ when 'ilike'
64
+ " ilike <var!>#{values[:value].to_json}</var!>"
65
+ when 'is_null'
66
+ " is #{values[:value].downcase == 'true' ? '' : 'not'} null"
67
+ when 'like'
68
+ " like <var!>#{values[:value].to_json}</var!>"
69
+ when 'less_than'
70
+ " < <var!>#{values[:value].to_json}</var!>"
71
+ when 'less_than_or_equal_to'
72
+ " <= <var!>#{values[:value].to_json}</var!>"
73
+ when 'not_equal', 'not_in'
74
+ " not in (<var!>#{values[:value].to_json}</var!>)"
75
+ when 'not_ilike'
76
+ " not ilike <var!>#{values[:value].to_json}</var!>"
77
+ when 'not_like'
78
+ " not like <var!>#{values[:value].to_json}</var!>"
79
+ when 'not_similar'
80
+ " not similar to <var!>#{values[:value].to_json}</var!>"
81
+ when 'similar'
82
+ " similar to <var!>#{values[:value].to_json}</var!>"
83
+ else
84
+ ''
85
+ end
86
+ end
87
+
88
+ def redefine_values(expression)
89
+ values = {}
90
+ variables = expression.scan(/<var!>(.+?)<\/var!>/i).flatten.uniq
91
+ for i in 0..(variables.count-1) do
92
+ values["v#{i}".to_sym] = JSON.parse(variables[i])
93
+ expression = expression.gsub("<var!>#{variables[i]}</var!>", ":v#{i}")
94
+ end
95
+ [expression, values]
96
+ end
97
+
98
+ # https://stackoverflow.com/questions/5826210/rails-order-with-nulls-last
99
+ def case_sort(field, sort_direction)
100
+ case sort_direction
101
+ when 'asc'
102
+ "#{field} asc"
103
+ when 'asc_nulls_first'
104
+ "CASE WHEN #{field} IS NOT NULL THEN 1 ELSE 0 END, #{field} ASC"
105
+ when 'asc_nulls_last'
106
+ "CASE WHEN #{field} IS NULL THEN 1 ELSE 0 END, #{field} ASC"
107
+ when 'desc'
108
+ "#{field} desc"
109
+ when 'desc_nulls_first'
110
+ "CASE WHEN #{field} IS NOT NULL THEN 1 ELSE 0 END, #{field} DESC"
111
+ when 'desc_nulls_last'
112
+ "CASE WHEN #{field} IS NULL THEN 1 ELSE 0 END, #{field} DESC"
113
+ else
114
+ ''
115
+ end
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,68 @@
1
+ require 'search_object/plugin/graphql'
2
+
3
+ class Resolvers::<%= plural_name_camelized %>Search < Resolvers::BaseSearchResolver
4
+
5
+ # scope is starting point for search
6
+ scope { <%= singular_name_camelized %>.all }
7
+
8
+ type types[Types::<%= singular_name_camelized %>Type]
9
+
10
+ ### Pagination
11
+ option :first, type: types.Int, with: :apply_first
12
+ option :skip, type: types.Int, with: :apply_skip
13
+
14
+ def apply_first(scope, value)
15
+ scope.limit(value)
16
+ end
17
+
18
+ def apply_skip(scope, value)
19
+ scope.offset(value)
20
+ end
21
+
22
+ ### Order by
23
+ class <%= plural_name_camelized %>OrderBy < ::Types::BaseInputObject
24
+ argument :field, Types::Enums::<%= plural_name_camelized %>Field, required: false
25
+ argument :sortDirection, Types::Enums::SortDir, required: false
26
+ end
27
+
28
+ option :sort_by, type: types[<%= plural_name_camelized %>OrderBy], with: :apply_sort_by
29
+
30
+ def apply_sort_by(scope, value)
31
+ scope.order( value.map{|arg|
32
+ Types::Enums::<%= plural_name_camelized %>Field.values.include?(arg[:field]) ? case_sort(arg[:field], arg[:sort_direction]) : ''
33
+ }.join(', ') )
34
+ end
35
+
36
+
37
+ ### Distinct
38
+ option :distinct_on, type: types[Types::Enums::<%= plural_name_camelized %>Field], with: :apply_distinct_on
39
+
40
+ def apply_distinct_on(scope, value)
41
+ scope.select("distinct on (#{value.uniq.join(', ')}) *")
42
+ end
43
+
44
+
45
+ ### Booleans
46
+ <% for attribute in columns_types -%>
47
+ <% if attribute[:type] == 'Boolean' -%>
48
+ option(:<%= attribute[:name] %>, type: types.Boolean, description: 'Filter for <%= attribute[:name] %>') { |scope, value| scope.where(<%= attribute[:name] %>: value)}
49
+ <% end -%>
50
+ <% end -%>
51
+
52
+ ### Filter
53
+ class <%= plural_name_camelized %>WhereExp < ::Types::BaseInputObject
54
+ argument :_or, [self], required: false
55
+ argument :_and, [self], required: false
56
+ argument :_not, [self], required: false
57
+ argument :field, Types::Enums::<%= plural_name_camelized %>Field, required: false
58
+ argument :operator, Types::Enums::Operator, required: false
59
+ argument :value, [String], required: false
60
+ end
61
+
62
+ option :where, type: types[<%= plural_name_camelized %>WhereExp], with: :apply_filter
63
+
64
+ def apply_filter(scope, value)
65
+ where == '()' ? scope : scope.where(query_where(value))
66
+ end
67
+
68
+ end
@@ -0,0 +1,7 @@
1
+ module Types
2
+ class BaseField < GraphQL::Schema::Field
3
+ def resolve_field(obj, args, ctx)
4
+ resolve(obj, args, ctx)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ Types::DateTimeType = GraphQL::ScalarType.define do
2
+ name 'DateTime'
3
+
4
+ coerce_input ->(value, _ctx) { Time.zone.parse(value) }
5
+ coerce_result ->(value, _ctx) { value.utc.iso8601 }
6
+ end
@@ -0,0 +1,22 @@
1
+ module Types
2
+ module Enums
3
+ class Operator < Types::BaseEnum
4
+ description 'Comparison Operators'
5
+ value('equal', 'Equal')
6
+ value('greater_than', 'Greater than')
7
+ value('greater_than_or_equal_to', 'Greater than or equal')
8
+ value('ilike', 'Case insensitive Text search')
9
+ value('in', 'In list')
10
+ value('is_null', 'Is Null')
11
+ value('like', 'Case sensitive Text search')
12
+ value('less_than', 'Less than')
13
+ value('less_than_or_equal_to', 'Less than or equal')
14
+ value('not_equal', 'Not Equal')
15
+ value('not_ilike', 'Case insensitive Text search negative')
16
+ value('not_in', 'Not in list')
17
+ value('not_like', 'Case sensitive Text search negative')
18
+ value('not_similar', 'Not Similar to Regular Expressions')
19
+ value('similar', 'Similar to Regular Expressions')
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Types
2
+ module Enums
3
+ class SortDir < Types::BaseEnum
4
+ description 'Column ordering options'
5
+ value('asc', 'in the ascending order, nulls last')
6
+ value('asc_nulls_first', 'in the ascending order, nulls first')
7
+ value('asc_nulls_last', 'in the ascending order, nulls last')
8
+ value('desc', 'in the descending order, nulls last')
9
+ value('desc_nulls_first', 'in the descending order, nulls first')
10
+ value('desc_nulls_last', 'in the descending order, nulls last')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module Types
2
+ module Enums
3
+ class <%= plural_name_camelized %>Field < Types::BaseEnum
4
+ description 'Fields from table <%= plural_name %>'
5
+ <% for attribute in columns_types -%>
6
+ value("<%= attribute[:name] %>")
7
+ <% end -%>
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class Types::<%= singular_name_camelized %>Type < Types::BaseObject
2
+ <% for attribute in columns_types -%>
3
+ field :<%= attribute[:name] %>, <%= attribute[:name] == primary_key ? 'ID' : attribute[:type] %>, null: <%= attribute[:null] %>
4
+ <% end -%>
5
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ class Graphql1stTest < ActionDispatch::IntegrationTest
4
+ test "can see the graphql endpoint" do
5
+ post '/graphql', params: {}
6
+ assert_response :success
7
+ assert_equal response.body, '{"errors":[{"message":"No query string was present"}]}'
8
+ end
9
+ end
@@ -0,0 +1,119 @@
1
+ require 'test_helper'
2
+
3
+ class Graphql<%= plural_name_camelized %>Test < ActionDispatch::IntegrationTest
4
+ setup do
5
+ @<%= singular_name_snaked %> = <%= plural_name_snaked %>(:one)
6
+ end
7
+
8
+ test "can see a result" do
9
+ gql = <<-GRAPHQL
10
+ {
11
+ <%= list_many %>(first: 10, sort_by: {field: <%= columns_type_without_primary_key.sample[:name] %>, sortDirection: asc}) {
12
+ <% for attribute in columns_types -%>
13
+ <%= attribute[:name] %>
14
+ <% end -%>
15
+ }
16
+ }
17
+ GRAPHQL
18
+
19
+ post '/graphql', params: {query: gql}
20
+ assert_response :success
21
+ json = JSON.parse(response.body)
22
+ assert_not_empty json['data']
23
+ assert_not_empty json['data']['<%= list_many %>']
24
+ end
25
+
26
+ test "can add a record" do
27
+ gql = <<-GRAPHQL
28
+ mutation a {
29
+ <%= create_one %>(input: {
30
+ <% for attribute in columns_type_without_primary_key -%>
31
+ <%= attribute[:name] %>: "<%= attribute[:sample] %>",
32
+ <% end -%>
33
+ clientMutationId: "test-1"
34
+ } )
35
+ {
36
+ <%= singular_name_snaked %> {
37
+ <% for attribute in columns_types -%>
38
+ <%= attribute[:name] %>
39
+ <% end -%>
40
+ }
41
+ clientMutationId
42
+ errors
43
+ }
44
+ }
45
+ GRAPHQL
46
+ post '/graphql', params: {query: gql}
47
+ assert_response :success
48
+ json = JSON.parse(response.body)
49
+ #puts response.body
50
+
51
+ assert_not_empty json['data']
52
+ assert_not_empty json['data']['<%= create_one %>']
53
+ assert_not_empty json['data']['<%= create_one %>']['<%= singular_name_snaked %>']
54
+ assert_empty json['data']['<%= create_one %>']['errors']
55
+
56
+ <%= primary_key %> = json['data']['<%= create_one %>']['<%= singular_name_snaked %>']['<%= primary_key %>']
57
+ assert_not_empty <%= singular_name_camelized %>.where(<%= primary_key %>: <%= primary_key %>)
58
+ end
59
+
60
+ test "can change a record" do
61
+ <%= primary_key %> = <%= singular_name_camelized %>.last.<%= primary_key %>
62
+ gql = <<-GRAPHQL
63
+ mutation a {
64
+ <%= change_one %>(input: {
65
+ <%= primary_key %>: "#{<%= primary_key %>}",
66
+ <% for attribute in columns_type_without_primary_key -%>
67
+ <%= attribute[:name] %>: "<%= attribute[:sample] %>",
68
+ <% end -%>
69
+ clientMutationId: "test-2"} )
70
+ {
71
+ <%= singular_name_snaked %> {
72
+ <% for attribute in columns_types -%>
73
+ <%= attribute[:name] %>
74
+ <% end -%>
75
+ }
76
+ clientMutationId
77
+ errors
78
+ }
79
+ }
80
+ GRAPHQL
81
+
82
+ post '/graphql', params: {query: gql}
83
+ assert_response :success
84
+ json = JSON.parse(response.body)
85
+ assert_not_empty json['data']
86
+ assert_not_empty json['data']['<%= change_one %>']
87
+ assert_empty json['data']['<%= change_one %>']['errors']
88
+
89
+ assert_equal <%= singular_name_camelized %>.find(id).url, json['data']['<%= change_one %>']['<%= singular_name_snaked %>']['url']
90
+ end
91
+
92
+ test "can destroy a record" do
93
+ <%= primary_key %> = <%= singular_name_camelized %>.last.<%= primary_key %>
94
+ gql = <<-GRAPHQL
95
+ mutation a {
96
+ <%= destroy_one %>(input: {<%= primary_key %>: "#{<%= primary_key %>}", clientMutationId: "test-3"} )
97
+ {
98
+ <%= singular_name_snaked %> {
99
+ <% for attribute in columns_types -%>
100
+ <%= attribute[:name] %>
101
+ <% end -%>
102
+ }
103
+ clientMutationId
104
+ errors
105
+ }
106
+ }
107
+ GRAPHQL
108
+
109
+ post '/graphql', params: {query: gql}
110
+ assert_response :success
111
+ json = JSON.parse(response.body)
112
+ assert_not_empty json['data']
113
+ assert_not_empty json['data']['<%= destroy_one %>']
114
+ assert_empty json['data']['<%= destroy_one %>']['errors']
115
+
116
+ assert_empty <%= singular_name_camelized %>.where(<%= primary_key %>: <%= primary_key %>)
117
+ end
118
+
119
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlScaffold
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql_scaffold/version'
4
+
5
+ module GraphqlScaffold
6
+ end
data/readme.md ADDED
@@ -0,0 +1,36 @@
1
+ # graphql scaffold <img src="https://cloud.githubusercontent.com/assets/2231765/9094460/cb43861e-3b66-11e5-9fbf-71066ff3ab13.png" height="40" alt="graphql-ruby"/>
2
+
3
+ [![Build Status](https://travis-ci.org/arthurmolina/graphql-ruby.svg?branch=master)](https://travis-ci.org/arthurmolina/graphql_scaffold)
4
+ [![Gem Version](https://badge.fury.io/rb/graphql_scaffold.svg)](https://rubygems.org/gems/graphql_scaffold)
5
+ [![GitHub](https://img.shields.io/github/license/arthurmolina/graphql_scaffold)](https://img.shields.io/github/license/arthurmolina/graphql_scaffold)
6
+
7
+ Rails generator for scaffolding models with [GraphQL-Ruby](https://github.com/rmosolgo/graphql-ruby/).
8
+
9
+ ## Installation
10
+
11
+ Install from RubyGems by adding it to your `Gemfile`, then bundling.
12
+
13
+ ```ruby
14
+ # Gemfile
15
+ gem 'graphql_scaffold'
16
+ ```
17
+
18
+ ```
19
+ $ bundle install
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ If you have a model already created:
25
+
26
+ ```
27
+ $ rails generate graphql_scaffold model_example
28
+ ```
29
+
30
+ If you want to create a model and scaffold your Graphql API:
31
+
32
+ ```
33
+ $ rails generate graphql_scaffold model_example field1 field2:integer field3
34
+ ```
35
+
36
+ After this, you may need to run `rails db:migrate`. The format for fields are the same as rails generate model.
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql_scaffold
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Arthur Molina
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-11-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - arthurmolina@yahoo.com.br
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - lib/generators/graphql_scaffold_common_methods.rb
22
+ - lib/generators/graphql_scaffold_generator.rb
23
+ - lib/generators/templates/app/graphql/mutations/base_mutation.rb
24
+ - lib/generators/templates/app/graphql/mutations/change_table.rb
25
+ - lib/generators/templates/app/graphql/mutations/create_table.rb
26
+ - lib/generators/templates/app/graphql/mutations/destroy_table.rb
27
+ - lib/generators/templates/app/graphql/resolvers/base_search_resolver.rb
28
+ - lib/generators/templates/app/graphql/resolvers/table_search.rb
29
+ - lib/generators/templates/app/graphql/types/base_field.rb
30
+ - lib/generators/templates/app/graphql/types/date_time_type.rb
31
+ - lib/generators/templates/app/graphql/types/enums/operator.rb
32
+ - lib/generators/templates/app/graphql/types/enums/sort_dir.rb
33
+ - lib/generators/templates/app/graphql/types/enums/table_field.rb
34
+ - lib/generators/templates/app/graphql/types/table_type.rb
35
+ - lib/generators/templates/test/integration/graphql_1st_test.rb
36
+ - lib/generators/templates/test/integration/graphql_table_test.rb
37
+ - lib/graphql_scaffold.rb
38
+ - lib/graphql_scaffold/version.rb
39
+ - readme.md
40
+ homepage: https://arthurmolina.com
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ - lib/generators
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.0.6
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: A good way to automatize graphql models
64
+ test_files: []