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: "../" diff --git a/guides/type_definitions/enums.md b/guides/type_definitions/enums.md index 120c08cfc9..ccf440b52e 100644 --- a/guides/type_definitions/enums.md +++ b/guides/type_definitions/enums.md @@ -72,3 +72,25 @@ 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" +``` + +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/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 diff --git a/lib/graphql/schema/enum.rb b/lib/graphql/schema/enum.rb index df41132986..2129c7cf26 100644 --- a/lib/graphql/schema/enum.rb +++ b/lib/graphql/schema/enum.rb @@ -61,12 +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) + + generate_value_method(value, value_method) + key = value.graphql_name prev_value = own_values[key] case prev_value @@ -223,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) + 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 + + 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 a75ea85551..0a7c46053a 100644 --- a/spec/graphql/schema/enum_spec.rb +++ b/spec/graphql/schema/enum_spec.rb @@ -10,6 +10,45 @@ end end + describe "value methods" 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.didgeridoo, "DIDGERIDOO" + assert_equal enum.keys, "KEYS" + 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.\n" + + 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 + describe "type info" do it "tells about the definition" do assert_equal "Family", enum.graphql_name 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