From 74b33446c4e8e29fc0513e658e153c35cdf4ff66 Mon Sep 17 00:00:00 2001 From: DmitryTsepelev Date: Sun, 12 Jan 2025 20:15:12 +0300 Subject: [PATCH 1/8] Allow accessing enum value names via methods --- guides/type_definitions/enums.md | 6 ++++++ lib/graphql/schema/enum.rb | 1 + spec/graphql/schema/enum_spec.rb | 12 ++++++++++++ 3 files changed, 19 insertions(+) diff --git a/guides/type_definitions/enums.md b/guides/type_definitions/enums.md index 120c08cfc9..3c39be22fd 100644 --- a/guides/type_definitions/enums.md +++ b/guides/type_definitions/enums.md @@ -72,3 +72,9 @@ value "AUDIO", value: :audio Then, GraphQL inputs of `AUDIO` will be converted to `:audio` and Ruby values of `:audio` will be converted to `"AUDIO"` in GraphQL responses. Enum classes are never instantiated and their methods are never called. + +You can get the GraphQL name of the enum value using the method matching its downcased name: + +```ruby +Types::MediaCategory.audio # => "Audio" +``` diff --git a/lib/graphql/schema/enum.rb b/lib/graphql/schema/enum.rb index df41132986..60d63ddbb3 100644 --- a/lib/graphql/schema/enum.rb +++ b/lib/graphql/schema/enum.rb @@ -67,6 +67,7 @@ class << self def value(*args, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) + singleton_class.define_method(value.graphql_name.downcase) { value.graphql_name } key = value.graphql_name prev_value = own_values[key] case prev_value diff --git a/spec/graphql/schema/enum_spec.rb b/spec/graphql/schema/enum_spec.rb index a75ea85551..e6ef9092ec 100644 --- a/spec/graphql/schema/enum_spec.rb +++ b/spec/graphql/schema/enum_spec.rb @@ -10,6 +10,18 @@ end end + describe "value methods" do + it "defines methods to fetch graphql names" do + assert_equal enum.string, "STRING" + assert_equal enum.woodwind, "WOODWIND" + assert_equal enum.brass, "BRASS" + assert_equal enum.percussion, "PERCUSSION" + assert_equal enum.didgeridoo, "DIDGERIDOO" + assert_equal enum.keys, "KEYS" + assert_equal enum.silence, "SILENCE" + end + end + describe "type info" do it "tells about the definition" do assert_equal "Family", enum.graphql_name From 4f3944521c13378cac3ecfbfde1d237eaaf6f651 Mon Sep 17 00:00:00 2001 From: DmitryTsepelev Date: Sat, 25 Jan 2025 15:38:39 +0300 Subject: [PATCH 2/8] Fix enum doc --- guides/type_definitions/enums.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/type_definitions/enums.md b/guides/type_definitions/enums.md index 3c39be22fd..ccb0d9621e 100644 --- a/guides/type_definitions/enums.md +++ b/guides/type_definitions/enums.md @@ -76,5 +76,5 @@ Enum classes are never instantiated and their methods are never called. You can get the GraphQL name of the enum value using the method matching its downcased name: ```ruby -Types::MediaCategory.audio # => "Audio" +Types::MediaCategory.audio # => "AUDIO" ``` From fd2d89e35b5c6c7783e45d3de4a6aa4d11d998f9 Mon Sep 17 00:00:00 2001 From: DmitryTsepelev Date: Sat, 25 Jan 2025 15:44:26 +0300 Subject: [PATCH 3/8] Generate enum methods via instance_eval --- lib/graphql/schema/enum.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphql/schema/enum.rb b/lib/graphql/schema/enum.rb index 60d63ddbb3..4645bbb8ac 100644 --- a/lib/graphql/schema/enum.rb +++ b/lib/graphql/schema/enum.rb @@ -67,7 +67,7 @@ class << self def value(*args, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) - singleton_class.define_method(value.graphql_name.downcase) { value.graphql_name } + instance_eval("def #{value.graphql_name.downcase}; #{value.graphql_name.inspect}; end;") key = value.graphql_name prev_value = own_values[key] case prev_value From 1c96c72e84de1961cbc915e0106c3ecee39ec771 Mon Sep 17 00:00:00 2001 From: DmitryTsepelev Date: Sat, 25 Jan 2025 16:33:36 +0300 Subject: [PATCH 4/8] Allow configure value_method for enum values --- guides/type_definitions/enums.md | 16 ++++++++++++++++ lib/graphql/schema/enum.rb | 21 ++++++++++++++++++-- spec/graphql/schema/enum_spec.rb | 33 +++++++++++++++++++++++++++++--- spec/support/jazz.rb | 5 +++-- 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/guides/type_definitions/enums.md b/guides/type_definitions/enums.md index ccb0d9621e..ccf440b52e 100644 --- a/guides/type_definitions/enums.md +++ b/guides/type_definitions/enums.md @@ -78,3 +78,19 @@ You can get the GraphQL name of the enum value using the method matching its dow ```ruby Types::MediaCategory.audio # => "AUDIO" ``` + +You can pass a `value_method:` to override the value of the generated method: + +```ruby +value "AUDIO", value: :audio, value_method: :lo_fi_audio + +# ... + +Types::MediaCategory.lo_fi_audio # => "AUDIO" +``` + +Also, you can completely skip the method generation by setting `value_method` to `false` + +```ruby +value "AUDIO", value: :audio, value_method: false +``` diff --git a/lib/graphql/schema/enum.rb b/lib/graphql/schema/enum.rb index 4645bbb8ac..1a5b1a8838 100644 --- a/lib/graphql/schema/enum.rb +++ b/lib/graphql/schema/enum.rb @@ -61,13 +61,16 @@ class << self # @option kwargs [String] :description, the GraphQL description for this value, present in documentation # @option kwargs [String] :comment, the GraphQL comment for this value, present in documentation # @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`) + # @option kwargs [::Object] :value_method, the method name to fetch `graphql_name` (defaults to `graphql_name.downcase`) # @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here # @return [void] # @see {Schema::EnumValue} which handles these inputs by default - def value(*args, **kwargs, &block) + def value(*args, value_method: nil, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) - instance_eval("def #{value.graphql_name.downcase}; #{value.graphql_name.inspect}; end;") + + generate_value_method(value, value_method) + key = value.graphql_name prev_value = own_values[key] case prev_value @@ -224,6 +227,20 @@ def inherited(child_class) def own_values @own_values ||= {} end + + def generate_value_method(value, configured_value_method) + return if configured_value_method == false + + value_method_name = configured_value_method || value.graphql_name.downcase + + if respond_to?(value_method_name.to_sym) + $stderr << "Failed to define value method for :#{value_method_name}, because " \ + "#{value.owner.name} already responds to that method. Use `value_name:` to override the method name." + return + end + + instance_eval("def #{value_method_name}; #{value.graphql_name.inspect}; end;") + end end enum_value_class(GraphQL::Schema::EnumValue) diff --git a/spec/graphql/schema/enum_spec.rb b/spec/graphql/schema/enum_spec.rb index e6ef9092ec..a4a9266fd6 100644 --- a/spec/graphql/schema/enum_spec.rb +++ b/spec/graphql/schema/enum_spec.rb @@ -11,14 +11,41 @@ end describe "value methods" do - it "defines methods to fetch graphql names" do + it "defines default methods to fetch graphql names" do assert_equal enum.string, "STRING" assert_equal enum.woodwind, "WOODWIND" assert_equal enum.brass, "BRASS" - assert_equal enum.percussion, "PERCUSSION" assert_equal enum.didgeridoo, "DIDGERIDOO" assert_equal enum.keys, "KEYS" - assert_equal enum.silence, "SILENCE" + end + + describe "when value_method is configured" do + it "use custom method" do + assert_equal enum.respond_to?(:percussion), false + assert_equal enum.precussion_custom_value_method, "PERCUSSION" + end + end + + describe "when value_method conflicts with existing method" do + it "does not define method and emits warning" do + expected_message = "Failed to define value method for :value, because " \ + "ConflictEnum already responds to that method. Use `value_name:` to override the method name." + + assert_warns(expected_message) do + conflict_enum = Class.new(GraphQL::Schema::Enum) + Object.const_set("ConflictEnum", conflict_enum) + already_defined_method = conflict_enum.method(:value) + conflict_enum.value "VALUE", "Makes conflict" + assert_equal conflict_enum.method(:value), already_defined_method + end + end + + end + + describe "when value_method = false" do + it "does not define method" do + assert_equal enum.respond_to?(:silence), false + end end end diff --git a/spec/support/jazz.rb b/spec/support/jazz.rb index 0c71c2e5d6..0ee875f7f4 100644 --- a/spec/support/jazz.rb +++ b/spec/support/jazz.rb @@ -240,12 +240,13 @@ class Family < BaseEnum value "STRING", "Makes a sound by vibrating strings", value: :str, custom_setting: 1 value :WOODWIND, "Makes a sound by vibrating air in a pipe" value :BRASS, "Makes a sound by amplifying the sound of buzzing lips" - value "PERCUSSION", "Makes a sound by hitting something that vibrates" + value "PERCUSSION", "Makes a sound by hitting something that vibrates", + value_method: :precussion_custom_value_method value "DIDGERIDOO", "Makes a sound by amplifying the sound of buzzing lips", deprecation_reason: "Merged into BRASS" value "KEYS" do description "Neither here nor there, really" end - value "SILENCE", "Makes no sound", value: false + value "SILENCE", "Makes no sound", value: false, value_method: false end class InstrumentType < BaseObject From eb465e73f9ac123521ef718606dac096f38480bd Mon Sep 17 00:00:00 2001 From: DmitryTsepelev Date: Sat, 25 Jan 2025 18:50:29 +0300 Subject: [PATCH 5/8] Disable value methods for DirectiveLocationEnum --- lib/graphql/introspection/directive_location_enum.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphql/introspection/directive_location_enum.rb b/lib/graphql/introspection/directive_location_enum.rb index 0a7428ccc6..4fe69f53ca 100644 --- a/lib/graphql/introspection/directive_location_enum.rb +++ b/lib/graphql/introspection/directive_location_enum.rb @@ -7,7 +7,7 @@ class DirectiveLocationEnum < GraphQL::Schema::Enum "a __DirectiveLocation describes one such possible adjacencies." GraphQL::Schema::Directive::LOCATIONS.each do |location| - value(location.to_s, GraphQL::Schema::Directive::LOCATION_DESCRIPTIONS[location], value: location) + value(location.to_s, GraphQL::Schema::Directive::LOCATION_DESCRIPTIONS[location], value: location, value_method: false) end introspection true end From 21dc28ac36e0274cd9b9110df18cf864c1689d2d Mon Sep 17 00:00:00 2001 From: DmitryTsepelev Date: Sat, 25 Jan 2025 18:54:26 +0300 Subject: [PATCH 6/8] Add concurrent-ruby to Rails 7.0 Gemfile to fix CI --- gemfiles/mongoid_7.gemfile | 1 + gemfiles/rails_7.0.gemfile | 1 + 2 files changed, 2 insertions(+) diff --git a/gemfiles/mongoid_7.gemfile b/gemfiles/mongoid_7.gemfile index 37e1c3a264..e90a522126 100644 --- a/gemfiles/mongoid_7.gemfile +++ b/gemfiles/mongoid_7.gemfile @@ -12,5 +12,6 @@ if RUBY_VERSION >= "3.0" gem "evt" end gem "fiber-storage" +gem "concurrent-ruby", "1.3.4" gemspec path: "../" diff --git a/gemfiles/rails_7.0.gemfile b/gemfiles/rails_7.0.gemfile index c43a185427..53680547d1 100644 --- a/gemfiles/rails_7.0.gemfile +++ b/gemfiles/rails_7.0.gemfile @@ -11,5 +11,6 @@ gem "sqlite3", "~> 1.4", platform: :ruby gem "sequel" gem "evt" gem "async" +gem "concurrent-ruby", "1.3.4" gemspec path: "../" From 6a503c81a1b4c6e72da823e1db061cedc64d0b3f Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Mon, 27 Jan 2025 19:16:39 -0500 Subject: [PATCH 7/8] Update lib/graphql/schema/enum.rb --- lib/graphql/schema/enum.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphql/schema/enum.rb b/lib/graphql/schema/enum.rb index 1a5b1a8838..2129c7cf26 100644 --- a/lib/graphql/schema/enum.rb +++ b/lib/graphql/schema/enum.rb @@ -234,7 +234,7 @@ def generate_value_method(value, configured_value_method) value_method_name = configured_value_method || value.graphql_name.downcase if respond_to?(value_method_name.to_sym) - $stderr << "Failed to define value method for :#{value_method_name}, because " \ + warn "Failed to define value method for :#{value_method_name}, because " \ "#{value.owner.name} already responds to that method. Use `value_name:` to override the method name." return end From 9bc6e68f55b5ac0509d6853b5a0e48740bcb9212 Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Mon, 27 Jan 2025 19:19:14 -0500 Subject: [PATCH 8/8] Update spec/graphql/schema/enum_spec.rb --- spec/graphql/schema/enum_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/graphql/schema/enum_spec.rb b/spec/graphql/schema/enum_spec.rb index a4a9266fd6..0a7c46053a 100644 --- a/spec/graphql/schema/enum_spec.rb +++ b/spec/graphql/schema/enum_spec.rb @@ -29,7 +29,7 @@ describe "when value_method conflicts with existing method" do it "does not define method and emits warning" do expected_message = "Failed to define value method for :value, because " \ - "ConflictEnum already responds to that method. Use `value_name:` to override the method name." + "ConflictEnum already responds to that method. Use `value_name:` to override the method name.\n" assert_warns(expected_message) do conflict_enum = Class.new(GraphQL::Schema::Enum)