Ruby : Converting String to a Method Call - ruby

I am new to Ruby. I'm trying to convert a string to a method call in
Ruby. I intend to store all my function calls in an Excel Worksheet and
use the extracted strings to make the actual method call. But I am not
able to convert the string obtained from the excel and use it as a
function call. I read somewhere that the Send() method helps convert
strings to method calls. But I am not able to use it correctly. For the
code mentioned below I obtain a "in <top (required)>': undefined method
Execute_Statement(5)' for main:Object (NoMethodError)"
begin
def Execute_Statement(var1)
puts("Hello",var1)
end
end
x='Execute_Statement(5)' #This would be fed from the Excel Worksheet
send(x)
What am I doing wrong ?

You can either adopt the bad practice, i.e. Just do eval(x). If don't want to adopt it, do some more work as below :
def Execute_Statement(var1)
puts("Hello",var1)
end
s = "Execute_Statement(5)" # I hope this is coming from your excel cell.
method_name,number = s[/.*(?=\()/],s[/\d+/]
send(method_name,number.to_i)
Remove the begin..end block, it is not needed for your case.

You should be passing your parameter as part of the send call. Also the method name needs to be a symbol. In other words define the function name as a symbol and define your parameter as a separate variable, then call send like so:
def Execute_Statement(var1)
puts("Hello",var1)
end
method_name = 'Execute_Statement'.to_sym
parameter = 5
send(method_name,parameter)
As commenter above says this does not seem like a great idea.

Related

Undefined local variable or method `translator' for main:Object (NameError)

I've used a gem and tried to create a method (trans) in my code.
require 'yandex-translator'
translator = Yandex::Translator.new(api_key)
def trans(text)
a = translator.translate text, to: "ru"
return a
end
puts trans("stack")
When I run the code, I get this error:
'trans': undefined local variable or method `translator' for main:Object (NameError)
Why did I get this error, and how can I solve this?
translator variable in this code is defined on class level, hence it’s a local variable in main context (since the whole code is executed in main context.)
You are trying to call it from the instance context, where it is obviously not defined. The easiest way to overcome it, would be to define #translator as being a class’ instance variable:
#translator = Yandex::Translator.new(api_key)
def trans(text)
#translator.translate text, to: "ru"
end
Because in this way you are looking for a local variable translator and you have not. Some solutions:
make translator global
$translator = Yandex::Translator.new(api_key)
or pass translator to trans method
def trans(translator, text)
translator.translate text, to: "ru"
end

Calling private methods by symbol name in Ruby

I have the symbol name of a method that I'd like to call with some arguments. What I'm really trying to do boils down to this code snippet:
method.to_proc.call(method)
In this case, method is the symbol name of a method on the object. In my case, I'm trying to call a method that happens to be private on the object.
This is the error output that I get:
>$ ruby symbol_methods.rb
symbol_methods.rb:33:in `call': private method `test_value_1' called for "value":String (NoMethodError)
from symbol_methods.rb:33:in `block (2 levels) in <main>'
from symbol_methods.rb:30:in `each'
from symbol_methods.rb:30:in `block in <main>'
from symbol_methods.rb:29:in `each'
from symbol_methods.rb:29:in `<main>'
Here's a self-contained example that demonstrates this behavior:
data = [
["value", true],
["any value here", true],
["Value", true],
]
def matches_value(string)
string == "value"
end
def contains_value(string)
string.gsub(/.*?value.*?/, "\\1")
end
def matches_value_ignore_case(string)
string.downcase == "value"
end
#tests
[:matches_value, :contains_value, :matches_value_ignore_case].each_with_index do |method, index|
test = data[index]
value = test[0]
expected_result = test[1]
result = method.to_proc.call(value) # <<== HERE
puts "#{method}: #{result == expected_result ? 'Pass' : 'Fail'}: '#{value}'"
end
The important bit is in the block marked #tests. The data variable is a set of inputs and expected results. The test_value_* methods are private methods that are the tests to run.
I've tried public_send(method, value) and method.to_proc.call(value), but both result in the private method error.
What would be the right way to call a private method named as a symbol in this case? I'm looking for both an explanation and a syntactically correct answer.
use send instead.
puts "#{method}: #{send(method, value) == expected_result ? 'Pass' : 'Fail'}: '#{value}'"
After a fair amount of searching, I found an alternative answer than Object#send, that has an unanticipated feature benefit. The solution is to use the Object#method to return a Method object for the symbol name.
A Method object is a Proc-like callable object, so it implements the #call interface, which fits the bill nicely. Object has many such useful helpers defined in its interface.
In context of the original question, this is how it works:
#tests
[:test_value_1, :test_value_2, :test_value_3].each do |method|
data.each do |test|
value = test[0]
expected_result = test[1]
puts "#{method}: #{self.method(method).call(value) == expected_result ? 'Pass' : 'Fail'}: '#{value}'"
end
end
The important bits are:
self.method(method).call(value)
This will convert the symbol name to a Method object, and then invoke the method with value supplied as the parameter. This works roughly equivalently to the send method solution, in functional terms. However, there are some differences to note.
send is going to be somewhat more efficient, as there's no overhead in the conversion to a Method. Method#call and send use different internal calling mechanisms, and it appears that send has less call overhead, as well.
The unanticipated feature of using Object#method is that the Method object is easily converted to a Proc object (using Method#to_proc). As such, it can be stored and passed as a first-class object. This means that it can be supplied in place of a block or provided as a callback, making it useful for implementing flexible dispatch solutions.

How does the syntax MODULE::METHODNAME('string') work

I recently had cause to use the nokogiri gem to parse html but while i going through their documentation, i came across this ruby syntax that i hadn't seen before
html_doc = Nokogiri::HTML('<html><body><h1>Mr. Belvedere Fan Club</h1></body></html>')
xml_doc = Nokogiri::XML('<root><aliens><alien><name>Alf</name></alien></aliens></root>')
The part of interest for me is Nokogiri::HTML('...'). This looks very much like a method invocation but i know ruby method names cannot be in capital letters. So i looked through code files nokogiri gem and i came across the following definition
module Nokogiri
class << self
###
# Parse HTML. Convenience method for Nokogiri::HTML::Document.parse
def HTML thing, url = nil, encoding = nil, options = XML::ParseOptions::DEFAULT_HTML, &block
Nokogiri::HTML::Document.parse(thing, url, encoding, options, &block)
end
end
# more code
end
I tried reproducing the same code
module How
class << self
def DOESTHISWORK
puts "In How Method"
end
end
end
How::DOESTHISWORK
But it keeps coming back with the error "uninitialized constant How::DOESTHISWORK (NameError)". I know it has to do with the method name starting in capitals but i just haven't been able to figure out how it works in nokogiri.
The difference is in the Nokogiri example the method is being called with parentheses and a parameter value which identifies it as a method call. Your DOESTHISWORK method takes no parameters but can be called with empty parentheses e.g.
irb(main):028:0> How::DOESTHISWORK()
In How Method
=> nil
If you add a parameter to your method that can also serve to identify it as a method like so:
irb(main):036:0> How::DOESTHISWORK 'some param'
Starting method names with a lowercase letter is good practice but isn't enforced. Something that begins with a capital letter is assumed to be a constant and will be looked up as such, this is why the parentheses or parameter is needed to indicate a method is being referred to. Another example:
irb(main):051:0> def Example
irb(main):052:1> puts "An example!"
irb(main):053:1> end
=> nil
irb(main):054:0> Example
NameError: uninitialized constant Example
from (irb):54
from /Users/mike/.rbenv/versions/1.9.3-p194/bin/irb:12:in `<main>'
irb(main):055:0> Example()
An example!
=> nil
I also found this post to be very helpful
What are the restrictions for method names in Ruby?
It's good practice, while not mandatory, to start the method name with
a lower-case character, because names that start with capital letters
are constants in Ruby. It's still possible to use a constant name for
a method, but you won't be able to invoke it without parentheses,
because the interpeter will look-up for the name as a constant

What object do I need to respond to .map(&:key)? (ruby)

I'm doing a code challenge, and know the message I need my code to respond to.
I also know I have the correct data in my object to pass the test, I just can't seem to get the format correct.
The test is
class.method.map(&:name)
Which should return an array of names that the method returns.
I have tried having my method return a hash with :name as a key and an array containing the hash but neither work, I'm getting this error
rb:82:in `each': undefined method `name' for [:name, "Name I want returned"]:Array
What do I need to do to respond to the map call correctly?
class.method.map(&:name) means
class.method.map do |instance|
instance.name
end
So basically your method needs to return a enumeration of objects, which has a method named name.

Ruby: Include a dynamic module name

I have a situation in my Rails application where I need to include arbitrary modules depending on the current runtime state. The module provides custom application code that is only needed when certain conditions are true. Basically, I'm pulling the name of a company from the current context and using that as the filename for the module and its definition:
p = self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
include self.class.const_get(self.user.company.subdomain.capitalize.to_sym)
self.custom_add_url
end
My test module looks like this:
module Companyx
def custom_add_url
puts "Calling custom_add_url"
end
end
Now in the console, this actually works fine. I can pull a user and include the module like so:
[1] pry(main)> c = Card.find_by_personal_url("username")
[2] pry(main)> include c.class.const_get(c.user.company.subdomain.capitalize)=> Object
[3] pry(main)> c.custom_add_url
Calling custom_add_url
If I try to run the include line from my model, I get
NoMethodError: undefined method `include' for #<Card:0x007f91f9094fb0>
Can anyone suggest why the include statement would work on the console, but not in my model code?
I'm doing a similar thing. I found this answer useful:
How to convert a string to a constant in Ruby?
Turns out I was looking for the constantize method. This is the line I'm using in my code:
include "ModuleName::#{var.attr}".constantize
Edit:
So ack, I ran into various problems with actually using that line myself. Partially because I was trying to call it inside a method in a class. But since I'm only calling one method in the class (which calls/runs everything else) the final working version I have now is
"ModuleName::#{var.attr}".constantize.new.methodname
Obviously methodname is an instance method, so you could get rid of the new if yours is a class method.
Include is a method on a class.
If you want to call it inside a model, you need to execute the code in the context of its singleton class.
p = self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
myself = self
class_eval do
include self.const_get(myself.user.company.subdomain.capitalize.to_sym)
end
self.custom_add_url
EDIT:
class << self doesn't accept a block; class_eval does, hence it preserves the state of local variables. I've modified my solution to use it.

Resources