Search code examples
ruby-on-railsrubymonads

Rails pass more than one parameter into dry-monads


Let's assume I've got below monad class:

require 'dry-monads'
require 'dry/monads/all'

module Test
  class MonadsClass
    include Dry::Monads

    def initialize(signees)
      @signees = signees
    end

    def call
      Success(signees)
        .bind(method(:fetch_template))
        .bind(method(:envelope_from_template))
    end

    attr_reader :signees

    private

    def fetch_template(signees)
      key = "template_#{signees.size}".to_sym
      template_id = Rails.application.credentials.some_api.fetch(key)

      Success(template_id: template_id, key: key)
    end

    def envelope_from_template(template_id:, key:)
      response = some_api.copy_from_template(template_id, key)

      response.failure? ? Failure(response.failure) : Success(response.value!)
    end
  end
end

Why in this combination I'm getting strange error of:

Failure/Error:
def envelope_from_template(template_id:, key:)
  response = some_api.copy_from_template(template_id)

  response.failure? ? Failure(response.failure) : Success(response.value!)
end

ArgumentError:
wrong number of arguments (given 1, expected 0; required keywords: template_id, key)

Solution

  • I think the issue is that you are expecting it to pass kwargs (keyword arguments) but it is passing a Hash. Ruby 2.7 deprecated "automatic conversion" of Hash to kwargs and ruby 3.0 removed it completely. You can read more about the separation of positional and keyword arguments Here

    In other words you are expecting it to call

    envelope_from_template(template_id: 123, key: 'abc')
    

    but you are actually calling

    envelope_from_template({template_id: 123, key: 'abc'})
    

    Instead you have a couple options which should work:

    • Option 1 (call bind with a block)
    def call
      Success(signees)
        .bind(method(:fetch_template))
        .bind {|h| envelope_from_template(**h) }
    end
    
    • Option 2 - Change the method signature and then use Hash access methods in the body
    def envelope_from_template(params)
      response = some_api.copy_from_template(params.values_at(:template_id, :key))
    
      response.failure? ? Failure(response.failure) : Success(response.value!)
    end