wrong number of arguments (given 1, expected 4) - ruby

I have an instance method thats accepting 4 inputs
class Foo
def baz(a, b, c, d)
puts 'something'
end
end
I am writing spec for the instance method and i have
let(:resp) {
Foo.new.baz (
a: '97334',
b: '38',
c: '0001110000',
d: 'N')
}
I tried changing it to the one below but no luck
let(:resp) {
Foo.new.baz '97334', '38', '0001110000','N')
}
why am i getting wrong number of arguments at instance method in Foo class?
Thanks

Try:
let(:resp) {
Foo.new.baz(
'97334',
'38',
'0001110000',
'N'
)
}
You are passing keyword arguments which could be thought of as passing one Hash. So your code is basically equivalent to:
params = Hash.new
params[:a] = '97334'
params[:b] = '38'
params[:c] = '0001110000'
params[:d] = 'N'
let(:resp){
Foo.new.baz(params)
}
This example above obviously only has 1 argument.
Another way to solve this would be parameterizing the function call:
class Foo
def baz(a:, b:, c:, d:)
puts 'something'
end
end
In general this is preferred when there are many args to a function, because it does not require the function user to know the specific order of the function arguments, only which arguments are needed.

In ruby <= 1.9, there are no named parameters, but you can achieve the same effect with a hash. The reason you get an error saying you only have one argument is because ruby assumes you are passing a single hash rather than 4 parameters. You can make it work like so:
class Foo
def baz(myhash)
puts "#{myhash[:a]}, #{myhash[:b]}"
end
end
Foo.new.baz(a: "hello", b: "world) #=> "hello, world"
In ruby 2.0, named parameters exist, but they can only be optional:
class Foo
def baz(a: "hi", b: "mum")
puts "#{myhash[:a]}, #{myhash[:b]}"
end
end
Foo.new.baz(a: "hello", b: "world) #=> "hello, world"
Foo.new.baz() #=> "hi, mum"
In ruby >= 2.1, named params can be optional OR required. you can make named params required by specifying them without default values like so:
class Foo
def baz(a:, b:)
puts "#{myhash[:a]}, #{myhash[:b]}"
end
end
Foo.new.baz(a: "hello", b: "world) #=> "hello, world"
In all of these rubies, your last code snippet should work if you remove the extra parenthesis:
class Foo
def baz(a, b, c, d)
puts 'something'
end
end
Foo.new.baz '97334', '38', '0001110000','N'

Related

Is there a way to pass a block into Class::new as an argument?

It seems like for a vast majority of methods that take blocks, if you have a block, proc, or lambda stored in a variable you can pass it in. For example
mapper = -> (foo) { foo * foo }
(1..5).map(&mapper) # => [1, 4, 9, 16, 25]
The method Class::new can be initialized using a block to create a class, as such:
klass = Class.new do
def foo
'bar'
end
end
klass.new.foo # => "bar"
However, I cannot do something like this
class_body = lambda do
def foo
'bar'
end
end
klass = Class.new(&class_body)
I get ArgumentError (wrong number of arguments (given 1, expected 0)). There is a way around this by doing something like this,
klass = Class.new { class_body.call }
klass.new.foo
But it seems like this shouldn't be necessary. Is there a more idiomatic way to instantiate a class with a block as an argument other than something like
def define_class(&class_body)
Class.new { class_body.call }
end
As both the documentation of Class::new and the error message clearly show, the block is passed one argument [bold emphasis mine]:
new(super_class=Object) { |mod| ... } → a_class
[…]
If a block is given, it is passed the class object, and the block is evaluated in the context of this class like class_eval.
Also, in the error message you posted:
ArgumentError (wrong number of arguments (given 1, expected 0))
The error message is saying that your lambda was passed one argument, but it is expecting none.
The fix is easy: add a parameter to your lambda:
class_body = -> * do
def foo
'bar'
end
end
klass = Class.new(&class_body)
Or, if you don't absolutely require a lambda, you could use a non-lambda Proc:
class_body = proc do
def foo
'bar'
end
end
klass = Class.new(&class_body)

How to determine how Ruby method arguments will be bound without calling the method

Because there are a few different kinds of method parameters in Ruby (required, default, keyword, variable length...), sometimes determining how actual arguments will be bound to formal parameters can be tricky.
I'm wondering if there's a way to determine what this binding will be without actually calling the method. For example, for the following method A#foo:
class A
def foo(a, *b, c)
...
end
end
I would like a method like determine_binding that we can use as follows:
A.instance_method(:foo).determine_binding(1,2,3,4,5) ## returns { a: 1, b: [2,3,4], c: 5 }
That is, determine_binding takes a list of arguments and determines the formal binding to foo's parameters, without actually having to call foo. Is there something like this (or similar) in Ruby?
You might go with a tiny DSL for that purpose.
module Watcher
def self.prepended(base)
base.instance_methods(false).map do |m|
mthd = base.instance_method(m)
names = mthd.parameters.map(&:last).map(&:to_s)
values = names.join(", ")
params =
mthd.parameters.map do |type, name|
case type
when :req then name.to_s
when :rest then "*#{name}"
when :keyrest then "**#{name}"
when :block then "&#{name}"
end
end.join(", ")
base.class_eval """
def #{m}(#{params})
#{names}.zip([#{values}]).to_h
end
"""
end
end
end
class A; def zoo(a, *b, c); 42; end; end
A.new.zoo(1,2,3,4,5)
#⇒ 42
A.prepend Watcher
A.new.zoo(1,2,3,4,5)
#⇒ {"a"=>1, "b"=>[2, 3, 4], "c"=>5}
So close. You're looking for Method#parameters
A.instance_method(:foo).parameters => [[:req, :a], [:rest, :b], [:req, :c]]
See the "Method" documentation for more information.

Can we call normal constructor and paramatrised constructor at a time in Ruby?

I tried like this. The file name of ClassA is instanceAndClassMethods
class ClassA
def initialize #constructor
puts "This is my constructor"
end
def initialize(a,b)
c=a-b
puts c
end
end
From other class I called above class as both are in same folder like:
require './instanceAndClassMethods' #filename should not contain spaces
obj = ClassA.new #constructor are automatically called when object is created
obj=ClassA.new(33,33)
When I run from command prompt, I'm getting:
Traceback (most recent call last):
2: from callMeth.rb:4:in `<main>'
1: from callMeth.rb:4:in `new'
C:/Users/vkuma102/Desktop/Ruby Learning/instanceAndClassMethods.rb:7:in `initial
ize': wrong number of arguments (given 0, expected 2) (ArgumentError)
If this is the case then it is difficult right whereas we can call both normal constructor and constructor with parameters in Java
No, Ruby does not have method overloading. Unlike e.g. Java or Crystal, you only get one method of the same name per class. Your second def is overwriting the first. It's like writing foo = 7; foo = 19 - the value 7 is not accessible any more from foo.
If you want to distinguish different argument lists, you need to do it yourself. Fortunately, unlike Java, Ruby has optional parameters (i.e. parameters with default values):
class ClassA
def initialize(a=nil, b=nil)
if a && b
c = a - b
puts c
else
puts "This is my constructor"
end
end
end
In addition to the overloading solution, which Amadan suggested, you can also provide factory methods to complement your constructor, for example:
class Foo
def initialize(_a = nil, _b = nil, _c = _nil)
#a, #b, #c = _a, _b, _c
end
# factories
def self.make_fancy_foo(x,y,z)
new(bar(x),y+1,baz(z-y))
end
def self.make_special_foo(x)
new(x,x,x)
end
end
This is how you can use them:
foo1 = Foo.new
foo2 = Foo.new(88)
foo3 = Foo.new(3,6,9)
foo4 = Foo.make_fancy_foo(7,-1,5)
foo5 = Foo.make_special_foo(6)
You're overwriting the first constructor, you can only have one method per name in ruby, to achieve the behavior you want you can do something like:
class ClassA
def initialize(a=nil, b=nil)
a && b ? puts(a+b) : puts "This is my constructor"
end
end
Or:
class ClassA
def initialize(*args)
arg.any? ? deal_with_params : puts "This is my constructor"
end
end

Ruby empty parameter in function

I'm trying to enqueue various functions in a generic way with this code :
{ Object.const_get(object_name).new(job[:params]||={}).delay(:queue => queue).send(method_name)}
job is a Hash where I get the name, objects parameters etc...
My problem is in this case :
class Foo
def initialize
puts 'bar'
end
end
Foo doesn't take parameters for its instanciation.
So if I use the previous line with Foo as object_name I'll get this error :
ArgumentError: wrong number pf arguments (1 for 0)
And I absolutly don't want to write something like that :
if job.has_key?[:param] then
Object.const_get(object_name).new(job[:params]||={}).delay(:queue => queue).send(method_name)
else
Object.const_get(object_name).new().delay(:queue => queue).send(method_name)
end
What could I write instead of job[:params]||={} so it works for every case?
Thanks in advance.
you can achieve this with using Foo.send and using an array.
For instance
Object.
const_get(object_name).
send(*(job.has_key?(:param) ? ['new', job[:param]] : ['new']))...
I personally think it is not worth it and an if statement is easier on the eyes.
The initialize method of your Foo class should receive a parameter with a default value. Like this:
class Foo
def initialize(params={})
# Here you do stuff like checking if params is empty or whatever.
end
end
This way you will achieve the two behaviors.
Based on your example, I think the test you accepted might be wrong. Your code suggests that you shouldn't be testing whether the :params key exists in the hash, you should be testing whether initialize takes an argument. If initialize does take an argument, then you send it an argument regardless of whether the :params key exists in the hash. The accepted answer will fail when the :params key doesn't exist in the hash, and yet the initialize method takes an argument--you'll get a 0 for 1 error. Is that a possibility?
class Dog
def initialize(params)
p params
puts "dog"
end
end
class Cat
def initialize
puts "cat"
end
end
class_names = ["Dog", "Cat"]
job = {
params: {a: 1, b: 2, c: 3}
}
class_names.each do |class_name|
class_obj = Object.const_get(class_name)
if class_obj.instance_method(:initialize).arity == 0
send_args = 'new'
else
send_args = 'new', job[:params] ||= {}
end
class_obj.send(*send_args)
end
--output:--
{:a=>1, :b=>2, :c=>3}
dog
cat

Perform method after select variable assignments in Ruby

I'm trying to DRY up some code, and I feel like Ruby's variable assignment must provide a way to simplify this. I have a class with a number of different instance variables defined. Some of these are intended to be hidden (or read-only), but many are public, with read/write access.
For all of the variables with public write-access, I want to perform a certain method after each assignment. I know that, in general, I can do this:
def foo=(new_foo)
#foo = new_foo
post_process(#foo)
end
def bar=(new_bar)
#bar = new_bar
post_process(#foo)
end
However, it seems that there should be a nice way to DRY this up, since I'm doing essentially the same thing after each assignment (ie, running the same method, and passing the newly-assigned variable as a parameter to that method). Since I have a number of such variables, it would be great to have a general-purpose solution.
Simpler solution
If you assign those variables in batch, you can do something like this:
kv_pairs = {:foo => new_foo_value,
:bar => new_bar_value}
kv_pairs.each do |k, v|
self.send(k.to_s + '=', v)
post_process(v)
end
Metaprogramming
Here's some ruby magic :-)
module PostProcessAssignments
def hooked_accessor( *symbols )
symbols.each { | symbol |
class_eval( "def #{symbol}() ##{symbol}; end" )
class_eval( "def #{symbol}=(val) ##{symbol} = val; post_process('#{symbol}', val); end" )
}
end
end
class MyClass
extend PostProcessAssignments
hooked_accessor :foo
def post_process prop, val
puts "#{prop} was set to #{val}"
end
end
mc = MyClass.new
mc.foo = 4
puts mc.foo
Outputs:
foo was set to 4
4

Resources