I'm trying to dynamically call a method given in a string using parameters given in the same string, I'm getting stuck on supplying the parameters though...
I currently have:
query = Query.new
while true
input = gets.split(%r{[/[[:blank:]]/,]})
puts (query.instance_exec(*input.drop(1)) { |x|
instance_eval input.at(0)
})
end
So the method name is input(0) and the arguments to this method are in the rest of input.
Is there any way to call this method with those parameters?
The method you are looking for is send. Its first argument will be the method, and the rest will be passed to that method.
query = Query.new
puts query.send(*gets.split(/\s+/)) while true
You can use while modifier.
Your regex looks complicated. I made it look simple.
Don't forget to use the splat operator *, which decomposes an array.
Related
Hey there
Is it possible to have optional attributes and a block as parameters
for a method call?
Example: I have to call
method(foo, foo: bar, -> { do_something }
and tried it with
def method(foo, *bar, &block)
end
As for my understanding the block always has to be at last position?
After a bit of research I found out the unary(?) * seems to be for
arrays. Since I try to pass a Hash I changed the code to
def method(foo, bar={}, &block)
end
But this doesn't do the trick either. I guess its because he cant
figure out where the bar ends and the block starts.
Any ideas or suggestions? Thank you in advance
Append: Just for the curious why I need this. We have a big json
schema running and have a small DSL that builds the json from the
model definitation. Without going to much into detail we wanted to
implement exportable_scopes.
class FooBar
exportable_scope :some_scope, title: 'Some Scope', -> { rewhere archived: true }
end
On some initializer this is supposed to happens:
def exportable_scope scope, attributes, &block
scope scope block
if attributes.any?
attributes.each do |attribute|
exportable_schema.scopes[scope] = attribute
end
else
exportable_schema.scopes[scope] = {title: scope}
end
end
So this is working fine, I just need a hint for the method
parameters.
Yes, it is possible.
When mixing different kinds of parameters, they have to be included in the method definition in a specific order:
Positional parameters (required and optional) and a single splat parameter, in any order;
Keyword parameters (required and optional), in any order;
Double splat parameter;
Block parameter (prefixed with &);
The order above is somewhat flexible. We could define a method and begin the parameter list with a single splat argument, then a couple of optional positional arguments, and so on. Even though Ruby allows that, it's usually a very bad practice as the code would be hard to read and even harder to debug. It's usually best to use the following order:
Required positional parameters;
Optional positional parameters (with default values);
Single splat parameter;
Keyword parameters (required and optional, their order is irrelevant);
Double splat parameter;
Explicit block parameter (prefixed with &).
Example:
def meditate cushion, meditation="kinhin", *room_items, time: , posture: "kekkafuza", **periods, &b
puts "We are practicing #{meditation}, for #{time} minutes, in the #{posture} posture (ouch, my knees!)."
puts "Room items: #{room_items}"
puts "Periods: #{periods}"
b.call # Run the proc received through the &b parameter
end
meditate("zafu", "zazen", "zabuton", "incense", time: 40, period1: "morning", period2: "afternoon" ) { puts "Hello from inside the block" }
# Output:
We are practicing zazen, for 40 minutes, in the kekkafuza posture (ouch, my knees!).
Room items: ["zabuton", "incense"]
Periods: {:period1=>"morning", :period2=>"afternoon"}
Hello from inside the block
Notice that when calling the method, we have:
Provided the cushion mandatory positional argument;
Overwritten the default value of the meditation optional positional argument;
Passed a couple of extra positional arguments (zabuton and incense) through the *room_items parameter;
Provided the time mandatory keyword argument;
Omitted the posture optional keyword argument;
Passed a couple of extra keyword arguments (period1: "morning", period2: "afternoon") through the **periods parameter;
Passed the block { puts "Hello from inside the block" } through the &b parameter;
Please note the example above servers only to illustrate the possibility of mixing different types of parameters. Building a method like this in real code would be a bad practice. If a method needs that many arguments, it's probably best to split it into smaller methods. If it's absolutely necessary to pass that much data to a single method, we should probably create a class to store the data in a more organized way, then pass an instance of that class to the method as a single argument.
I have a code
def pitch_class(note)
note_hash = {:C=>0, :D=>2, :E=>4, :F=>5, :G=>7, :A=>9, :B=>11}
note_hash[:note]
end
but whenever I try to call the value inside it returns nil.
pitch_class("C")
#=> nil
How can I call the values using the key as the argument?
Thanks!
"C" != :C. Therefore, pitch_class(:C) will work.
You can also use String#to_sym to force the argument inside the function, if you want to be able to accept a string argument. Or you can create the hash with string keys in the first place.
EDIT: Also, :note is not note.
EDIT2: As a performance tweak, I'd rather have note_hash declared outside the method, rather than instantiating it each time the method is called. Stuffing it into a class constant (NOTE_HASH) would be the best way to handle it.
You hardcoded :note symbol instead of reading parameter passed to your method:
def pitch_class(note)
note_hash = {:C=>0, :D=>2, :E=>4, :F=>5, :G=>7, :A=>9, :B=>11}
note_hash[note.to_sym]
end
I'm debugging some Ruby code and come across something which I'm not familiar with.
user_array = user_array.send(:[], hash_key)
What does this line do? I'm confused by the :[] which is passed to send as the first argument.
For reference, user_array is an instance of a Class (Chef::Node), and hash_key is a string.
send dynamically calls a method:
obj.send(:method, arg1, arg2)
will effectively be like:
obj.method(arg1, arg2)
So in your case the code is equivalent to:
user_array.[](hash_key)
The method [] is a special method name, which is normally written in ruby using its syntactic sugar form:
user_array[hash_key]
It simply calls the [] method. It returns an element of the array/hash. It's equivalent to call:
user_array[hash_key]
send calls the first argument with the rest of the parameters.
In this case, it should translate to user_array[hash_key]
http://ruby-doc.org/core-2.1.2/Object.html#method-i-send
I have a function with no parameters declared in its firm, but I need to obtain them if eventually any was passed to it.
For example, in javascript I can have a function as follows:
function plus() {
return operator("+", arguments);
}
As you can see, I can obtain the function arguments via "arguments" implicit parameter.
Does ruby have something similar to javascript argument parameter?
Thanks.
PS: I did a previous research in google, stackoverflow and this book with no result, maybe there is a workaround for this and no an official way to obtain it.
How about using variable length arguments:
def plus(*args)
# Do something with `args` array
end
In ruby you can always put optional arguments in a hash, such as
def some_function(args = {})
end
and you can call it like
some_function :arg1 => some_integer, :arg2 => "some_string"
Forgive the beginner question, but say I have an array:
a = [1,2,3]
And a function somewhere; let's say it's an instance function:
class Ilike
def turtles(*args)
puts args.inspect
end
end
How do I invoke Ilike.turtles with a as if I were calling (Ilike.new).turtles(1,2,3).
I'm familiar with send, but this doesn't seem to translate an array into an argument list.
A parallel of what I'm looking for is the Javascript apply, which is equivalent to call but converts the array into an argument list.
As you know, when you define a method, you can use the * to turn a list of arguments into an array. Similarly when you call a method you can use the * to turn an array into a list of arguments. So in your example you can just do:
Ilike.new.turtles(*a)