Ruby - get method params names and values [duplicate] - ruby

This question already has answers here:
Is there a way to access method arguments in Ruby?
(11 answers)
Closed 5 years ago.
Is there a way to get method parameters names and values dynamically, maybe a metaprogramming?
def my_method(name, age)
# some code that solves this issue
end
my_method('John', 22) # => { name: 'John', age: 22 }
I want to use this in all my methods in order to log methods calls and respective parameters, but without to do this manually in every method.
Thanks.

Yup! In ruby, it's called the binding, which is an object that encapsulates the context in which a particular line runs. The full docs are here, but in the case of what you're trying to do...
def my_method(arg1, arg2)
var = arg2
p binding.local_variables #=> [:arg1, :arg2, :var]
p binding.local_variable_get(:arg1) #=> 1
p Hash[binding.local_variables.map{|x| [x, binding.local_variable_get(x)]}] #=> {:arg1 => 1, :arg2 => 2, :var => 2}
end
my_method(1, 2)
I'd strongly advise against Binding#eval, if you can possibly help it. There's almost always a better way to sovle problems than by using eval. Be aware that binding encapsulates context on the line at which it is called, so if you were hoping to have a simple log_parameters_at_this_point method, you'll either need to pass the binding into that method, or use something cleverer like binding_of_caller

Related

How to use value of a string to refer to variables?

Some programmer made a method that gets lots of arguements like this:
def age_entry(age_1, age_2, age_3, age_4, age_5, age_6, age_7, age_8)
end
They could pass an array but simply they didn't. I love automation and hate to repeatedly add these variables to and array like this
ages = [age_1, age_2, age_3 ,..., age_8]
I would like to use metaprogramming or other ways to loop with a for or each methods to add them variables to an array like this:
(1..8).each do |index| do
ages << "age_" + index #value of age_[index] get saved to ages
end
P.S. I know I can use copy and paste but this is only for doing automation stuff with Ruby.
"Some programmer" should remember that you can pass in arrays. This sort of method signature is really obnoxious to work with for a multitude of reasons, some of them you've already discovered.
One way to refactor this method and preserve functionality is to just take in varargs:
def age_entry(*ages)
end
Now those values are put in an array for you but you can call the method the same way as before. As a plus you can specify more or fewer entries.
Variables with names like x1, x2 and so on are violations of the Zero, One or Infinity Rule and are a sign you need to think about the problem differently.
You don’t need any metaprogramming here. Just splat them:
ages = [age_1, age_2, age_3 ,..., age_8]
# ⇓ HERE
age_entry(*ages)
If you want to collect age_(1..8) into the array, assuming all local vars are defined, use Kernel#binding:
b = binding
ages = (1..8).map { |i| b.local_variable_get("age_#{i}") }
Suppose the method is as follows.
def oldest(age_bill, age_barb, age_trixie)
puts "Barb is #{age_barb} years old"
[age_bill, age_barb, age_trixie].max
end
oldest(35, 97, 29)
#=> 97
As well as the calculation in the penultimate line (which the OP wishes to avoid), this method requires knowledge of an individual method argument (age_barb). The following is one way to accomplishing both requirements.
def oldest(age_bill, age_barb, age_trixie)
puts "Barb is #{age_barb} years old"
b = binding
args = method(__method__).parameters.map { |arg| b.local_variable_get(arg[1]) }
args.max
end
#=> 97
Barb is 97 years old
Here
args
#=> [35, 97, 29]

What about implicit yield in Ruby? [duplicate]

This question already has answers here:
Can you supply arguments to the map(&:method) syntax in Ruby?
(9 answers)
Closed 8 years ago.
I often write:
some_array.each { |array_element| array_element.some_method(args) }
Why not have an option for an implicit yield so you could write e.g.:
some_array.each { _.some_method(args) }
I am not sure what character _ should actually be, and I imagine it would only be used in the most boilerplate setting, where you're dealing with a one-dimensional array and just trying to yield each item to the block in succession. It would save a lot of redundant typing.
Are you familiar with #to_proc and the & syntax for method calls? They cover some cases similar to the one you show here. For example:
[1, -2, -4].map(&:abs) => [1, 2, 4]
The & is used to pass an object in place of a block. If the object isn't a Proc, #to_proc is automatically called on it to get a Proc before it is used in place of the block. Symbol#to_proc returns a Proc which behaves like: { |obj, *args| obj.symbol_name(*args) }.
In your example, you use args which are presumably captured from the surrounding lexical environment. Symbol#to_proc won't help you there. But it wouldn't be hard to make a new Proc-building method which would. For example:
class Symbol
def with_args(*args)
Proc.new { |x| x.send(self, *args) }
end
end
Then you could do:
some_array.each(&:some_method.with_args(args))
Whether that is any better than an explicit block is up to you. In any case, this is a technique you should be aware of.

Ruby method chaining - `tap` with replacement [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Is there a `pipe` equivalent in ruby?
I'm looking at the tap method in Ruby - but unfortunately the object returned from the passed block is not passed on. What method do I use to have the object passed on?
Here's what I'm trying to (unsuccessfully) do:
obj.tap{ |o| first_transform(o) }.tap{ |o| second_transform(o)}
This is, of course, equivalent to second_transform(first_transform(o)). I'm just asking how to do it the first way.
Doing this is trivial with lists:
list.map{ |item| first_transform(item) }.map{ |item| second_transform(item)}
Why isn't it as easy with objects?
class Object
def as
yield self
end
end
With this, you can do [1,2,3].as{|l| l << 4}.as{|l| l << 5}
You could also consider to make #first_transform and #second_transform instance methods of the item's class (and return the transformed item of course).
These methods definitions should look like this:
class ItemClass
# If you want your method to modify the object you should
# add a bang at the end of the method name: first_transform!
def first_transform
# ... Manipulate the item (self) then return it
transformed_item
end
end
This way you could simply chain the methods calls this way:
list.map {|item| item.first_transform.second_transform }
It even reads better in my humble opinion ;)
The simple answer is tap doesn't do what you think it does.
tap is called on an object and will always return that same object.
As a simple example of taps use
"foo".tap do |foo|
bar(foo)
end
This still returns "foo"
In your example you have an object, and you want to apply two functions to it in succession.
second_transform(first_transform(obj))
UPDATED:
So I guess I'd ask why you want to chain in this way.
obj.do{|o| first_transform(o)}.do{|o| second_transform(o)}
Is that really more clear than
second_transform(first_transform(obj))
Lets take an example I often use
markdown(truncate(#post.content))
or
truncated_post = truncate(#post.content)
markdown(truncated_post)
I guess it depends on the nature of your transform

How to pass a function instead of a block [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Shorter way to pass every element of an array to a function
I know this will work:
def inc(a)
a+1
end
[1,2,3].map{|a| inc a}
but in Python, I just need to write:
map(inc, [1,2,3])
or
[inc(x) for x in [1,2,3])
I was wondering whether I can skip the steps of making a block in Ruby, and did this:
[1,2,3].map inc
# => ArgumentError: wrong number of arguments (0 for 1)
# from (irb):19:in `inc'
Does anyone have ideas about how to do this?
According to "Passing Methods like Blocks in Ruby", you can pass a method as a block like so:
p [1,2,3].map(&method(:inc))
Don't know if that's much better than rolling your own block, honestly.
If your method is defined on the class of the objects you're using, you could do this:
# Adding inc to the Integer class in order to relate to the original post.
class Integer
def inc
self + 1
end
end
p [1,2,3].map(&:inc)
In that case, Ruby will interpret the symbol as an instance method name and attempt to call the method on that object.
The reason you can pass a function name as a first-class object in Python, but not in Ruby, is because Ruby allows you to call a method with zero arguments without parentheses. Python's grammar, since it requires the parentheses, prevents any possible ambiguity between passing in a function name and calling a function with no arguments.
Does not answer your question but if you really just want to increment all your variables, you have Integer#next
4.next
#=> 5
[1,2,3].map(&:next)
#=> [2, 3, 4]

Changing default Ruby arguments

I want to change the default arguments passed to a Ruby function. For example, instead of each time writing
[1,2,3].do_stuff(:option => ' my option ')
I want to modify the defaults so that I can write
[1,2,3].do_stuff
What is the simplest, cleanest, most Ruby-like way of changing default parameters?
>> [1, 2, 3].do_stuff
=> Result I get
>> [1, 2, 3].do_stuff :an_option => a_value
=> Result I really want, but don't want to specify the argument
I like to use super for this. It allows us to add some functionality to the method apart from just changing default arguments:
class Array
def do_stuff(options = {})
# Verify if caller has not passed the option
options[:argument_i_want_to_change] = default_value_i_want unless options.has_key? :argument_i_want_to_change
# call super
super
end
end
Result:
>> [1, 2, 3].do_stuff
=> Result that I really want
UPDATE: Removed reverse_merge! dependency. (Now looking for a better alternatives to using []= method)
(moved from your original question)
I assume you are talking about a method Array#do_stuff that already exists, but you want to modify it slightly (in your case by changing a default parameter).
A post here gives a nice way of doing it. It doesn't suffer from the same problems as the alias technique, as there isn't a leftover "old" method.
Here how you could use that technique with your example problem (tested with ruby 1.9)
class Array
old_do_stuff = instance_method(:do_stuff)
define_method(:do_stuff) { |options = {}|
options[:option] ||= " option "
old_do_stuff.bind(self).call(options)
}
end
You might also want read up on UnboundMethod if the above code is confusing. Note that old_do_stuff goes out of scope after the end statement, so it isn't a problem for future uses of Array.
Are you wanting a solution for code you didn't write yourself? There are two options I'm aware of.
Code you wrote yourself:
def some_method_you_wrote(options)
becomes:
def some_method_you_wrote(options = { :option1 => 'value' })
(Swanand's answer is nice too)
For code you didn't write, look into aliasing methods. (Rails provides something called alias_method_chain for this purpose, IIRC.)

Resources