diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ae058..1447a22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] * Added/Changed/Deprecated/Removed/Fixed/Security: YOUR CHANGE HERE +* Added: automatic conversion from symbols to keywords setting * Added: relation.empty?, relation.blank?, relation.present? methods * Added: fields now can be marked as "keywords" * Added: it's possible to set order for relation diff --git a/Gemfile.lock b/Gemfile.lock index 439afef..7ed9e26 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,6 +69,7 @@ GEM byebug (~> 11.0) pry (>= 0.13, < 0.15) public_suffix (5.0.0) + racc (1.8.1) rainbow (3.1.1) rake (13.0.6) regexp_parser (2.6.1) @@ -126,8 +127,8 @@ DEPENDENCIES activerecord bundler (~> 2.0) graphql_rails (>= 2.2.0) - pry pry-byebug + racc rake (~> 13.0) rspec (~> 3.4) rubocop (= 1.39.0) diff --git a/active_graphql.gemspec b/active_graphql.gemspec index 94bfb6d..85600c5 100644 --- a/active_graphql.gemspec +++ b/active_graphql.gemspec @@ -43,7 +43,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> 2.0" spec.add_development_dependency "webmock", "~> 3" - spec.add_development_dependency "pry" + spec.add_development_dependency "pry-byebug" + spec.add_development_dependency "racc" spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "rspec", "~> 3.4" spec.add_development_dependency "rubocop", "1.39.0" diff --git a/docs/client.md b/docs/client.md index 8091342..d9edf03 100644 --- a/docs/client.md +++ b/docs/client.md @@ -8,12 +8,36 @@ to initialize graphql client, simply create new client instance with url: client = ActiveGraphql::Client.new(url: 'http://example.com/graphql') ``` -you can also provide extra options which will be accepted by addapter, like this: +you can also provide extra options which will be accepted by adapter, like this: ```ruby client = ActiveGraphql::Client.new(url: 'http://example.com/graphql', headers: {}, schema_path: '...') ``` +### `treat_symbol_as_keyword` option + +By default, ActiveGraphql converts all String/Symbol values to GraphQL strings. This creates a challenge when working with GraphQL Enums. To bypass this issue, you can pass `treat_symbol_as_keyword` option so symbol values will be converted to GraphQL keywords (enum values). + +```ruby +default_client = ActiveGraphql::Client.new(url: 'http://example.com/graphql') +default_client.query(:users).select(:name).(status: :ACTIVE).to_graphql +# => +# query { +# users(status: "ACTIVE") { +# status +# } +# } + +client = ActiveGraphql::Client.new(url: 'http://example.com/graphql', treat_symbol_as_keyword: true) +client.query(:users).select(:name).(status: :ACTIVE).to_graphql +# => +# query { +# users(status: ACTIVE) { +# status +# } +# } + + ## query and mutation actions ```ruby diff --git a/lib/active_graphql/client.rb b/lib/active_graphql/client.rb index a050669..d018c18 100644 --- a/lib/active_graphql/client.rb +++ b/lib/active_graphql/client.rb @@ -11,6 +11,8 @@ class Client require 'active_graphql/client/adapters' require 'active_graphql/client/response' + attr_reader :config + def initialize(config) @config = config.dup @adapter_class = @config.delete(:adapter) @@ -30,9 +32,5 @@ def adapter adapter_builder.new(config) end end - - private - - attr_reader :config end end diff --git a/lib/active_graphql/client/actions/action.rb b/lib/active_graphql/client/actions/action.rb index 8397da9..bc1945e 100644 --- a/lib/active_graphql/client/actions/action.rb +++ b/lib/active_graphql/client/actions/action.rb @@ -86,7 +86,7 @@ def join_array_and_hash(*array, **hash) end def formatted_inputs - FormatInputs.new(input_attributes).call + FormatInputs.new(input_attributes, client:).call end def formatted_outputs diff --git a/lib/active_graphql/client/actions/action/format_inputs.rb b/lib/active_graphql/client/actions/action/format_inputs.rb index bcebdd3..4e24960 100644 --- a/lib/active_graphql/client/actions/action/format_inputs.rb +++ b/lib/active_graphql/client/actions/action/format_inputs.rb @@ -4,12 +4,13 @@ module ActiveGraphql class Client module Actions class Action - # converts ruby object in to grapqhl input string + # converts ruby object in to graphql input string class FormatInputs include VariableDetectable - def initialize(inputs) + def initialize(inputs, client:) @inputs = inputs + @client = client end def call @@ -20,7 +21,11 @@ def call private - attr_reader :inputs + attr_reader :inputs, :client + + def treat_symbol_as_keyword? + client.config[:treat_symbol_as_keyword] + end def formatted(attributes, parent_keys: []) if attributes.is_a?(Hash) @@ -34,25 +39,25 @@ def formatted(attributes, parent_keys: []) end def formatted_attributes(attributes, parent_keys: []) - attributes = attributes.dup - keyword_fields = (attributes.delete(:__keyword_attributes) || []).map(&:to_s) - - formatted_attributes = attributes.map do |key, val| - if keyword_fields.include?(key.to_s) - formatted_key_and_keyword(key, val, parent_keys:) - else - formatted_key_and_value(key, val, parent_keys:) - end + dup_attributes = attributes.dup + keyword_fields = (dup_attributes.delete(:__keyword_attributes) || []).map(&:to_s) + + global_keyword_classes = treat_symbol_as_keyword? ? [Symbol] : [] + + formatted_attributes = dup_attributes.map do |key, val| + keyword_classes = keyword_fields.include?(key.to_s) ? [Symbol, String] : global_keyword_classes + formatted_key_and_value(key, val, parent_keys:, keyword_classes:) end formatted_attributes.join(', ') end - def formatted_key_and_value(key, value, parent_keys:) + def formatted_key_and_value(key, value, parent_keys:, keyword_classes:) if variable_value?(value) "#{key}: $#{[*parent_keys, key].compact.join('_')}" else - "#{key}: #{formatted_value(value, parent_keys: [*parent_keys, key])}" + formatted_value = formatted_value_for(value, parent_keys: [*parent_keys, key], keyword_classes:) + "#{key}: #{formatted_value}" end end @@ -64,23 +69,32 @@ def formatted_key_and_keyword(key, value, parent_keys:) end end - def formatted_value(value, parent_keys:) # rubocop:disable Metrics/MethodLength - case value - when Hash - "{ #{formatted(value, parent_keys:)} }" - when Array - formatted_values = value.map.with_index do |it, idx| - formatted_value(it, parent_keys: [*parent_keys, idx]) - end - "[#{formatted_values.join(', ')}]" - when nil + def formatted_value_for(value, parent_keys:, keyword_classes:) # rubocop:disable Metrics/MethodLength + if value.is_a?(Hash) + formatted_hash_value_for(value, parent_keys:) + elsif value.is_a?(Array) + formatted_array_value_for(value, parent_keys:, keyword_classes:) + elsif value.nil? 'null' - when Symbol + elsif keyword_classes.any? { |klass| value.is_a?(klass) } + value.to_s + elsif value.is_a?(Symbol) value.to_s.inspect else value.inspect end end + + def formatted_array_value_for(value, parent_keys:, keyword_classes:) + formatted_values = value.map.with_index do |it, idx| + formatted_value_for(it, parent_keys: [*parent_keys, idx], keyword_classes:) + end + "[#{formatted_values.join(', ')}]" + end + + def formatted_hash_value_for(value, parent_keys:) + "{ #{formatted(value, parent_keys:)} }" + end end end end diff --git a/lib/active_graphql/client/adapters/graphlient_adapter.rb b/lib/active_graphql/client/adapters/graphlient_adapter.rb index d4e6330..b8ca5dc 100644 --- a/lib/active_graphql/client/adapters/graphlient_adapter.rb +++ b/lib/active_graphql/client/adapters/graphlient_adapter.rb @@ -9,6 +9,8 @@ class GraphlientAdapter require_relative './graphql_client_patch' require_relative './graphlient_multipart_adapter' + attr_reader :config + def initialize(config) @url = config[:url] @config = config @@ -29,7 +31,7 @@ def adapter_config private - attr_reader :url, :config + attr_reader :url def graphql_client @graphql_client ||= Graphlient::Client.new(url, **adapter_config) diff --git a/spec/lib/active_graphql/client/actions/action/format_inputs_spec.rb b/spec/lib/active_graphql/client/actions/action/format_inputs_spec.rb index f7d2451..b5b8a3a 100644 --- a/spec/lib/active_graphql/client/actions/action/format_inputs_spec.rb +++ b/spec/lib/active_graphql/client/actions/action/format_inputs_spec.rb @@ -3,9 +3,11 @@ require 'spec_helper' RSpec.describe ActiveGraphql::Client::Actions::Action::FormatInputs do - subject(:format_inputs) { described_class.new(inputs) } + subject(:format_inputs) { described_class.new(inputs, client:) } let(:inputs) { {} } + let(:client) { ActiveGraphql::Client::Adapters::GraphlientAdapter.new(client_config)} + let(:client_config) { { url: 'http://example.com/graphql' } } describe '#call' do subject(:call) { format_inputs.call } @@ -17,8 +19,18 @@ context 'when value is symbol' do let(:inputs) { { val: :yes } } - it 'convers Symbol values to strings' do - expect(call).to eq 'val: "yes"' + context 'when treat_symbol_as_keyword is false or not set' do + it 'converts Symbol values to strings' do + expect(call).to eq 'val: "yes"' + end + end + + context 'when treat_symbol_as_keyword is true' do + let(:client_config) { super().merge(treat_symbol_as_keyword: true) } + + it 'converts Symbol values to keywords' do + expect(call).to eq 'val: yes' + end end end @@ -85,7 +97,7 @@ context 'when value is symbol' do let(:inputs) { { val: :YES, __keyword_attributes: [:val] } } - it 'convers Symbol values to strings' do + it 'converts Symbol values to strings' do expect(call).to eq 'val: YES' end end diff --git a/spec/lib/active_graphql/client/actions/mutation_action_spec.rb b/spec/lib/active_graphql/client/actions/mutation_action_spec.rb index dd82cf2..ed39a93 100644 --- a/spec/lib/active_graphql/client/actions/mutation_action_spec.rb +++ b/spec/lib/active_graphql/client/actions/mutation_action_spec.rb @@ -16,7 +16,7 @@ def to_h(*) end let(:action_name) { 'createAccessToken' } - let(:action_client) { instance_double(Adapters::GraphlientAdapter, post: response) } + let(:action_client) { instance_double(Adapters::GraphlientAdapter, post: response, config: {}) } let(:response) { Response.new(result, error) } let(:email) { 'test@example.com' } let(:error) { nil } diff --git a/spec/lib/active_graphql/client/actions/query_action_spec.rb b/spec/lib/active_graphql/client/actions/query_action_spec.rb index cc6fe43..95ed4a3 100644 --- a/spec/lib/active_graphql/client/actions/query_action_spec.rb +++ b/spec/lib/active_graphql/client/actions/query_action_spec.rb @@ -8,7 +8,7 @@ let(:initial_action) { described_class.new(name:, client: authenticator_client) } let(:name) { :findUser } let(:authenticator_client) do - instance_double(ActiveGraphql::Client::Adapters::GraphlientAdapter, post: response_mock) + instance_double(ActiveGraphql::Client::Adapters::GraphlientAdapter, post: response_mock, config: {}) end let(:response_mock) { instance_double(ActiveGraphql::Client::Response, result: nil) }