Ruby use array as class arguments - ruby

I am making a class with a ton of arguments. I will have other classes with similar arguments.
I don't want to type all the arguments multiple times. I'm looking to do something like this:
argList = [arg1, arg2, arg3, ... arg100]
class myClass1
def initialize(*argList)
# ...
end
end
class myClass2
def initialize(*argList, extraArg1, ...)
# ...
end
end
But this doesn't work because the elements of argList are undefined variables.
So, is there a way to use an array as class arguments?

The elements of argList will not be undefined at run time. If you comment out that line the following code runs fine.
# argList = [arg1, arg2, arg3, ... arg100]
class MyClass1
def initialize(*argList)
p *argList
arg1, arg2, arg3 = *argList
puts "arg1 = #{arg1}"
# ...
end
end
class MyClass2
def initialize(*argList, extraArg1)
p *argList
puts "extraArg1 = #{extraArg1}"
# ...
end
end
my1 = MyClass1.new(1,2,3)
my3 = MyClass2.new(4,5,6,7)
#spickermann's comments still hold.
It would be strange for any method to require 100 named arguments.
What are the "arguments" you are passing in, is it just an array of things you will process? In that case just pass in an array not a splat-array.
Are they parameters representing some complex object? In which case maybe you should consider creating the object, potentially a composed object, and passing that in. Maybe you want to consider passing in a Hash object?

Related

Is there any way to call all the methods inside the class with the single line code in Ruby?

I have done online research on this and also searched for the solution on SO but still didn't got any.
Need a simple, efficient, time and space saving way to call all the functions in a class
Here i have a class with many methods defined inside. after the end of the class, i have to call all the defined methods to execute the block of code inside each methods.
class Sample
def initialize(arg1, arg2)
#arg1 = arg1
#arg2 = arg2
end
def method1
puts #arg1
end
def method2
puts #arg2
end
def method3
puts "This is method3"
end
def method4
puts "This is method4"
end
.............
.............
............. etc...
end
Now creating an object for calling the class and method
object = Sample.new(par1, par2)
object.method1
object.method2
object.method3
object.method4
.............
............. etc...
calling the methods one by one using the object.method_name(parameter) is really hard and taking very long space and time.
is it possible to call all the methods by a single line code (or) with any other efficient way?
is it possible to call all the methods by a single line code
Yes, that is possible.
Personally, I don't get the obsession with squeezing everything into a single line. It does not make code smaller, it does not make code better, it does not make code easier to read. But it is technically possible.
In Ruby, line breaks are always optional. They can always be replaced with something else. If the line break is used as an expression separator, it can be replaced with a semicolon (;), which is also an expression separator. If the line break is used to terminate some syntactic element, it can be replaced with either a semicolon (;), a keyword (for example then in if and unless, when in case, do in for, while, and until, and so on), and sometimes just whitespace.
So, you could write your code in a single line like this:
object = Sample.new(par1, par2); object.method1; object.method2; object.method3; object.method4; # … etc …
calling the methods one by one using the object.method_name(parameter) is really hard and taking very long space and time.
Whether you write the code on one line or multiple lines has no effect on the space or time requirements of your program.
If you execute the methods sequentially, the space requirement will be the maximum of the space requirements of all the methods and the time requirement will be the sum of the time requirements of all the methods.
You can execute the methods in parallel. In that case, the space requirement will be the sum of the space requirements of all the methods and the time requirement will be the maximum of the time requirements of all the methods plus any time needed to coordinate the parallel execution and merge the results back together. Also, executing the methods in parallel will change the result of the program!
Either way, you can only improve either space or time, not both.
You need to add any prefix like auto __call__ in method name that you need to call automatically in single line dynamic code
just find method names using simple string operation in method names array then call them using send method
class Sample
def initialize(arg1, arg2)
#arg1 = arg1
#arg2 = arg2
end
def auto__call__method1
puts #arg1
end
def auto__call__method2
puts #arg2
end
def auto__call__method3
puts "This is method3"
end
def auto__call__method4
puts "This is method4"
end
end
object = Sample.new('arg1', 'arg2')
object.public_methods
.select{ |m| m.to_s.include? 'auto__call__'}
.each{ |auto__call_method| object.send(auto__call_method) }
output
arg2
This is method3
This is method4
arg1
if you need to any presidency in method calling then add presidency prefix then sort method names then call it
like below
class Sample
def initialize(arg1, arg2)
#arg1 = arg1
#arg2 = arg2
end
def auto__call_4_method1
puts #arg1
end
def auto__call_3_method2
puts #arg2
end
def auto__call_2_method3
puts "This is method3"
end
def auto__call_1_method4
puts "This is method4"
end
end
object = Sample.new('arg1', 'arg2')
object.public_methods
.select{ |m| m.to_s.include? 'auto__call_'}
.sort
.each{ |my_method_name| object.send(my_method_name) }
output
This is method4
This is method3
arg2
arg1
Can you give more context what these methods are supposed to do?
I think I would go for adding an extra method in there that calls them for you.
def call_em_all
method1
method2
method3
method4
end

(Argument Error) thrown when trying to instantiate a custom object in Ruby

I want to instantiate an object from a class I wrote on a different file. What I got is wrong number of arguments (given 1, expected 0) (ArgumentError)
Here is the main code
# ./lib/parking_lot
require_relative './lot.rb'
class ParkingLotInterface
def initialize(input: $stdin, output: $stdout)
#input, #output = input, output
#lot = nil
end
def prompt_input
#lot = Lot.new(10)
end
end
parking_lot_interface = ParkingLotInterface.new(input: $stdin, output: $stdout)
parking_lot_interface.prompt_input
And here is the object class
# ./lib/lot
class Lot
attr_reader :slots,
def initialize(size)
#slots = Arrays.new(size)
end
end
The error was thrown at the line where I tried to instantiate a new Lot object. Looking at the internet, people who had the same problem got told that they didn't specify def initialize in the class, or they mistyped it. However, I did what they all said and I still faced wrong number of arguments (given 1, expected 0) (ArgumentError)
What did I do wrong?
In Ruby, method definitions are expressions as well (in fact, in Ruby, everything is an expression, there are no statements), so they evaluate to an object. Method definition expressions evaluate to a Symbol denoting the name of the method that was defined.
So,
def initialize(*) end
#=> :initialize
In your code, you have a comma after attr_reader :slots, which means that you pass two arguments to attr_reader, namely the symbol :slots and the expression def initialize(…) … end. Since Ruby is a strict language, the arguments to attr_reader will be evaluated first, before attr_reader itself is executed.
So, what happens first is that the method definition expression gets evaluated. This defines a (private) method named initialize. It also evaluates to the symbol :initialize.
Next, the expression attr_reader :slots, :initialize gets evaluated, which defines two methods named slots and initialize, thus overwriting the method you just defined. Note that this will print a warning:
lot.rb:3: warning: method redefined; discarding old initialize
lot.rb:5: warning: previous definition of initialize was here
You should always read the warnings, the Ruby developers don't spend the hard work putting them in just for the fun of it!
The solution is to remove the comma telling Ruby to look for a second argument.
There is a second error in your code, namely that you misspelt Array within Lot#initialize.
And, there are a couple of stylistic improvements that you could make:
There is no need to pass a path and a filename extension to require_relative. It should be require_relative 'lot'.
Un-initialized instance variables evaluate to nil, so there is no need to initialize #lot to nil.
$stdin and $stdout are the default argument values of the stdin: and stdout: keyword parameters, so there is no need to pass them explicitly.
It is seldom necessary to create an array of a specific size, since Ruby arrays are dynamic and can change their size at any time.
With all this taken in to account, your code would look something like this:
# ./lib/parking_lot
require_relative 'lot'
class ParkingLotInterface
def initialize(input: $stdin, output: $stdout)
#input, #output = input, output
end
def prompt_input
#lot = Lot.new(10)
end
end
parking_lot_interface = ParkingLotInterface.new
parking_lot_interface.prompt_input
# ./lib/lot
class Lot
attr_reader :slots
def initialize(size)
#slots = Array.new(size)
# could be #slots = []
# depending on how you use `#slots` later
end
end
Delete the comma after
attr_reader :slots,
it would be
attr_reader :slots
And take a look, you are trying to instance Arrays (and must not to be in plural) on lot.rb
def initialize(size)
#slots = Arrays.new(size)
end
it would be
def initialize(size)
#slots = Array.new(size)
end

Passing multiple arguments to send

I have a script that contains several methods, each with a varying number of arguments:
def method1
end
def method2(arg1)
end
def method3(arg1, arg2)
end
def method4(arg1, arg2, arg3)
end
I need to invoke these methods from the command line, which I am currently doing like so:
if ARGV.length == 1
send(ARGV[0])
elsif ARGV.length == 2
send(ARGV[0], ARGV[1])
else
send(ARGV[0], ARGV[1], ARGV[2])
end
Is there a one-line way of invoking the method and passing all the arguments (or none if there aren't any)?
Ruby has a splat operator, the unary prefix * operator that can be used in two places with dual meanings:
in a parameter list in a method, block, or lambda definition, it means "package all remaining arguments into an Array and bind it to this parameter"
in an argument list of a message send or a yield as well as the left-hand side of an assignment expression, it means "explode this Array into its individual elements as if they had been written individually in its place"
So, for example:
foo(*some_array)
is equivalent to
foo(some_array[0], some_array[1], some_array[2], …, some_array[some_array.size])
So, in your case, all you need to do is
send(*ARGV)
Note, that this obviously allows anyone who can manipulate ARGV to execute any arbitrary Ruby code, including but not limited to, erasing the hard disk, or launching the proverbial nuclear missiles. But, your original code has that same flaw. You really should perform validation here, but that is orthogonal to your question.
You could use this:
send("method#{ARGV.size+1}", *ARGV)
As an example:
def method1
puts "METHOD 1. No parameter"
end
def method2(arg1)
puts "METHOD 2. #{arg1}"
end
def method3(arg1, arg2)
puts "METHOD 3. #{arg1}, #{arg2}"
end
def method4(arg1, arg2, arg3)
puts "METHOD 4. #{arg1}, #{arg2}, #{arg3}"
end
args = %w(a b c)
send("method#{args.size+1}", *args)
# METHOD 4. a, b, c
It's not really clean or robust, though.
You can use splat operator (*args) for both method definition and call:
def generic_method(*args)
puts "Generic method has been called with #{args.size} parameters: #{args.inspect}"
# Add logic here
end
generic_method
# Generic method has been called with 0 parameters: []
generic_method('a')
# Generic method has been called with 1 parameters: ["a"]
generic_method('a', 'b', 'c')
# Generic method has been called with 3 parameters: ["a", "b", "c"]
No need for send anymore!
As Ursus suggests (but his answer is gone now) you can do this very simply with
send(*ARGV)
But this is potentially dangerous as this works with any method in ARGV[0] so you should have some mechanism to ensure only certain methods are allowed
filter_send(*ARGV)
def filter_send(method, *arguments)
send(method, *arguments) if good_method?(method)
end
def good_method?(sent_method)
%w(method1 method2 method3 method4).include?(sent_method.to_s)
end
so now you can do...
filter_send(*ARGV)
...and be confident that dangerous methods can't be passed

Difference between initializing variables in def and doing so in normal method

Suppose I have two classes like:
class Abc
def initialize(arg1, arg2)
#a = arg1
#b = arg2
end
def sum
return #a+#b
end
end
obj = Abc.new(2, 3)
obj.add # => 5
or
class Abc1
def sum(arg1, arg2)
return arg1+arg2
end
end
obj = Abc1.new
obj.sum(2,3) # =>5
In both classes, I call a sum method and get 5 as the result. Which approach is better and why?
It depends on the use case. If the variables are to be shared among other method calls on the object, then it makes sense to initialize them as instance variables. If they are only used for a particular method call, then they should be passed as method arguments.
So both can be potentially correct, but in your particular code, the second one (Abc1) does not make (much) sense since in the method call of sum, nothing particular to an Abc1 instance is used in the execution. It would make sense as a class method:
class Abc1
def self.sum(arg1, arg2)
arg1 + arg2
end
end
Abc1.sum(2,3) # =>5

How to pass a function as method argument

I am trying to pass a function as an argument to a method of class. I know I need to use proc, but I am not sure I am using the right syntax. This is my attempt.
module MyApp;end
module MyApp::Stats
def self.sum(a)
a.inject(0){ |accum, i| accum + i }
end
def self.mean(a)
sum(a) / a.length.to_f
end
# Many more functions....
def self.sum_proc
Proc.new{|x| sum(x) }
end
def self.mean_proc
Proc.new{|x| mean(x)}
end
# And many more procs
end
class MyData
attr_reader :values
attr_reader :aggregates
def initialize(array)
#values = array
#aggregates = {}
end
def aggregate(aggregator)
puts aggregator.call(#values)
#I would also like to create a hash of aggregator. Something like:
#aggregates["aggregator"] = aggregator.call(#values)
end
end
I can then do
ar = [1,2,3,4,5,6,7,8,9]
data = MyData.new(ar)
And call the aggregate method in various ways:
aggregator = Proc.new{|x| MyApp::Stats.sum(x)}
data.aggregate(aggregator)
data.aggregate(Proc.new{|x| MyApp::Stats.mean(x)} )
data.aggregate(Proc.new{|x| x.count{|y| y > 3.0} })
data.aggregate(MyApp::Stats.sum_proc)
data.aggregate(MyApp::Stats.mean_proc)
I have two issues with this code. First it seems redundant as I have to define the aggregator first and then the associated proc, e.g. sum and sum_proc. Second, I wonder how I could pass any of the standard enumerator methods without defining a proc for it: say count or first.
Finally, I would like to create a hash for the aggregators so that I could do:
puts data.aggregates["sum"]
puts data.aggregates["mean"]
Methods aren't objects in Ruby. You can't pass a method as an argument because you can only pass objects as arguments.
However, you can get a Method proxy object representing a method by calling the method method and passing the name of the method as an argument. Method proxy objects duck-type Proc, so they respond to arity, parameters, and most importantly to_proc and call.
The idiomatic way of taking a single first-class procedure as an argument, is to take a block like this:
def aggregate
yield #values
end
and pass a method using the unary prefix & operator:
data.aggregate(&MyApp::Stats.:sum)

Resources