Ruby on Rails升级后Mailer方法触发ArgumentError问题咨询
问题背景
我们近期将代码库从Ruby 2.7.x + Rails 5.2.x迁移至Ruby 3.2.2 + Rails 6.1.7.3。此前邮件发送器定义如下:
class ApplicationMailer < ActionMailer::Base ... end
class MyMailer < ApplicationMailer def custom(user: , timestamp: Time.now) @user = user @timestamp = timestamp end end
调用方式为MyMailer.custom(user: @myuser).deliver_now,升级后触发错误:ArgumentError: wrong number of arguments (given 1, expected 0; required keyword: user)。
我们发现官方推荐通过params哈希传递参数,但该方式不够简洁,官方示例如下:
class MyMailer < ApplicationMailer def custom @user = params[:user] @timestamp = params[:timestamp] || Time.now end end
现咨询两个问题:
- 是Ruby或Rails的哪项变更导致原有方案失效?
- 是否仍可使用自定义关键字参数定义Mailer方法?
问题解答
1. 导致失效的变更点
是两方面变化共同作用的结果:
- Rails Action Mailer调用机制变更:Rails 6.0+重构了Action Mailer的类方法调用逻辑,旧版本中调用
MyMailer.custom(user: @myuser)时,Rails会直接将关键字参数传递给对应的实例方法;但升级后,类方法会把所有传入参数打包到params哈希中,不再直接转发给实例方法,实例方法默认被调用时不接收任何参数,这就导致你定义的带关键字参数的实例方法收到0个参数,触发参数不匹配错误。 - Ruby 3.0+关键字参数严格化:Ruby 3.0开始彻底区分位置参数和关键字参数,不再允许哈希自动转换为关键字参数。Ruby 2.7的兼容模式还能勉强运行,但Ruby 3.2.2的严格模式直接抛出参数错误,放大了这个问题。
2. 仍可使用自定义关键字参数的方案
可以保留自定义关键字参数的定义,只需调整调用方式或稍作修改Mailer实现,提供两种可行方案:
方案一:直接调用实例方法
绕过Rails的类方法代理,直接实例化Mailer后调用实例方法:
MyMailer.new.custom(user: @myuser).deliver_now
这种方式完全沿用原有实例方法定义,不需要修改Mailer代码,仅调整调用处即可。
方案二:自定义类方法转发参数
在Mailer中定义对应的类方法,手动将关键字参数转发给实例方法:
class MyMailer < ApplicationMailer def custom(user: , timestamp: Time.now) @user = user @timestamp = timestamp end def self.custom(user:, timestamp: Time.now) new.custom(user: user, timestamp: timestamp) end end
这样原来的调用方式MyMailer.custom(user: @myuser).deliver_now可以保持不变,无需修改调用代码。
折中方案:结合params与关键字参数
如果想兼顾Rails 6+的规范和关键字参数的可读性,可以在实例方法中从params解构出关键字参数:
class MyMailer < ApplicationMailer def custom user = params.require(:user) timestamp = params[:timestamp] || Time.now setup_email(user: user, timestamp: timestamp) end private def setup_email(user:, timestamp:) @user = user @timestamp = timestamp end end
调用时使用Rails推荐的with方法:MyMailer.with(user: @myuser).custom.deliver_now,既符合新规范,又能在内部方法中保留关键字参数的清晰性。
内容的提问来源于stack exchange,提问作者Irina
相关产品推荐
相关产品推荐

