How do I troubleshoot "wrong number of arguments" in solidus affirm - ruby-on-rails

Struggling to get solidus_affirm working and hoping anyone might have some ideas. I always have trouble with "wrong number of arguments" errors like this.
Can get through checkout all the way to the confirm step, where it throws a 500.
ArgumentError in Spree::CheckoutController#update
wrong number of arguments (given 2, expected 1)
Extracted source (around line #28):
def post(path, **data)
connection.post(normalized_path(path), data)
end
affirm-ruby (1.1.2) lib/affirm/client.rb:28:in 'post'
affirm-ruby (1.1.2) lib/affirm/client.rb:10:in 'public_send'
affirm-ruby (1.1.2) lib/affirm/client.rb:10:in 'request'
affirm-ruby (1.1.2) lib/affirm/charge.rb:7:in 'authorize'
solidus_affirm (4f8d9ee63345) lib/solidus_affirm/affirm_client.rb:18:in 'authorize'
solidus_core (3.1.5) app/models/spree/payment_method.rb:40:in 'authorize'
if I put a binding.pry in lib/affirm/charge.rb:7:in 'authorize' of the affirm gem, and try the respond Client.request method there, I get a faraday deprecation warning, not sure if that's the cause or unrelated. Seems like it's injecting an auth header or something?
pry(Affirm::Charge)> respond Client.request(:post, "charges", checkout_token: token)
WARNING: 'Faraday::Connection#basic_auth' is deprecated; it will be removed in version 2.0.
While initializing your connection, use '#request(:basic_auth, ...)' instead.
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
In that same binding.pry, I tried a few things to see what came back:
[2] pry(Affirm::Charge)> respond Client.request(:post)
ArgumentError: wrong number of arguments (given 1, expected 2)
pry(Affirm::Charge)> respond Client.request(:post, "charges")
WARNING: 'Faraday::Connection#basic_auth' is deprecated; it will be removed in version 2.0.
While initializing your connection, use '#request(:basic_auth, ...)' instead.
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
ArgumentError: wrong number of arguments (given 2, expected 1)
Any help troubleshooting the error would be much appreciated.

Apparently the error was caused by a change in the behavior of the double splat operator in Ruby 3 and needs to be fixed in the affirm-ruby gem. I submitted a PR and overrode the methods in an initializer.
In Ruby 3.0, positional arguments and keyword arguments will be separated.
Ruby 2.7 will warn for behaviors that will change in Ruby 3.0. If you
see the following warnings, you need to update your code:
Using the last argument as keyword parameters is deprecated, or
Passing the keyword argument as the last hash parameter is deprecated,
or Splitting the last argument into positional and keyword parameters
is deprecated In most cases, you can avoid the incompatibility by
adding the double splat operator. It explicitly specifies passing
keyword arguments instead of a Hash object. Likewise, you may add
braces {} to explicitly pass a Hash object, instead of keyword
arguments.
Source: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
The splat operator was breaking things: request(method, path, **data)
I created an initializer at config/initializers/affirm-ruby.rb to override the gem's methods in our Solidus app.
module Affirm
class Client
class << self
def request(method, path, data={})
new.public_send(method, path, data)
end
end
def get(path, data={})
connection.get(normalized_path(path), data)
end
def post(path, data={})
connection.post(normalized_path(path), data)
end
end
end

The problem is that the call to request() is ambiguous.
respond Client.request(:post, "charges", checkout_token: token)
Looking at the source for the request() method, it takes two specific positional arguments, followed by any number of positional arguments. That's the problem: it only takes positional arguments. Your call includes a keyword argument of checkout_token, but the request() method doesn't know what that is, because there's no checkout_token keyword parameter on the method definition.
If you need to give a method a hash, you should give it exactly that, just like the article you linked suggests:
respond Client.request(:post, "charges", { checkout_token: token })

Related

Calling a method with keywords arguments

Ruby 2.6.3
Rails 5.2
def test_method(param1:, param2:, param3:)
end
test_method(param1: "food")
It is giving me the following error message:
Traceback (most recent call last):
2: from (irb):11
1: from (irb):8:in `test_method'
ArgumentError (missing keywords: param2, param3)
I thought the point of using method parameters, is for me to be able to specify the params I want to use, when calling this method. What am I missing? I am using this in a Rails 5.2 application, if that makes a difference.
There are requried keyword arguments (use something:) and optional keyword arguments (use something: default_value). I think required keyword parameters should go before optional ones.

What is the difference between `try` and `&.` (safe navigation operator) in Ruby

Here is my code:
class Order < Grape::Entity
expose :id { |order, options| order.id.obfuscate }
expose :time_left_to_review do |order, options|
byebug
order&.time_left_to_review # ERROR
end
expose :created_at { |order, options| order.last_transition.created_at }
end
# NoMethodError Exception: undefined method `time_left_to_review' for #<Order:0x007f83b9efc970>
I thought &. is a shortcut for .try but I guess I was wrong. May someone point me to the right direction regarding what I am missing?
I feel like it's not ruby related. Grape maybe? Though I don't get how it could be.
&. works like #try!, not #try.
And here is description of #try! (from documentation):
Same as #try, but will raise a NoMethodError exception if the receiving is not nil and does not implemented the tried method.
So basically it saves you from calling a method on nil, but if an object is presented it will try to call its method as usual.
The quote is from Rails Documentation, and so it's important to emphasize
that Ruby does not provide #try; it's provided by Rails, or more accurately ActiveSupport. The safe navigation operator (&.) however, is a language feature presented in Ruby 2.3.0.
The try method ignores a lot of things, it just gives it a shot and calls it a day if things don't work out.
The & conditional navigation option will only block calls on nil objects. Anything else is considered to be valid and will proceed with full consequences, exceptions included.
I am arriving to the party a bit late here, the other answers have covered how it works, but I wanted to add something that the other answers have not covered.
Your question asks What is the difference between try and &. in Ruby. Ruby being the key word here.
The biggest difference is that try doesn't exist in Ruby, it is a method provided by Rails. you can see this or yourself if you do something like this in the rails console:
[1, 2, 3].try(:join, '-')
#=> "1-2-3"
However if you do the same thing in the irb console, you will get:
[1, 2, 3].try(:join, '-')
NoMethodError: undefined method `try' for [1, 2, 3]:Array
The &. is part of the Ruby standard library, and is therefore available in any Ruby project, not just Rails.
In addition to the above answers, I am adding some examples.1
account = Account.new(owner: Object.new)
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
As we can see, try doesn't check if the receiver responds to the given method or not. Whereas try! and &. behaves the same. If the receiver responds to the address method, all of them will return the same result. I prefer using &. as it looks more cleaner.
For more information on The Safe Navigation Operator (&.), I have found this blog really helpful https://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

Deprecation warning for using (*args) with method

I'm going to be deprecating passing arguments on a method. I know how to use ActiveSupport::Deprecation for a method, but the method isn't going anywhere. Just passing arguments to it is going away.
This particular method is used a TON in testing, so I definitely don't want a warning every time! I've already removed the arguments from the tests, so I shouldn't see any warnings when I run the test suite unless someone adds the method with the arguments. How can I make this happen?
Here's a faked version of the method with the (*args) that will be deprecated:
def assert_in_tests(*args)
code_happens_here
end
The method (once the (*args) are deprecated) will be the following (and I don't want a deprecation warning when it's utilized):
def assert_in_tests
code_happens_here
end
the method isn't going anywhere. Just passing arguments to it is going away ... so I shouldn't see any warnings when I run the test suite unless someone adds the method with the arguments
A simple if statement should suffice would it not?
def assert_in_tests(*args)
ActiveSupport::Deprecation.warn("args to be deprecated ...") if args.present?
code_happens_here
end

RSpec stub throws wrong number of arguments error

I'm try to stub the Slack gem with the new syntax (old syntax throws the same error):
before :each do
allow(Slack).to receive(:channels_join).and_return(true)
end
This line throws wrong number of arguments (2 for 1). Breaking up the line into pieces, it seems like the call to .to is throwing the error:
a = allow(Slack)
b = receive(:channels_join)
c = b.and_return(true)
a.to(c)
Changing the arguments to .to didn't change anything:
a.to(c, c)
throws the same 2 for 1 error.
a.to(5)
throws a reasonable error: only the receive or receive_messages matchers are supported with allow(...).to, but you have provided: 5
Why is the 2 for 1 error being thrown?
If you have enabled the verify_partial_doubles option than RSpec will check if Slack responds to :channels_join when you stub it. Unfortunately, Slack.respond_to? is implemented incorrectly:
def self.respond_to?(method)
return client.respond_to?(method) || super
end
The problem is that Object#respond_to? accepts two arguments (and has for years, since at least 1.8.7, if not sooner!), and RSpec passes a 2nd arg, expecting that respond_to? will accept two arguments since it is supposed to.
To fix it, you can (temporarily) monkey patch the slack gem:
module Slack
def self.respond_to?(method, include_all=false)
return client.respond_to?(method, include_all) || super
end
end
This should really be fixed in the slack gem, though, so I'd encourage you to open a PR with the maintainers with this fix.
More broadly, when you run into this kind of error, you can learn more about the problem by passing rspec the -b (or --backtrace) flag, which will print the full backtrace. In your situation I would have expected it to show both the respond_to? call site within RSpec and the line where Slack defines respond_to? accepting only one argument. Then you could look at those lines to figure out what's going on.

Why URI.escape fails when called on ActionView::OutputBuffer?

I'm upgrading an application from Rails 2 to Rails 3. Apparently, calling render() now returns ActionView::OutputBuffer and not String. I need to pass the results of render() to URI.escape(), and this fails with exception...
Here is my brief testing in the console
ob = ActionView::OutputBuffer.new("test test")
URI.escape(ob)
`NoMethodError: undefined method 'each_byte' for nil:NilClass`.
from /opt/ruby19/lib/ruby/1.9.1/uri/common.rb:307:in `block in escape'
from ..../ruby/1.9.1/gems/activesupport-3.2.1/lib/active_support/core_ext/string/output_safety.rb:160:in `gsub'
from ..../ruby/1.9.1/gems/activesupport-3.2.1/lib/active_support/core_ext/string/output_safety.rb:160:in `gsub'
from /opt/ruby19/lib/ruby/1.9.1/uri/common.rb:304:in `escape'
from /opt/ruby19/lib/ruby/1.9.1/uri/common.rb:623:in `escape'
Moreover, calling to_s on OutputBuffer returns same OutputBuffer class, so I cannot even convert this buffer into a honest string?
ob.to_s.class
ActionView::OutputBuffer
Of course, calling URI.escape("test test") returns "test%20test" as expected, so this is not URI problem.
Environment:
ruby 1.9.3p125 (2012-02-16 revision 34643) [i686-linux]
Rails 3.2.1
My question is: Why does this happen and how can I work around this issue?
Update: Apparently, using '' + ob as a form of ob.to_s converts OutputBuffer to String, which effectively works around the problem... But my question 'why does this happen' still remains, e.g. is this a bug, should I report it, or I'm doing something wrong?
This is a bug in Rails:
When calling gsub with a block on an ActiveSupport::SafeBuffer the global variables $1, $2, etc. for referencing submatches are not always properly set (anymore?) when the block is called.
This is why URI.escape (and any other function that uses gsub() will fail on ActiveSupprt::Safebuffer.
There are several discussions about this, apparently the safest route right now is to call to_str before passing SafeBuffer to anything that can call gsub, e.g. URI.encode, escape_javascript and similar functions.
My other quesion about to_s returning the same class - obviously safe buffer will return itself and not a bare String, this is by design. In order to get a true String, .to_str can be used.
This is due to the fact that Rails 3 introduced the concept of safe buffers
In Rails3 your Views are protected by XSS by default by making all rendering be safely escaped unless you explicitly use the raw() helper or html_safe
This is a dumb bug that i'm currently encountering in Rails 5. My stupid workaround was to do something like
ob = ActionView::OutputBuffer.new("test test")
URI.escape(ob.to_sym.to_s)
Again it works, but i'm still looking for a cleaner solution.

Resources