Ruby passing lots of args - ruby

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.

Related

Make Ruby object respond to double splat operator **

I have a library that has an #execute method like this
def execute(query, **args)
# ...
end
I have a class that generates the data for args (which has a lot of logic depending on user abilities)
class Abilities
def to_h
{ user: user } # and a lot more data
end
end
Now when I'm using #execute I always have to remember to use #to_h, which is pretty annoying and leads to mistakes when someone forgets it:
execute(query, abilities.to_h)
So I was wondering if my Abilities class could somehow respond to the ** (double splat) operator, so that I can simply pass the object:
execute(query, abilities)
When I try to call it like this, it throws an error:
ArgumentError: wrong number of arguments (given 2, expected 1)
So, is there any way to make my Abilities class behave like a Hash? I could derive it like this Abilities < Hash but then I have all the Hash logic on it, which seems pretty dirty.
You can implement to_hash: (or define it as an alias for to_h)
class MyClass
def to_hash
{ a: 1, b: 2 }
end
end
def foo(**kwargs)
p kwargs: kwargs
end
foo(MyClass.new)
#=> {:kwargs=>{:a=>1, :b=>2}}
If you specify the API to execute in such a way that it accepts anything that supports to_h, then you have a solution:
def execute(query, args = {})
args = args.to_h
...
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

Ruby Bracket Method with Block

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.

Magic Methods in Ruby?

Ruby enthusiasts! I am trying to write a DSL in ruby and i would like to be able to create some magic methods (not sure that is the most accurate term for what i want).
I would like to be able to do things like the following:
a = [1, 2, 3]
b = 2
(a contains b)
And have it resolve to true or false.
Essentially, how can i define the function "contains" so that it takes an array a and a variable b and performs a.contains?(b), but without all of the associated ruby-specific syntax?
if you want a DSL that doesn't use ruby syntax, you need to write a parser at the very least to perform the transformation (raganwalds rewrite lib might be a starting point, http://github.com/raganwald/rewrite)
That said, you don't want to do this. This is more code to maintain and Ruby has already made a lot of the tough decisions that make writing a language syntax hard. Natural language programming also isn't much easier for nonprogrammers to use as the exactness of the format is the challenging aspect (see applescript for instance).
You can abuse method_missing. The tricky thing is, that you cannot access the blocks local variables directly. You'll have to capture the blocks inner binding somewhere (unfortunately block.binding returns the block's outer binding).
You can run this code:
DSL.new do
a = [1, 2, 3]
b = 2
a contains b
end
With the following:
class DSL
attr_reader :last_binding
def initialize(&block)
set_trace_func method(:trace).to_proc
instance_eval(&block)
set_trace_func nil
end
def trace(event, file, line, id, binding, klass)
if event.to_s == "call" and klass == self.class and id.to_s == "method_missing"
#last_binding ||= #current_binding
set_trace_func nil
else
#current_binding = binding
end
end
def lvars
eval('local_variables', last_binding).map(&:to_s)
end
def method_missing(name, *args)
name = name.to_s
if lvars.include? name
eval(name, last_binding).send(*args.flatten)
else
["#{name}?", *args]
end
end
end
class Array
alias contains? include?
end
The closest thing I could think of would be:
def contains var, useless_symbol, arr
arr.include? var
end
Then you could call it like:
contains b, :in, a
I don't think there is any way to be able to use infix notation in your own functions.

Mass assignment on construction from within ruby [duplicate]

This question already has answers here:
Closed 13 years ago.
Possible Duplicate:
Idiomatic object creation in ruby
Sometimes it's useful to assign numerous of a constructed arguments to instance variables on construction. Other than the obvious method:
def initialize(arg1, arg2, arg3)
#arg1, #arg2, #arg3 = arg1, arg2, arg3
end
Is there a more concise idiom for achieving the same result? Something like that found in scala for instance:
class FancyGreeter(greeting: String) {
def greet() = println(greeting)
}
Where in this case the object FancyGreeter has a default constructor that provides assignment for it's passed arguments.
In Ruby 1.8, block arguments and method arguments have different semantics: method arguments have binding semantics, block arguments have assignment semantics.
What that means is that when you call a method, the method arguments get bound to the values that you pass in. When you call a block, the values get assigned to the arguments.
So, you can create some pretty crazy looking blocks that way, that seemingly don't do anything:
lambda {|#a|}.call(42)
The block body is empty, but because of the argument assignment semantics, the instance variable #a will be assigned the value 42. It works even crazier:
lambda {|foo.bar|}.call(42)
Yes, attr_writer methods work too. Or what about
foo = {}
lambda {|foo[:bar]|}.call(42)
p foo # => {:bar => 42}
Yup, those too.
And since you can define methods using blocks, you can do this:
class FancyGreeter
define_method(:initialize) {|#greeting|}
def greet; puts #greeting end
end
or even
class FancyGreeter
attr_accessor :greeting
define_method(:initialize) {|self.greeting|}
def greet; puts greeting end
end
However, I wouldn't recommend this for two reasons:
Not many Rubyists know this, be kind to the people who have to maintain the code after you.
In Ruby 1.9 and onwards, block argument semantics are gone, blocks also use method argument semantics, therefore this does no longer work.
I suppose you could do....
def initialize *e
#a, #b, #c = e
end
I don't know about "better" but there are varying levels of 'clever':
def initialize args={}
args.each do |key, value|
instance_variable_set "##{key}", value
end
end
But "clever" is usually dangerous when you program :-)
Edit: Given the edited question, I'll add this:
Class PickMe
def initialize say="what?"
#say = say
end
end
Just because I don't know if you're aware of default options. Otherwise, think of the value of self-documenting code. A cleanly-written 'initialize' method is priceless.
It was either Andy Hunt or Dave Thomas who proposed that Ruby should be able to handle this syntax for initializing member variables from constructor arguments:
def initialize(#a, #b, #c)
...
end
Matz did not accept their proposal; I don't remember why.

Resources