From 5c071d42fda1682282461978923a2bd89f2ad27d Mon Sep 17 00:00:00 2001 From: Adam Hess Date: Tue, 23 Jun 2020 09:54:55 -0700 Subject: [PATCH] Fix ruby 2.7 kwargs warning Ruby 2.7 deprecated passing kwargs when the method expects a hash or passing a hash when the method expects kwargs. In factory_bot, this creates the warning: ``` /Users/hparker/code/factory_bot/lib/factory_bot/decorator/new_constructor.rb:9: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call /Users/hparker/code/factory_bot/spec/acceptance/initialize_with_spec.rb:220: warning: The called method `initialize' is defined here ``` We can fix this warning by updating the syntax. We need to include `**kwargs` in the `method_missing` calls when we are on ruby 2.7 or later. In decorator.rb, adding `**kwargs` alone doesn't work since adding `**kwargs` can change what arguments remain in the `args`. In this case we have to class eval the method if we are running ruby 2.7. This way the syntax is valid in previous versions and we can use the `...` operator which allows us to avoid changing the arguments passed on in method missing. Co-authored-by: Lee Quarella --- lib/factory_bot/decorator.rb | 24 ++++++++++++++----- .../decorator/invocation_tracker.rb | 13 +++++++--- lib/factory_bot/evaluator.rb | 20 ++++++++++++---- spec/acceptance/initialize_with_spec.rb | 4 ++-- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/lib/factory_bot/decorator.rb b/lib/factory_bot/decorator.rb index 0658d9f..357f6be 100644 --- a/lib/factory_bot/decorator.rb +++ b/lib/factory_bot/decorator.rb @@ -6,18 +6,30 @@ module FactoryBot @component = component end - def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissingSuper - @component.send(name, *args, &block) + if ::Gem::Version.new(::RUBY_VERSION) >= ::Gem::Version.new("2.7") + class_eval(<<~RUBY, __FILE__, __LINE__ + 1) + def method_missing(...) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing + @component.send(...) + end + + def send(...) + __send__(...) + end + RUBY + else + def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing + @component.send(name, *args, &block) + end + + def send(symbol, *args, &block) + __send__(symbol, *args, &block) + end end def respond_to_missing?(name, include_private = false) @component.respond_to?(name, true) || super end - def send(symbol, *args, &block) - __send__(symbol, *args, &block) - end - def self.const_missing(name) ::Object.const_get(name) end diff --git a/lib/factory_bot/decorator/invocation_tracker.rb b/lib/factory_bot/decorator/invocation_tracker.rb index fac96e9..1e05e14 100644 --- a/lib/factory_bot/decorator/invocation_tracker.rb +++ b/lib/factory_bot/decorator/invocation_tracker.rb @@ -6,9 +6,16 @@ module FactoryBot @invoked_methods = [] end - def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing - @invoked_methods << name - super + if ::Gem::Version.new(::RUBY_VERSION) >= ::Gem::Version.new("2.7") + def method_missing(name, *args, **kwargs, &block) # rubocop:disable Style/MissingRespondToMissing + @invoked_methods << name + super + end + else + def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing + @invoked_methods << name + super + end end def __invoked_methods__ diff --git a/lib/factory_bot/evaluator.rb b/lib/factory_bot/evaluator.rb index f3deeed..82c0da9 100644 --- a/lib/factory_bot/evaluator.rb +++ b/lib/factory_bot/evaluator.rb @@ -35,11 +35,21 @@ module FactoryBot attr_writer :instance - def method_missing(method_name, *args, &block) # rubocop:disable Style/MethodMissingSuper - if @instance.respond_to?(method_name) - @instance.send(method_name, *args, &block) - else - SyntaxRunner.new.send(method_name, *args, &block) + if ::Gem::Version.new(::RUBY_VERSION) >= ::Gem::Version.new("2.7") + def method_missing(method_name, *args, **kwargs, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing + if @instance.respond_to?(method_name) + @instance.send(method_name, *args, **kwargs, &block) + else + SyntaxRunner.new.send(method_name, *args, **kwargs, &block) + end + end + else + def method_missing(method_name, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing + if @instance.respond_to?(method_name) + @instance.send(method_name, *args, &block) + else + SyntaxRunner.new.send(method_name, *args, &block) + end end end diff --git a/spec/acceptance/initialize_with_spec.rb b/spec/acceptance/initialize_with_spec.rb index 77ccf23..35b8d5f 100644 --- a/spec/acceptance/initialize_with_spec.rb +++ b/spec/acceptance/initialize_with_spec.rb @@ -201,7 +201,7 @@ describe "initialize_with has access to all attributes for construction" do name { email.gsub(/@.+/, "") } - initialize_with { new(attributes) } + initialize_with { new(**attributes) } end end @@ -225,7 +225,7 @@ describe "initialize_with with an 'attributes' attribute" do FactoryBot.define do factory :user do attributes { {name: "Daniel"} } - initialize_with { new(attributes) } + initialize_with { new(**attributes) } end end