rails-graphql 1.0.0.rc1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/lib/generators/graphql/templates/config.rb +2 -2
  4. data/lib/gql_parser.so +0 -0
  5. data/lib/rails/graphql/alternative/query.rb +4 -4
  6. data/lib/rails/graphql/alternative.rb +4 -0
  7. data/lib/rails/graphql/callback.rb +2 -1
  8. data/lib/rails/graphql/config.rb +18 -2
  9. data/lib/rails/graphql/directive.rb +1 -1
  10. data/lib/rails/graphql/field/proxied_field.rb +1 -1
  11. data/lib/rails/graphql/helpers/inherited_collection/base.rb +1 -1
  12. data/lib/rails/graphql/helpers/with_assignment.rb +5 -5
  13. data/lib/rails/graphql/helpers/with_fields.rb +1 -1
  14. data/lib/rails/graphql/helpers/with_schema_fields.rb +3 -3
  15. data/lib/rails/graphql/railties/controller.rb +10 -6
  16. data/lib/rails/graphql/request/component/operation.rb +1 -1
  17. data/lib/rails/graphql/request.rb +4 -4
  18. data/lib/rails/graphql/source/active_record/builders.rb +3 -4
  19. data/lib/rails/graphql/source/active_record_source.rb +5 -3
  20. data/lib/rails/graphql/source/base.rb +3 -2
  21. data/lib/rails/graphql/source/builder.rb +9 -4
  22. data/lib/rails/graphql/source.rb +8 -1
  23. data/lib/rails/graphql/type/creator.rb +8 -6
  24. data/lib/rails/graphql/type/enum.rb +16 -2
  25. data/lib/rails/graphql/type/input.rb +10 -3
  26. data/lib/rails/graphql/type/object.rb +1 -1
  27. data/lib/rails/graphql/type/scalar/date_scalar.rb +1 -1
  28. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +1 -1
  29. data/lib/rails/graphql/type/scalar/time_scalar.rb +1 -1
  30. data/lib/rails/graphql/type/scalar.rb +1 -1
  31. data/lib/rails/graphql/type_map.rb +7 -5
  32. data/lib/rails/graphql/version.rb +1 -1
  33. data/lib/rails/graphql.rb +2 -1
  34. data/test/assets/mysql.gql +3 -3
  35. data/test/assets/sqlite.gql +3 -3
  36. data/test/graphql/source_test.rb +1 -1
  37. data/test/graphql/type/enum_test.rb +15 -0
  38. data/test/integration/global_id_test.rb +2 -2
  39. data/test/integration/resolver_precedence_test.rb +1 -1
  40. data/test/integration/schemas/mysql.rb +1 -0
  41. data/test/integration/sqlite/star_wars_global_id_test.rb +1 -1
  42. data/test/test_ext.rb +6 -0
  43. metadata +6 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4567d054599bcd2c6c93008cd8e82cad8259bf15db57a508eb5ebc671faf64e1
4
- data.tar.gz: d75085b1ccf8125a31a442369b13628bab65cd6aaa7b8c48ebb85016156a19d4
3
+ metadata.gz: 38fafc81ae4e9f8c11c4d1495b8290656dcf388ac0b6d5536829e0f77ee09bb2
4
+ data.tar.gz: 8a8298e375df5ac6905b24deeacf29ca2fd7b4dc39dfb43601ba2331828ac939
5
5
  SHA512:
6
- metadata.gz: f7349679b342745edc2bbf8c89b2c1c3687db9331de6883d042eb1523c3a77566cc0485382b5f1eb6dc3656cee4aed7b77f22ce268417b77564f702185415601
7
- data.tar.gz: 38dc53be74565810ce48c474c3c4ea2ceb01cdbb947dfe771647032f7cdc314cb645daf226b32f3d2d62b2f4fe62db3cfab1815481d218b9ac24e37c4c47a203
6
+ metadata.gz: fa92f53a2111c001863967bca893a25bab09f9a9c163af945631a15054c865db2e9102467e8c85ac1c3cbc0591c377a97f98c2439bdc94c72694d72cbef99e05
7
+ data.tar.gz: d994c9ff91179fbd72ecb3afde6f3bb005805697ebe994cfcc88b2bc9720e30f16ac76f6dab81b65760ad3e61af5db427d9a5e9118f6222b756df00c35b499d7
data/Rakefile CHANGED
@@ -2,7 +2,7 @@
2
2
  # Rake tasks for development purposes
3
3
 
4
4
  begin
5
- require 'bundler/setup'
5
+ require 'bundler/setup' unless ENV.key?('CI_COMPILE')
6
6
  rescue LoadError
7
7
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
8
8
  end
@@ -40,7 +40,7 @@ Rails::GraphQL.configure do |config|
40
40
  # Introspection is enabled by default. It is recommended to only use
41
41
  # introspection during development and tests, never in production.
42
42
  # This can also be set per schema level.
43
- config.enable_introspection = true
43
+ config.enable_introspection = !Rails.env.production?
44
44
 
45
45
  # Define the names of the schema/operations types. The single "_" is a
46
46
  # suggestion. In an application that has a Subscription object, it will
@@ -93,5 +93,5 @@ Rails::GraphQL.configure do |config|
93
93
  # behaves closer to YAML. The received value is ensured to be wrapped in
94
94
  # "{}". If that produces unexpected results, you can assign a proc and then
95
95
  # parse the value in any other way.
96
- # config.literal_input_parser = JSON.method(:parse)
96
+ # config.literal_input_parser = Psych.method(:safe_load)
97
97
  end
data/lib/gql_parser.so CHANGED
Binary file
@@ -15,9 +15,9 @@ module Rails
15
15
  self.abstract = true
16
16
 
17
17
  class << self
18
- delegate :gql_name, :to_sym, :desc, :argument, :ref_argument, :id_argument,
19
- :use, :internal?, :disabled?, :enabled?, :disable!, :enable!, :rename!,
20
- :authorize, :on, :description=, :description, to: :field
18
+ delegate :gql_name, :to_sym, :desc, :arguments, :argument, :ref_argument, :id_argument,
19
+ :use, :internal?, :disabled?, :enabled?, :disable!, :enable!, :rename!, :authorize,
20
+ :on, :description=, :description, to: :field
21
21
 
22
22
  # Returns the type of the field class
23
23
  def type_field_class
@@ -85,7 +85,7 @@ module Rails
85
85
  def field_class
86
86
  return @field.class if defined?(@field)
87
87
  list = Helpers::WithSchemaFields::TYPE_FIELD_CLASS
88
- GraphQL::Field.const_get(list[type_field_class])
88
+ GraphQL::Field.const_get(list[type_field_class], false)
89
89
  end
90
90
  end
91
91
  end
@@ -10,6 +10,10 @@ module Rails
10
10
  autoload :Mutation
11
11
  autoload :Subscription
12
12
 
13
+ autoload_at "#{__dir__}/alternative/query" do
14
+ autoload :Field
15
+ end
16
+
13
17
  autoload_at "#{__dir__}/alternative/field_set" do
14
18
  autoload :FieldSet
15
19
  autoload :MutationSet
@@ -83,7 +83,8 @@ module Rails
83
83
  # Find the proper owner of the symbol based callback
84
84
  def owner
85
85
  @owner ||= target.all_owners.find do |item|
86
- item.is_a?(Class) ? item.method_defined?(block) : item.respond_to?(block)
86
+ item.is_a?(Class) && item.included_modules.include?(Helpers::Instantiable) &&
87
+ item.method_defined?(block)
87
88
  end || target
88
89
  end
89
90
 
@@ -29,8 +29,8 @@ module Rails
29
29
 
30
30
  # The list of nested paths inside of the graphql folder that does not
31
31
  # require to be in their own namespace.
32
- config.paths = %w[directives fields sources enums inputs interfaces object
33
- scalars unions].to_set
32
+ config.paths = %w[directives fields sources enums inputs interfaces objects
33
+ scalars unions concerns].to_set
34
34
 
35
35
  # This is very similar to `ActiveRecord` verbose logs, which simply show the
36
36
  # path of the file that started a GraphQL request.
@@ -61,6 +61,11 @@ module Rails
61
61
  # different suffix, change this value to it.
62
62
  config.auto_suffix_input_objects = 'Input'
63
63
 
64
+ # Set if the server should allow strings be used as input for ENUM inputs.
65
+ # It means that operations will support quotes for ENUM values embedded in
66
+ # the documents (heredoc won't be accepted).
67
+ config.allow_string_as_enum_input = false
68
+
64
69
  # Introspection is enabled by default. It is recommended to only use
65
70
  # introspection during development and tests, never in production.
66
71
  # This can also be set per schema level.
@@ -180,6 +185,17 @@ module Rails
180
185
  # parse the value in any other way.
181
186
  config.literal_input_parser = JSON.method(:parse)
182
187
 
188
+ # A mapping for the internal parameters and where they should be taken
189
+ # from. You can point to nested values using dot notation.
190
+ # TODO: Needs implementation
191
+ config.params_mapping = {
192
+ query: 'query',
193
+ variables: 'variables',
194
+ operation_name: 'operation_name',
195
+ query_cache_key: 'extensions.persistedQuery.sha256Hash',
196
+ query_cache_version: 'extensions.persistedQuery.version',
197
+ }
198
+
183
199
  # TODO: To be implemented
184
200
  # allow_query_serialization
185
201
  end
@@ -129,7 +129,7 @@ module Rails
129
129
  GraphQL.enumerate(setting).map do |item|
130
130
  next item unless item.is_a?(String) || item.is_a?(Symbol)
131
131
  GraphQL.type_map.fetch(item, namespaces: namespaces) ||
132
- ::GraphQL.const_get(item)
132
+ ::GraphQL.const_get(item, false)
133
133
  end
134
134
  end
135
135
 
@@ -72,7 +72,7 @@ module Rails
72
72
 
73
73
  # Override this to include proxied owners
74
74
  def all_owners
75
- super + proxied_owner.all_owners
75
+ super + @field.all_owners
76
76
  end
77
77
 
78
78
  # Return the proxied field
@@ -12,7 +12,7 @@ module Rails
12
12
  # Just a little helper to initialize the iterator form a given +source+
13
13
  def self.handle(source, ivar, type)
14
14
  klass = (type == :array || type == :set) ? :Array : :Hash
15
- InheritedCollection.const_get(klass).new(source, ivar, type)
15
+ InheritedCollection.const_get(klass, false).new(source, ivar, type)
16
16
  end
17
17
 
18
18
  def initialize(source, ivar, type)
@@ -61,12 +61,12 @@ module Rails
61
61
  return if abstract?
62
62
  return super unless assigned?
63
63
 
64
- result = super
65
- return result unless (klass = safe_assigned_class)
66
- return result if GraphQL.type_map.exist?(klass, namespaces: namespaces)
64
+ super.tap do
65
+ break unless (klass = safe_assigned_class)
66
+ break if GraphQL.type_map.exist?(klass, namespaces: namespaces)
67
67
 
68
- GraphQL.type_map.register_alias(klass, to_sym, namespaces: namespaces)
69
- result
68
+ GraphQL.type_map.register_alias(klass, gql_name, namespaces: namespaces)
69
+ end
70
70
  end
71
71
 
72
72
  protected
@@ -159,7 +159,7 @@ module Rails
159
159
  # Import a module containing several classes to be imported
160
160
  def import_all(mod, recursive: false, **xargs)
161
161
  mod.constants.each do |const_name|
162
- object = mod.const_get(const_name)
162
+ object = mod.const_get(const_name, false)
163
163
 
164
164
  import(object, **xargs) if object.is_a?(Class)
165
165
  import_all(object, recursive: recursive, **xargs) if recursive && object.is_a?(Module)
@@ -105,7 +105,7 @@ module Rails
105
105
  # Add a new field of the give +type+
106
106
  # See {OutputField}[rdoc-ref:Rails::GraphQL::OutputField] class.
107
107
  def add_field(type, *args, **xargs, &block)
108
- klass = Field.const_get(TYPE_FIELD_CLASS[type])
108
+ klass = Field.const_get(TYPE_FIELD_CLASS[type], false)
109
109
  object = klass.new(*args, **xargs, owner: self, &block)
110
110
 
111
111
  raise DuplicatedError, (+<<~MSG).squish if has_field?(type, object.name)
@@ -126,7 +126,7 @@ module Rails
126
126
  A #{field.schema_type} field cannot be added as a #{type} field.
127
127
  MSG
128
128
 
129
- klass = Field.const_get(TYPE_FIELD_CLASS[type])
129
+ klass = Field.const_get(TYPE_FIELD_CLASS[type], false)
130
130
  raise ArgumentError, (+<<~MSG).squish unless field.is_a?(klass)
131
131
  The #{field.class.name} is not a valid field for #{type} fields.
132
132
  MSG
@@ -236,7 +236,7 @@ module Rails
236
236
  # TODO: Maybe add deepness into the recursive value
237
237
  def import_all_into(type, mod, recursive: false, **xargs)
238
238
  mod.constants.each do |const_name|
239
- object = mod.const_get(const_name)
239
+ object = mod.const_get(const_name, false)
240
240
 
241
241
  import_into(type, object, **xargs) if object.is_a?(Class)
242
242
  import_all_into(type, object, recursive: recursive, **xargs) if recursive && object.is_a?(Module)
@@ -33,8 +33,12 @@ module Rails
33
33
  end
34
34
 
35
35
  # GET /describe
36
- def describe
37
- render plain: gql_schema_header + gql_describe_schema + gql_schema_footer
36
+ def describe(schema = gql_schema)
37
+ render plain: [
38
+ gql_schema_header(schema),
39
+ gql_describe_schema(schema),
40
+ gql_schema_footer,
41
+ ].join
38
42
  end
39
43
 
40
44
  # GET /graphiql
@@ -128,7 +132,7 @@ module Rails
128
132
  end
129
133
 
130
134
  # Shows a text representation of the schema
131
- def gql_describe_schema(schema = gql_schema)
135
+ def gql_describe_schema(schema)
132
136
  schema.to_gql(
133
137
  with_descriptions: !params.key?(:without_descriptions),
134
138
  with_spec: !params.key?(:without_spec),
@@ -137,9 +141,9 @@ module Rails
137
141
 
138
142
  # Print a header of the current schema for the description process
139
143
  # TODO: Maybe add a way to detect from which file the schema is being loaded
140
- def gql_schema_header
141
- ns = +" [#{gql_schema.namespace}]" if gql_schema.namespace != :base
142
- +"#{DESCRIBE_HEADER}# Schema #{gql_schema.name}#{ns}\n"
144
+ def gql_schema_header(schema)
145
+ ns = +" [#{schema.namespace}]" if schema.namespace != :base
146
+ +"#{DESCRIBE_HEADER}# Schema #{schema.name}#{ns}\n"
143
147
  end
144
148
 
145
149
  # Show the footer of the describe page
@@ -18,7 +18,7 @@ module Rails
18
18
 
19
19
  # Helper method to initialize an operation given the node
20
20
  def build(request, node)
21
- request.build(const_get(node.type.to_s.classify), request, node)
21
+ request.build(const_get(node.type.to_s.classify, false), request, node)
22
22
  end
23
23
 
24
24
  # Rewrite the kind to always return +:operation+
@@ -95,12 +95,12 @@ module Rails
95
95
 
96
96
  # Allow accessing component-based objects through the request
97
97
  def const_defined?(name, *)
98
- Component.const_defined?(name) || super
98
+ Component.const_defined?(name, false) || super
99
99
  end
100
100
 
101
101
  # Allow accessing component-based objects through the request
102
102
  def const_missing(name)
103
- Component.const_defined?(name) ? Component.const_get(name) : super
103
+ Component.const_defined?(name, false) ? Component.const_get(name, false) : super
104
104
  end
105
105
  end
106
106
 
@@ -476,13 +476,13 @@ module Rails
476
476
  modules.each do |mod|
477
477
  mod.constants.each do |const_name|
478
478
  const_name = const_name.to_s
479
- const = mod.const_get(const_name)
479
+ const = mod.const_get(const_name, false)
480
480
  next unless const.is_a?(Module)
481
481
 
482
482
  # Find the related request class to extend
483
483
  klass = const_name === 'Request' ? self.class : begin
484
484
  const_name.split('_').inject(self.class) do |k, next_const|
485
- k.const_defined?(next_const) ? k.const_get(next_const) : break
485
+ k.const_defined?(next_const) ? k.const_get(next_const, false) : break
486
486
  end
487
487
  end
488
488
 
@@ -66,7 +66,7 @@ module Rails
66
66
  return remove_instance_variable(:@enums) if enums.blank?
67
67
 
68
68
  @enums = enums.each_with_object({}) do |(attribute, setting), hash|
69
- class_name = base_name + attribute.to_s.classify
69
+ class_name = base_name.tr('_', '') + attribute.to_s.classify
70
70
  hash[attribute.to_s] = create_enum(class_name, setting)
71
71
  rescue DuplicatedError
72
72
  next
@@ -75,15 +75,14 @@ module Rails
75
75
 
76
76
  # Build all necessary attribute fields into the given +holder+
77
77
  def build_attribute_fields(holder, **field_options)
78
- each_attribute(holder) do |key, type, **options|
79
- next if holder.has_field?(key) || skip_field?(key, on: holder.kind)
80
78
 
79
+ each_attribute(holder) do |key, type, **options|
81
80
  str_key = key.to_s
82
81
  type = (defined?(@enums) && @enums.key?(str_key) && @enums[str_key]) ||
83
82
  (id_columns.include?(str_key) && :id) || type
84
83
 
85
84
  options[:null] = !attr_required?(key) unless options.key?(:null)
86
- holder.field(key, type, **options.merge(field_options[key] || {}))
85
+ holder.safe_field(key, type, **options.merge(field_options[key] || {}))
87
86
  end
88
87
  end
89
88
 
@@ -30,7 +30,7 @@ module Rails
30
30
 
31
31
  # Marks if the source should be threated as an interface, meaning that
32
32
  # no object will be created, instead an interface will
33
- class_attribute :act_as_interface, instance_accessor: false, default: false
33
+ class_attribute :act_as_interface, instance_accessor: false
34
34
 
35
35
  # The name of the class (or the class itself) to be used as superclass for
36
36
  # the generate GraphQL interface type of this source
@@ -125,7 +125,8 @@ module Rails
125
125
 
126
126
  # Set the assignment to a model with a similar name as the source
127
127
  def assigned_to
128
- @assigned_to ||= name.delete_prefix('GraphQL::')[0..-7]
128
+ return @assigned_to if defined?(@assigned_to)
129
+ @assigned_to = base_name.gsub('_', '::')
129
130
  end
130
131
 
131
132
  # Stores columns associated with enums so that the fields can have a
@@ -147,7 +148,8 @@ module Rails
147
148
 
148
149
  # Checks if the source is building an interface instead of an object
149
150
  def interface?
150
- defined?(@interface) || act_as_interface? || sti_interface?
151
+ defined?(@interface) || act_as_interface == true ||
152
+ (act_as_interface != false && sti_interface?)
151
153
  end
152
154
 
153
155
  # Provides access to the default plural query field, for associations interconnection
@@ -62,17 +62,18 @@ module Rails
62
62
  xargs[:values] = enumerator.sort_by(&:last).map(&:first)
63
63
  xargs[:indexed] = enumerator.first.last.is_a?(Numeric)
64
64
 
65
- create_type(:enum, enum_name.classify, **xargs, &block)
65
+ create_type(:enum, enum_name.to_s.classify, **xargs, &block)
66
66
  end
67
67
 
68
68
  # Helper method to create a class based on the given +type+ and
69
69
  # allows several other settings to be executed on it
70
- def create_type(type = nil, name = base_name, **xargs, &block)
70
+ def create_type(type = nil, name = nil, **xargs, &block)
71
71
  xargs[:owner] ||= self
72
72
  xargs[:namespaces] = namespaces
73
73
  xargs[:assigned_to] = safe_assigned_class
74
74
  superclass = xargs.delete(:superclass) || type
75
75
 
76
+ name ||= base_name.tr('_', '')
76
77
  GraphQL::Type.create!(self, name, superclass, **xargs, &block)
77
78
  end
78
79
 
@@ -21,13 +21,13 @@ module Rails
21
21
  build_all! unless abstract?
22
22
  end
23
23
 
24
- # Allows building anything that is in hooks
24
+ # Make sure to properly indicate about build methods
25
25
  def respond_to_missing?(method_name, *)
26
26
  return super unless method_name.to_s.start_with?('build_') &&
27
27
  hook_names.include?(method_name.to_s[6..-1].to_sym)
28
28
  end
29
29
 
30
- # Allow fast creation of values
30
+ # Allows all sorts of building methods to be called
31
31
  def method_missing(method_name, *args, **xargs, &block)
32
32
  return super unless method_name.to_s.start_with?('build_')
33
33
 
@@ -47,6 +47,11 @@ module Rails
47
47
  @built ||= Set.new
48
48
  end
49
49
 
50
+ # Mark as built before running the hooks
51
+ def run_hooks(type, *)
52
+ built.add(type)
53
+ end
54
+
50
55
  private
51
56
 
52
57
  # Import all options-based settings for skipping field
@@ -93,7 +98,6 @@ module Rails
93
98
  # Build all the objects associated with this source
94
99
  def build!(type)
95
100
  ensure_build!(type)
96
- built << type
97
101
 
98
102
  schema_type = Helpers::WithSchemaFields::TYPE_FIELD_CLASS.key?(type)
99
103
  catch(:skip) { run_hooks(:start) } unless built?(:start)
@@ -106,7 +110,8 @@ module Rails
106
110
  def hook_scope_for(type, schema_type)
107
111
  type = type.to_sym
108
112
  klass = schema_type ? 'WithSchemaFields::ScopedConfig' : 'AttributeDelegator'
109
- Source::ScopedConfig.new(self, Helpers.const_get(klass).new(self, type), type)
113
+ klass = Helpers.const_get(klass, false).new(self, type)
114
+ Source::ScopedConfig.new(self, klass, type)
110
115
  end
111
116
 
112
117
  end
@@ -85,7 +85,13 @@ module Rails
85
85
 
86
86
  # Get the main name of the source
87
87
  def base_name
88
- name.demodulize[0..-7]
88
+ @base_name ||= begin
89
+ nested = "::#{Type::Creator::NESTED_MODULE}::"
90
+
91
+ value = name.delete_prefix('GraphQL::')
92
+ value = name.split(nested).last if name.include?(nested)
93
+ value.chomp('Source')
94
+ end
89
95
  end
90
96
 
91
97
  # :singleton-method:
@@ -214,6 +220,7 @@ module Rails
214
220
 
215
221
  # Run a list of hooks using the +source+ as the instance of the block
216
222
  def run_hooks(hook_name, source = self)
223
+ super
217
224
  all_hooks.try(:[], hook_name.to_sym)&.reverse_each do |block|
218
225
  source.instance_exec(&block)
219
226
  end
@@ -127,10 +127,10 @@ module Rails
127
127
 
128
128
  # Create the class under the nested module
129
129
  return base.const_set(name, Class.new(superclass)) \
130
- unless base.const_defined?(name)
130
+ unless base.const_defined?(name, false)
131
131
 
132
132
  # Get the existing class and check for the once setting
133
- klass = base.const_get(name)
133
+ klass = base.const_get(name, false)
134
134
  return klass unless !once? && klass < superclass
135
135
 
136
136
  # Created once or not from the same superclass
@@ -141,7 +141,7 @@ module Rails
141
141
 
142
142
  # Make sure to properly get the superclass
143
143
  def sanitize_superclass(value)
144
- value = Type.const_get(value.to_s.classify) unless value.is_a?(Class)
144
+ value = Type.const_get(value.to_s.classify, false) unless value.is_a?(Class)
145
145
 
146
146
  valid_class = value.is_a?(Class) && value.respond_to?(:kind)
147
147
  valid_class &= SUPPORTED_KINDS.include?(value.kind)
@@ -155,7 +155,7 @@ module Rails
155
155
  # Let's clean up the name
156
156
  def sanitize_name(name_or_object)
157
157
  name = name_or_object.is_a?(Module) ? name_or_object.name : name_or_object.to_s
158
- name = name.classify.gsub(/::/, '_')
158
+ name = name.classify.delete_prefix('GraphQL::').gsub(/::/, '_')
159
159
  name.end_with?(name_suffix) ? name : name + name_suffix
160
160
  end
161
161
 
@@ -174,9 +174,11 @@ module Rails
174
174
  def base_module
175
175
  base = @from.is_a?(Module) ? @from : @from.class
176
176
  if base.const_defined?(NESTED_MODULE, false)
177
- base.const_get(NESTED_MODULE)
177
+ base.const_get(NESTED_MODULE, false)
178
178
  else
179
- base.const_set(NESTED_MODULE, Module.new)
179
+ base.const_set(NESTED_MODULE, Module.new).tap do
180
+ base.private_constant(NESTED_MODULE)
181
+ end
180
182
  end
181
183
  end
182
184
 
@@ -39,7 +39,9 @@ module Rails
39
39
  # Check if a given value is a valid non-deserialized input
40
40
  def valid_input?(value)
41
41
  (valid_token?(value, :enum) && all_values.include?(value.to_s)) ||
42
- (value.is_a?(String) && all_values.include?(value))
42
+ (value.is_a?(String) && all_values.include?(value)) ||
43
+ (allow_string_input? && valid_token?(value, :string) &&
44
+ all_values.include?(value.to_s[1..-2]))
43
45
  end
44
46
 
45
47
  # Check if a given value is a valid non-serialized output
@@ -62,7 +64,13 @@ module Rails
62
64
 
63
65
  # Turn a user input of this given type into an ruby object
64
66
  def deserialize(value)
65
- new(value.is_a?(::GQLParser::Token) ? value.to_s : value) if valid_input?(value)
67
+ if valid_token?(value, :enum)
68
+ new(value.to_s)
69
+ elsif allow_string_input? && valid_token?(value, :string)
70
+ new(value[1..-2])
71
+ elsif valid_input?(value)
72
+ new(value)
73
+ end
66
74
  end
67
75
 
68
76
  # Use the instance as decorator
@@ -142,6 +150,12 @@ module Rails
142
150
  #{inspect_directives}
143
151
  INFO
144
152
  end
153
+
154
+ private
155
+
156
+ def allow_string_input?
157
+ GraphQL.config.allow_string_as_enum_input
158
+ end
145
159
  end
146
160
 
147
161
  attr_reader :value
@@ -114,7 +114,7 @@ module Rails
114
114
  delegate_missing_to :resource
115
115
 
116
116
  def initialize(args = nil, **xargs)
117
- @args = args || OpenStruct.new(xargs.transform_keys { |key| key.to_s.underscore })
117
+ @args = args || build_ostruct(xargs)
118
118
  @args.freeze
119
119
 
120
120
  validate! if args.nil?
@@ -135,7 +135,7 @@ module Rails
135
135
 
136
136
  # Just return the arguments as an hash
137
137
  def params
138
- parametrize(self)
138
+ parametrize(@args.to_h)
139
139
  end
140
140
 
141
141
  # Correctly turn all the arguments into their +as_json+ version and
@@ -184,12 +184,19 @@ module Rails
184
184
  end
185
185
  end
186
186
 
187
+ protected
188
+
189
+ # A helper to turn a hash into a proper Open Struct instance
190
+ def build_ostruct(hash)
191
+ OpenStruct.new(hash.transform_keys { |key| key.to_s.underscore })
192
+ end
193
+
187
194
  private
188
195
 
189
196
  # Make sure to turn inputs into params
190
197
  def parametrize(input)
191
198
  case input
192
- when Type::Input then parametrize(input.args.to_h)
199
+ when Type::Input then input.params
193
200
  when Array then input.map(&method(:parametrize))
194
201
  when Hash then input.transform_values(&method(:parametrize))
195
202
  else input
@@ -77,7 +77,7 @@ module Rails
77
77
 
78
78
  def inspect
79
79
  return super if self.eql?(Type::Object)
80
- fields = @fields.values.map(&:inspect)
80
+ fields = @fields&.values&.map(&:inspect)
81
81
  fields = fields.presence && +" {#{fields.join(', ')}}"
82
82
 
83
83
  directives = inspect_directives
@@ -7,7 +7,7 @@ module Rails
7
7
  class Scalar::DateScalar < Scalar
8
8
  desc 'The Date scalar type represents a ISO 8601 string value.'
9
9
 
10
- use :specified_by, url: 'https://www.rfc-editor.org/rfc/rfc3339'
10
+ use :specified_by, url: 'https://en.wikipedia.org/wiki/ISO_8601'
11
11
 
12
12
  class << self
13
13
  def valid_input?(value)
@@ -9,7 +9,7 @@ module Rails
9
9
 
10
10
  desc 'The DateTime scalar type represents a ISO 8601 string value.'
11
11
 
12
- use :specified_by, url: 'https://www.rfc-editor.org/rfc/rfc3339'
12
+ use :specified_by, url: 'https://en.wikipedia.org/wiki/ISO_8601'
13
13
 
14
14
  class << self
15
15
  def valid_input?(value)
@@ -13,7 +13,7 @@ module Rails
13
13
  minutes, seconds, and milliseconds.
14
14
  MSG
15
15
 
16
- use :specified_by, url: 'https://www.rfc-editor.org/rfc/rfc3339'
16
+ use :specified_by, url: 'https://en.wikipedia.org/wiki/ISO_8601'
17
17
 
18
18
  # A +base_object+ helps to identify what methods are actually available
19
19
  # to work as resolvers
@@ -48,7 +48,7 @@ module Rails
48
48
 
49
49
  # Transforms the given value to its representation in a JSON string
50
50
  def to_json(value)
51
- as_json(value)&.inspect
51
+ as_json(value).inspect
52
52
  end
53
53
 
54
54
  # Transforms the given value to its representation in a Hash object
@@ -19,6 +19,7 @@ module Rails
19
19
  extend ActiveSupport::Autoload
20
20
 
21
21
  FILTER_REGISTER_TRACE = /((inherited|initialize)'$|schema\.rb:\d+)/.freeze
22
+ NESTED_MODULE = Type::Creator::NESTED_MODULE
22
23
 
23
24
  # Store all the base classes that are managed by the Type Map
24
25
  mattr_accessor :base_classes, instance_writer: false,
@@ -97,6 +98,8 @@ module Rails
97
98
  ns = @module_namespaces[mod]
98
99
  break ns unless ns.nil?
99
100
  end
101
+ rescue ::NameError
102
+ # If any module parent can't be found, there is no much we can do
100
103
  end
101
104
 
102
105
  # Register a given object, which must be a class where the namespaces and
@@ -144,7 +147,6 @@ module Rails
144
147
  # Unregister all the provided objects by simply assigning nil to their
145
148
  # final value on the index
146
149
  def unregister(*objects)
147
- sub_mod = Type::Creator::NESTED_MODULE
148
150
  objects.each do |object|
149
151
  namespaces = sanitize_namespaces(namespaces: object.namespaces, exclusive: true)
150
152
  namespaces << :base if namespaces.empty?
@@ -155,11 +157,11 @@ module Rails
155
157
  @objects -= 1
156
158
  end
157
159
 
158
- return unless object.const_defined?(sub_mod, false)
160
+ return unless object.const_defined?(NESTED_MODULE, false)
159
161
 
160
- nested_mod = object.const_get(sub_mod)
161
- unregister(*nested_mod.constants.map(&nested_mod.method(:const_get)))
162
- object.send(:remove_const, sub_mod)
162
+ nested_mod = object.const_get(NESTED_MODULE, false)
163
+ nested_mod.constants.each { |name| nested_mod.const_get(name, false).unregister! }
164
+ object.send(:remove_const, NESTED_MODULE)
163
165
  end
164
166
  end
165
167
 
@@ -15,7 +15,7 @@ module Rails
15
15
  MAJOR = 1
16
16
  MINOR = 0
17
17
  TINY = 0
18
- PRE = 'rc1'
18
+ PRE = nil
19
19
 
20
20
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
21
21
  end
data/lib/rails/graphql.rb CHANGED
@@ -7,6 +7,7 @@ require 'active_support'
7
7
 
8
8
  require 'active_support/core_ext/module/attribute_accessors_per_thread'
9
9
  require 'active_support/core_ext/string/strip'
10
+ require 'active_support/core_ext/enumerable'
10
11
 
11
12
  require 'rails/graphql/version'
12
13
  require 'rails/graphql/uri'
@@ -40,7 +41,7 @@ module Rails
40
41
  # * <tt>Directives:</tt> A directive definition belongs to the introspection
41
42
  # and is handled in the Singleton extent. They are handled as instance
42
43
  # whenever a definition or an execution uses them.
43
- # * <tt>Fields:</tt> Many other types and helper containers holds a serie of
44
+ # * <tt>Fields:</tt> Many other types and helper containers holds a series of
44
45
  # fields, which means that fields with the same name will probably behave
45
46
  # differently.
46
47
  module GraphQL
@@ -22,10 +22,10 @@ scalar Binary @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3548")
22
22
  scalar Boolean
23
23
 
24
24
  "The Date scalar type represents a ISO 8601 string value."
25
- scalar Date @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339")
25
+ scalar Date @specifiedBy(url: "https://en.wikipedia.org/wiki/ISO_8601")
26
26
 
27
27
  "The DateTime scalar type represents a ISO 8601 string value."
28
- scalar DateTime @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339")
28
+ scalar DateTime @specifiedBy(url: "https://en.wikipedia.org/wiki/ISO_8601")
29
29
 
30
30
  """
31
31
  The Decimal scalar type represents signed fractional values with extra precision.
@@ -61,7 +61,7 @@ scalar String
61
61
  The Time scalar type that represents a distance in time using hours,
62
62
  minutes, seconds, and milliseconds.
63
63
  """
64
- scalar Time @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339")
64
+ scalar Time @specifiedBy(url: "https://en.wikipedia.org/wiki/ISO_8601")
65
65
 
66
66
  "The valid locations that a directive may be placed."
67
67
  enum __DirectiveLocation {
@@ -22,10 +22,10 @@ scalar Binary @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3548")
22
22
  scalar Boolean
23
23
 
24
24
  "The Date scalar type represents a ISO 8601 string value."
25
- scalar Date @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339")
25
+ scalar Date @specifiedBy(url: "https://en.wikipedia.org/wiki/ISO_8601")
26
26
 
27
27
  "The DateTime scalar type represents a ISO 8601 string value."
28
- scalar DateTime @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339")
28
+ scalar DateTime @specifiedBy(url: "https://en.wikipedia.org/wiki/ISO_8601")
29
29
 
30
30
  """
31
31
  The Decimal scalar type represents signed fractional values with extra precision.
@@ -61,7 +61,7 @@ scalar String
61
61
  The Time scalar type that represents a distance in time using hours,
62
62
  minutes, seconds, and milliseconds.
63
63
  """
64
- scalar Time @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339")
64
+ scalar Time @specifiedBy(url: "https://en.wikipedia.org/wiki/ISO_8601")
65
65
 
66
66
  "The valid locations that a directive may be placed."
67
67
  enum __DirectiveLocation {
@@ -25,7 +25,7 @@ class GraphQL_SourceTest < GraphQL::TestCase
25
25
 
26
26
  def test_base_name
27
27
  described_class.stub(:abstract?, false) do
28
- assert_equal('DESCRIBED', described_class.base_name)
28
+ assert_equal('DESCRIBED_CLASS', described_class.base_name)
29
29
  end
30
30
  end
31
31
 
@@ -17,6 +17,13 @@ class GraphQL_Type_EnumTest < GraphQL::TestCase
17
17
  refute(DESCRIBED_CLASS.valid_input?(1))
18
18
  refute(DESCRIBED_CLASS.valid_input?(nil))
19
19
  refute(DESCRIBED_CLASS.valid_input?('abc'))
20
+
21
+ str_token = new_token('"A"', :string)
22
+ refute(DESCRIBED_CLASS.valid_input?(str_token))
23
+
24
+ stubbed_config(:allow_string_as_enum_input, true) do
25
+ assert(DESCRIBED_CLASS.valid_input?(str_token))
26
+ end
20
27
  end
21
28
 
22
29
  def test_valid_output_ask
@@ -58,6 +65,14 @@ class GraphQL_Type_EnumTest < GraphQL::TestCase
58
65
  test_value = DESCRIBED_CLASS.deserialize('A')
59
66
  assert_instance_of(DESCRIBED_CLASS, test_value)
60
67
  assert_equal('A', test_value.value)
68
+
69
+ str_token = new_token('"A"', :string)
70
+ assert_nil(DESCRIBED_CLASS.deserialize(str_token))
71
+ stubbed_config(:allow_string_as_enum_input, true) do
72
+ test_value = DESCRIBED_CLASS.deserialize(str_token)
73
+ assert_instance_of(DESCRIBED_CLASS, test_value)
74
+ assert_equal('A', test_value.value)
75
+ end
61
76
  end
62
77
 
63
78
  def test_decorate
@@ -35,7 +35,7 @@ class Integration_GlobalIDTest < GraphQL::IntegrationTestCase
35
35
  end
36
36
 
37
37
  def test_create_object_field
38
- obj = SCHEMA::NestedTypes::HumanObject[:name]
38
+ obj = SCHEMA.const_get(:NestedTypes)::HumanObject[:name]
39
39
  assert_gid_value('gql://start-wars-mem/Human/name', obj)
40
40
  end
41
41
 
@@ -85,7 +85,7 @@ class Integration_GlobalIDTest < GraphQL::IntegrationTestCase
85
85
 
86
86
  def test_parse_object_field
87
87
  obj = find_gid('gql://start-wars-mem/Human/name')
88
- assert_equal(SCHEMA::NestedTypes::HumanObject[:name], obj)
88
+ assert_equal(SCHEMA.const_get(:NestedTypes)::HumanObject[:name], obj)
89
89
  end
90
90
 
91
91
  def test_parse_scalar_type
@@ -97,7 +97,7 @@ class Integration_ResolverPrecedenceTest < GraphQL::IntegrationTestCase
97
97
  end
98
98
 
99
99
  def test_simple_object
100
- object = SCHEMA::NestedTypes::Object1Object
100
+ object = SCHEMA.const_get(:NestedTypes)::Object1Object
101
101
  field = object[:field1]
102
102
 
103
103
  # (1) Block has higher precedence than default value
@@ -10,6 +10,7 @@ class MySQLRecord < ActiveRecord::Base
10
10
  database: ENV.fetch('GQL_MYSQL_DATABASE', 'starwars'),
11
11
  username: ENV.fetch('GQL_MYSQL_USERNAME', 'root'),
12
12
  password: ENV['GQL_MYSQL_PASSWORD'],
13
+ port: ENV['GQL_MYSQL_PORT'],
13
14
  )
14
15
  end
15
16
 
@@ -84,6 +84,6 @@ class Integration_SQLite_StarWarsGlobalIDTest < GraphQL::IntegrationTestCase
84
84
  end
85
85
 
86
86
  def source
87
- StartWarsSqliteSchema::NestedTypes::LiteFactionSource
87
+ StartWarsSqliteSchema.const_get(:NestedTypes)::LiteFactionSource
88
88
  end
89
89
  end
data/test/test_ext.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  # Core ext methods
2
2
 
3
3
  class Object < BasicObject
4
+ def new_token(value, type)
5
+ GQLParser::Token.new(value).tap do |token|
6
+ token.instance_variable_set(:@type, type)
7
+ end
8
+ end
9
+
4
10
  def stub_ivar(name, value = nil)
5
11
  instance_variable_set(name, value)
6
12
  yield
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Silva
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-06 00:00:00.000000000 Z
11
+ date: 2023-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -397,7 +397,7 @@ homepage: https://github.com/virtualshield/rails-graphql
397
397
  licenses:
398
398
  - MIT
399
399
  metadata:
400
- homepage_uri: https://www.rails-graphql.dev/
400
+ homepage_uri: https://rails-graphql.dev/
401
401
  source_code_uri: https://github.com/virtualshield/rails-graphql
402
402
  bug_tracker_uri: https://github.com/virtualshield/rails-graphql/issues
403
403
  changelog_uri: https://github.com/virtualshield/rails-graphql/blob/master/CHANGELOG.md
@@ -414,11 +414,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
414
414
  version: 2.6.3
415
415
  required_rubygems_version: !ruby/object:Gem::Requirement
416
416
  requirements:
417
- - - ">"
417
+ - - ">="
418
418
  - !ruby/object:Gem::Version
419
- version: 1.3.1
419
+ version: '0'
420
420
  requirements: []
421
- rubygems_version: 3.2.33
421
+ rubygems_version: 3.3.26
422
422
  signing_key:
423
423
  specification_version: 4
424
424
  summary: GraphQL meets RoR with the most Ruby-like DSL