Ruby 3.2.2升级后Rails Scope中Lambda无法接收关键字参数
问题
将应用从Ruby 2.7.5升级至3.2.2(Rails版本为6.0.6.1)后,发现带位置参数+关键字参数的Scope Lambda无法正常接收参数,触发ArgumentError提示参数数量错误。虽然Ruby 3.0有位置参数与关键字参数分离的破坏性变更,但普通方法使用相同签名可正常运行,因此判断问题不在Ruby本身。
示例代码:
class TestClass scope :test_scope, lambda do |arg1, arg2:| puts arg1 puts arg2 end end
调用时的错误:
> TestClass.test_scope wrong number of arguments (given 0, expected 1; required keyword: arg2) (ArgumentError) > TestClass.test_scope('abc') missing keyword: :arg2 (ArgumentError) > TestClass.test_scope('abc', arg2: 'def') wrong number of arguments (given 2, expected 1; required keyword: arg2) (ArgumentError)
更新:完整堆栈跟踪
调用示例:
ExampleClass.test_scope('abc', arg2: 'def')
堆栈跟踪:
ArgumentError: wrong number of arguments (given 2, expected 1; required keyword: arg2) /app/app/models/example_class.rb:2:in `block in <class:ExampleClass>' /bundle/vendor/ruby/3.2.0/gems/activerecord-6.1.7.3/lib/active_record/relation.rb:411:in `instance_exec' /bundle/vendor/ruby/3.2.0/gems/activerecord-6.1.7.3/lib/active_record/relation.rb:411:in `block in _exec_scope' /bundle/vendor/ruby/3.2.0/gems/activerecord-6.1.7.3/lib/active_record/relation.rb:804:in `_scoping' /bundle/vendor/ruby/3.2.0/gems/activerecord-6.1.7.3/lib/active_record/relation.rb:411:in `_exec_scope' /bundle/vendor/ruby/3.2.0/gems/activerecord-6.1.7.3/lib/active_record/scoping/named.rb:176:in `block in scope' /app/config/initializers/test_init.rb:13:in `<main>' /bundle/vendor/ruby/3.2.0/gems/bootsnap-1.11.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:39:in `load' /bundle/vendor/ruby/3.2.0/gems/bootsnap-1.11.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:39:in `load' /bundle/vendor/ruby/3.2.0/gems/activesupport-6.1.7.3/lib/active_support/dependencies.rb:326:in `block in load' /bundle/vendor/ruby/3.2.0/gems/activesupport-6.1.7.3/lib/active_support/dependencies.rb:299:in `load_dependency' /bundle/vendor/ruby/3.2.0/gems/activesupport-6.1.7.3/lib/active_support/dependencies.rb:326:in `load' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/engine.rb:681:in `block in load_config_initializer' /bundle/vendor/ruby/3.2.0/gems/activesupport-6.1.7.3/lib/active_support/notifications.rb:205:in `instrument' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/engine.rb:680:in `load_config_initializer' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/engine.rb:634:in `block (2 levels) in <class:Engine>' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/engine.rb:633:in `each' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/engine.rb:633:in `block in <class:Engine>' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/initializable.rb:32:in `instance_exec' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/initializable.rb:32:in `run' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/initializable.rb:61:in `block in run_initializers' /usr/local/lib/ruby/3.2.0/tsort.rb:228:in `block in tsort_each' /usr/local/lib/ruby/3.2.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component' /usr/local/lib/ruby/3.2.0/tsort.rb:422:in `block (2 levels) in each_strongly_connected_component_from' /usr/local/lib/ruby/3.2.0/tsort.rb:431:in `each_strongly_connected_component_from' /usr/local/lib/ruby/3.2.0/tsort.rb:421:in `block in each_strongly_connected_component_from' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/initializable.rb:50:in `each' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/initializable.rb:50:in `tsort_each_child' /usr/local/lib/ruby/3.2.0/tsort.rb:415:in `call' /usr/local/lib/ruby/3.2.0/tsort.rb:415:in `each_strongly_connected_component_from' /usr/local/lib/ruby/3.2.0/tsort.rb:349:in `block in each_strongly_connected_component' /usr/local/lib/ruby/3.2.0/tsort.rb:347:in `each' /usr/local/lib/ruby/3.2.0/tsort.rb:347:in `call' /usr/local/lib/ruby/3.2.0/tsort.rb:347:in `each_strongly_connected_component' /usr/local/lib/ruby/3.2.0/tsort.rb:226:in `tsort_each' /usr/local/lib/ruby/3.2.0/tsort.rb:205:in `tsort_each' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/initializable.rb:60:in `run_initializers' /bundle/vendor/ruby/3.2.0/gems/railties-6.1.7.3/lib/rails/application.rb:391:in `initialize!' /app/config/environment.rb:5:in `<top (required)>' config.ru:3:in `require_relative' config.ru:3:in `block in <main>' /bundle/vendor/ruby/3.2.0/gems/rack-2.2.7/lib/rack/builder.rb:116:in `eval' /bundle/vendor/ruby/3.2.0/gems/rack-2.2.7/lib/rack/builder.rb:116:in `new_from_string' /bundle/vendor/ruby/3.2.0/gems/rack-2.2.7/lib/rack/builder.rb:105:in `load_file' /bundle/vendor/ruby/3.2.0/gems/rack-2.2.7/lib/rack/builder.rb:66:in `parse_file' /bundle/vendor/ruby/3.2.0/gems/puma-5.6.5/lib/puma/configuration.rb:348:in `load_rackup' /bundle/vendor/ruby/3.2.0/gems/puma-5.6.5/lib/puma/configuration.rb:270:in `app' /bundle/vendor/ruby/3.2.0/gems/puma-5.6.5/lib/puma/runner.rb:150:in `load_and_bind' /bundle/vendor/ruby/3.2.0/gems/puma-5.6.5/lib/puma/single.rb:44:in `run' /bundle/vendor/ruby/3.2.0/gems/puma-5.6.5/lib/puma/launcher.rb:193:in `run' /bundle/vendor/ruby/3.2.0/gems/puma-5.6.5/lib/puma/cli.rb:81:in `run' /bundle/vendor/ruby/3.2.0/gems/puma-5.6.5/bin/puma:10:in `<top (required)>' /bundle/vendor/ruby/3.2.0/bin/puma:25:in `load' /bundle/vendor/ruby/3.2.0/bin/puma:25:in `<top (required)>'
解决方案
问题根源在于Rails 6.0.x版本的Active Record对Scope参数的处理逻辑不兼容Ruby 3+的关键字参数分离规则。
从堆栈跟踪可以看到,Active Record的_exec_scope方法通过instance_exec调用Scope的lambda时,会把所有传入的参数(包括关键字参数)作为位置参数传递,而Ruby 3+会严格区分位置参数和关键字参数,导致lambda的参数签名不匹配,触发错误。而普通方法不受影响是因为Ruby直接处理方法调用的参数分离,而Scope的参数经过了Active Record的一层封装。
有两种解决方式:
方式一:修改Scope的参数签名,兼容现有Rails版本
把关键字参数改为哈希参数,或者用**kwargs捕获所有关键字参数,避免参数类型不匹配:
方案1:合并为哈希参数
class TestClass scope :test_scope, lambda do |arg1, kwargs| puts arg1 puts kwargs[:arg2] end end # 调用方式不变: TestClass.test_scope('abc', arg2: 'def')
方案2:用**kwargs捕获关键字参数
class TestClass scope :test_scope, lambda do |arg1, **kwargs| puts arg1 puts kwargs[:arg2] end end # 调用方式不变: TestClass.test_scope('abc', arg2: 'def')
方式二:升级Rails版本到6.1.x及以上
Rails 6.1版本已经修复了Ruby 3+下关键字参数的兼容问题,Active Record会正确分离位置参数和关键字参数传递给Scope的lambda。升级后可以保留原有的参数签名不变。
内容的提问来源于stack exchange,提问作者Henridv
相关产品推荐
相关产品推荐

