ruby-on-railsrspecruby-3ruby-3.1

Handling args and kwargs Ruby 3.0 and 3.1 with Rspec


I am doing an upgrade from Ruby 3.0.6 to Ruby 3.2.2 on our project, and I ahve encountered a weird issue that I tried to understand for past few days. We are using RSpec for tests and in one of the tests we have this code:


    let(:request) { FactoryBot.build(:custom_request) }

    context "when not requesting their data" do
      let(:mail) { CustomtMailer.request_email(request: request) }

      it "renders the headers" do
        #expect something
      end
    end

and CustomMailer class looks like this:

class CustomMailer < ActionMailer::Base
  layout "application_mailer"

 
  def request_email(request:, csv: nil)
    #some data processing

    recipients = request.email
    sender = request.sender
    subject = "Some subject"

    mail(to: recipients, subject: subject, from: sender)
  end

Well, the first problem I have with existing code is that while the request_email is an instance method, it is being called as class method in whole project, and somehow it works??? Second problem is the tests used to pass with Ruby 2.7.8 and Ruby 3.0.6. But after Ruby 3.1 tests are failing, and I get this error:

ArgumentError: wrong number of arguments (given 1, expected 0; required keyword: request)

My first idea was to make the method a class method, since it doesn't use any instance variables, but then I get the problem with the mail function NoMethodError: undefined method 'mail' for CustomMailer:Class

But the most troubling thing is that the call to the request_email method doesn't fail, and when I tried debugging and inspected how the params were passed, I saw that all arguments were passed as positional arguments, and are a part of args object, and not kwargs object. Any ideas why it was working before, and how to fix this?


Solution

  • A lot of time passed, but someone reminded me of this question, and I'm posting a solution now. The way I solved the issue was I moved from CustomtMailer.request_email(request: request) to CustomtMailer.with(request: request).request_email

    and in the mailer class, instead of calling keyword argument

    def request_email(request:)
        # ...
        req = request
        # ...
      end
    

    I gett request value like this

    def request_email
       # ...
       req = params.fetch(:request)
       # ...