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
Related
That title is not the best, so I will explain further here.
I have a Card class and a Deck class, and in Main, I am creating a Deck of Cards and then printing out all the Cards in the Deck. I've made a to_string method for Card (below):
def to_string
puts "#{self.rank} of #{self.suit}"
end
and then used that and a for/each statement in Main to print all the Cards in Deck:
for card in deck
puts card.to_string
end
But I received an error saying that there was an "undefined method 'each' for #Deck: (NoMethodError). I did some searching and found that the solution was to add this method to my Deck class:
def each(&block)
#deck.each(&block)
end
I do understand (or, I think I do) how .each works, as I used it in creating all the Card objects for my Deck--it will go through an array and grab each array item in turn. So it made sense that for card in deck is basically deck.each. But I'm not really sure what &block is doing here. I did some research on what blocks are (to my understanding, basically "anonymous code"--so for example, the instructions inside of a array.each statement. A method that isn't a formally written method) but I still don't know what &block itself does.
Every method in Ruby can (but doesn't have to) take an optional block argument, denoted with an ampersand & before the argument name. This argument is given the value of an explicit block when the method is called.
We can see this behavior explicitly by writing a function which simply returns its block.
def foo(&block)
p block
end
Then if we run
> foo() # Note: No block argument
nil
> foo { 1 } # Note: Block provided
#<Proc:0x...>
So if you pass an explicit block to a method, it gets passed as a Proc object to the method. This is how .each works. The line
[1, 2, 3].each { |x| puts x }
calls each on [1, 2, 3], passing a block argument whose call method runs puts x. You can think of it as similar to
[1, 2, 3].each(->(x) { puts x })
Here, we pass a regular argument which happens to be a lambda. This is not equivalent to the above (block arguments are treated as special), but we could theoretically have implemented each either way; the block syntax is just more convenient.
As you've correctly surmised, for loops desugar to something kind of like .each. Roughly speaking, the following two are equivalent.
for i in [1, 2, 3]
foo i
end
i = nil
[1, 2, 3].each do |i|
foo i
end
Note that for loops actually go to additional effort to ensure that the variable does escape the loop scope, whereas using .each directly produces a local-only variable that doesn't escape. For this reason, .each is generally considered more idiomatic in Ruby anyway, and most Ruby code shies away from explicit for loops in general. (IMO .each is also prettier and more consistent with all of the other aspects of Ruby syntax)
I'm not sure I succeed to clarify this properly, just a try...
Actually for item in deck is actually syntax sugar for those, that previously learned some procedural language, like Pascal or C, and is equivalent for method call: deck.each { |item| ... }. The part in curly brackets is a block or anynomous function. So, your code:
for card in deck
puts card
Is actually translated to call of each on deck object:
deck.each { |card| puts card }
What actually does this method - you define it for collection of something and it should pass its item one after another to given block. And inside block we can use those item as a variable with name we like (card in example).
You can actually ignore item in block, for example:
deck.each { puts "Hello" }
And if your deck has 52 cards, you'll see 52 times "Hello".
What is most interesting, is that having defined only this each method you actually can have a bunch of others, like count, min, max, map, etc. For this, you only need to include mixin Enumerable in your class.
Like this:
class Deck
include Enumerable
def each(&block)
#deck.each(&block)
And now, if you call deck.count it will actually return 52, or whatever is the number of cards, you've put there.
How it works: it actually executes something like:
c = 0
deck.each { c = c + 1 }
c
Well, hope that helps...
why do I need to specify an each method using &block in the class of the object I am iterating through?
You don't have to use the &block syntax.
each is supposed to yield each element to the given block, e.g.:
class Foo
def each
yield 1
yield 2
yield 3
end
end
foo = Foo.new
for i in foo
puts i
end
# or more idiomatic:
foo.each do |i|
puts i
end
Since you rarely yield hard-coded values, there's usually some kind of loop within each, e.g.:
class Foo
def each
1.upto(3) do |i|
yield i
end
end
end
In your case, the loop is based on #deck.each, so you could write:
def each
#deck.each do |card|
yield card
end
end
In order to make each more useful, it should also return an enumerator if no block is given:
def each
return enum_for(:each) unless block_given?
#deck.each do |card|
yield card
end
end
So what about that &block syntax?
If you read the documentation for Array#each you might notice that it already does all these things – it yields the elements to the block and returns an enumerator if no block is given. So you could avoid writing the above code just by passing the block that was given to your each along to Array#each.
But how can we refer to the given block? That's what the &block syntax is for – it assigns the block to a variable block which can be passed as a block argument to another method using the same &block syntax:
def each(&block)
#deck.each(&block)
end
Some considerations:
you should avoid for and prefer each-style loops
to_string should just return the string, not print it. You can omit self and you should call it to_s:
def to_s
"#{rank} of #{suit}"
end
which allows you to directly puts your card:
deck.each do |card|
puts card
end
it might be cleaner to call the method each_card
How can I pass an argument to the definition of a singleton method of [] on A, in other words A['some_argument']?
class A
def self.[]
# some_argument
end
end
Just as to any other method, using argument list:
class A
def self.[](arg)
puts arg
end
end
A[1]
# => 1
Good answers are already given, but I think they miss to mention an important point.
The method [], when used ordinarily in the form foo[params] is actually in syntax sugar form. The underlying method name is [], and calling it in the underlying form would be foo.[](params).
Syntax sugar plays around with syntax, and transforms a method call in the form foo[params] into foo.[](params). But that does not work in method definition, so you have to define such method in the underlying form, not in the syntax sugar form.
class A
def self.[](*args)
puts args
end
end
> A[1,2,3]
1
2
3
You can also implement "set" functionality
class A
def self.[]=(key, value)
puts "#{key} = #{value}"
end
end
A['one'] = '1'
# one = 1
def my_function(&block)
p block.call #1
# lambda{return "inside the block"}.call #2
end
p my_function{return "implicit block"}
Why is line 1 giving a LocalJumpError(Its saying unexpected return)?
Although according to me line 1 and line 2 are basically the same thing.The block variable here is a proc object and so is a lambda.
Shouldn't they also behave the same. line 2 once uncommented doesn't seem to give an error
#Andre
def my_function(&block)
p block.call #1
# lambda{return "inside the block"}.call #2
end
def abc
p my_function{return "implicit block"}
end
abc
Shouldn't this work?
There are many differences between Lambda and Proc, as you can see in this post for example.
One of them is how the return behaves in each case.
When you return in a Proc, it will return from the method it is being called.
And when you return in a Lambda it returns only to outside the lambda code.
The reason for the LocalJumpError is simply because you are you're calling return probably from your ruby console and there is no enclosing method to return. So if you surround your code with a method and call it, it will work.
def test
def my_function(&block)
p block.call
end
p my_function{ return "implicit block" }
end
test
=> "implicit block"
def my_function(&block)
p block.call #1
# lambda{return "inside the block"}.call #2
end
p my_function{return "implicit block"}
Why is line 1 giving a LocalJumpError(Its saying unexpected return)?
A block (and a Proc) return from their enclosing methods. In your example, there is no enclosing method (the block literal is at the top-level), therefore there is nothing to return from.
Lambdas OTOH return from themselves, just like methods.
def my_function(&block)
p block.call #1
# lambda{return "inside the block"}.call #2
end
def abc
p my_function{return "implicit block"}
end
abc
Shouldn't this work?
Yes, it should, and it does.
For completeness' sake: there are two differences between methods / lambdas and blocks / Procs:
Blocks and Procs return from their enclosing methods, lambdas and methods return from themselves.
Blocks and Procs use loose argument binding with semantics that are similar to assignment (in fact, prior to Ruby 1.9, they used assignment semantics), lambdas and methods use strict argument binding.
Here's a kind-of stupid mnemonic I use: "block" and Proc rhyme and they behave the same, "method" and "lambda" are both Greek words and they behave the same.
I would like to define the [] method on a class of my own creation to take a block. I have done so as follows.
class A
def self.[](*args, &block)
puts "I am calling #{block} on #{args}."
block.(*args)
end
end
I can invoke this as follows.
# Explicit method invocation
A.[](1) { |x| puts x }
# With a procedure argument
arg = proc { |x| puts x }
A[2, &arg]
However, what I would like to be able to do is this.
A[3] { |x| puts x }
Which unfortunately seems to produce a syntax error. Is there a block syntax for the bracket method, or am I stuck with the first two ways of invoking it? In fact, more generally, which Ruby method names will allow blocks in their invocation, as it seems that there might be a limitation on when this is allowed?
There's not much you can do against a syntax error, so you'll have to change the syntax.
If you accept :
to define (i.e. pollute) an uppercase method inside Kernel (similar to Kernel#Array)
to use parens instead of brackets
You could write :
class A
def self.call_block_with_args(*args, &block)
puts "I am calling #{block} on #{args}."
block.call(*args)
end
end
module Kernel
def A(*args, &block)
A.call_block_with_args(*args, &block)
end
end
It works this way :
A(3) { |x| puts x }
#=>
# I am calling #<Proc:0x000000012b9c50#block_brackets.rb:14> on [3].
# 3
It's not clean, but it's probably the closest you can be to A[3] { |x| puts x }.
Blocks work with normal method calls only.
Ruby has plenty of operators, listing all of them here would be exhaustive, there are more than two dozens. Even `a` and !a and -a are method calls in Ruby. And obviously there are limitations to all these operators, eg + must take one parameter but not more, et cetera.
Fun fact, loop is a method call too.
I have a method that receives about 20 arguments, in that argument I would like to pass all those to another method. Can I pass them all into the second method without retyping them all? Is there an args array or something that I can pass?
What I have:
def my_method(arg1, arg2, ..., arg20)
#do some stuff
my_second_method(arg1, arg2, ..., arg20)
#do other stuff
end
What I would like:
def my_method(arg1, arg2, ..., arg20)
#do some stuff
my_second_method(args[array])
#do other stuff
end
Having a method that accepts that many arguments is a code smell. This method is almost surely trying to do too many things with too many different kinds of data.
That said, if this is unavoidable, try this approach, using Ruby's splat operator.
def one(*args)
# process/validate args
two(*args) # Note that the splat is needed here too.
end
def two(*args)
# do work
end
You can use Ruby's splat operator:
def my_method(*args)
# do some stuff
my_second_method(*args)
# do other stuff
end
def my_second_method(*args)
# use args[0] through args[19]
end
So, this might not be the best idea, but having said that, you can use a hash to manage arguments.
Rails does this a lot:
render :partial => "new", :locals => {:object => #my_object}
Ruby's ActiveSupport has a way to deal with this, the extract_options! function, that is detailed on simone carletti 's blog
def as_array(*args)
if args.length == 3
a, b, c = args
puts "a=#{a} b=#{b} c=#{c}"
end
end
as_array 1, 2, 3
There's a common idiom that you can often see in Ruby code:
def method1(*args)
method2(*args)
end
def method2(*args)
end
method1(a, b, c, d, e, f, g)
Notice how the splat operator is used. It basically first packs the args into an array, and then, on method call, unpacks it back into the individual arguments. It's very handy when you just need to pass the args further to another method, eg in an initializer.