I have a JSON with the following construct.
{
"methods": [
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
},
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
}
]
}
This JSON gets parsed and the arguments are found with:
def parse_json(json)
methods = JSON.parse(json, , :symbolize_names => true)
methods.each do |options|
pass_method(options)
end
end
def pass_method(options)
argument_names = self.class.instance_method(options[:method].to_sym).parameters.map(&:last)
args = argument_names.map do |arg|
if arg == :options
options
else
options[arg] || ''
end
end
self.send(options[:method], *args)
end
Now I would like to pass blocks to those methods. The JSON would look like:
{
"methods": [
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value",
"block": [
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
},
{
"method": "method_name",
"argument": "argument_value",
"options_key": "options_value",
"another_options_key": "options_value"
}
]
}
]
}
How can I make this work? So I can pass blocks to the methods and have sub-methods executed in the block?
Well, first of all I think your current approach of passing arguments based on the names of the method parameters is a bit strange. Instead, why not pass arguments as an array, similar to the form required by Object#send
?
{
"method": "push",
"arguments": [
1, "some_text", {"key": "value"}, ["array"]
]
}
This greatly simplifies the code, and makes it possible to pass keyword arguments using a hash.
As for how to pass blocks to methods, you can do that by constructing a Proc object, and passing that to the send
method using the &block
syntax:
method = :inspect
args = [1, "some_text", {"key": "value"}, ["array"]]
block = proc{|x| x.send(method, *args) }
some_object.send(:map!, &block)
Putting all these ideas together, we arrive at the following solution:
json = <<-JSON
{
"methods": [
{
"method": "push",
"arguments": [
1, "some_text", {"key": "value"}, ["array"]
]
},
{
"method": "delete",
"arguments": ["some_text"]
},
{
"method": "map!",
"arguments": [],
"block": [
{
"method": "to_s",
"arguments": []
}
]
}
]
}
JSON
def to_call_proc(method)
method_name = method['method'] || ''
arguments = method['arguments'] || []
block = to_multi_call_proc(method['block']) if method.has_key? 'block'
if block
proc{|x| x.public_send(method_name, *arguments, &block) }
else
proc{|x| x.public_send(method_name, *arguments) }
end
end
def to_multi_call_proc(methods)
call_procs = methods.map(&method(:to_call_proc))
last_call_proc = call_procs.pop
proc do |x|
call_procs.each{|call_proc| call_proc.call(x)}
last_call_proc.call(x) if last_call_proc
end
end
def call_methods(receiver, methods)
to_multi_call_proc(methods).call(receiver)
end
require 'json'
a = []
call_methods(a, JSON.parse(json)['methods'])
p a
Result:
["1", "{\"key\"=>\"value\"}", "[\"array\"]"]