Reputation: 4536
I have a module that has a function defined within:
module Settings
def find_by_name(name, start)
puts "find_by_name: Found me!"
end
In miniTest I'm trying to test "sending" to this function in a manner that simulates it dynamically being defined/called at runtime. Here's what I'm trying to do in MiniTest.
require 'settings'
class TestFileUtilities < MiniTest::Unit::TestCase
def test_dynamic_call_to_Settings
foreign_function = 'Settings::find_by_name'
send(foreign_function, 'name.txt', 'Users/MCP/Desktop/Projects/TestDir')
end
What I am expecting is that send() will call the "foreign_function" method, converting the string to a symbol, and then will add the two parameters to the call. All I want is no errors, and a puts to my console so that I know it's working...then I'll start defining "assertions". Right now though I get:
NoMethodError: undefined method `Settings::find_by_name' for #<TestFileUtilities:0x007fdf59840310>
This is the first time I'm playing around with Ruby's ability to dynamically call methods and I was able to get it to work when I wasn't using the scope resolution operator and was include Settings
inside of TestFileUtilities
but I'd rather use scope so that user defined functions don't conflict with anything inside of my class (not just conflict with MiniTest, but the actual FileUtilities class). Any thoughts/help would be greatly apprecaited! For anyone who's stumbled here looking for info, this is what got me started.
Upvotes: 4
Views: 4539
Reputation: 4536
I this specific instance I had trouble trying to include functions that were in another module, not within any class. I was require
ing the module I was trying to send to, but when I would do ModuleName::send(:function_name)
I was getting an error stating "no method exists by that name" (or something close to that error). What fixed this problem was to define the functions in the module that I was trying to "send" to as "self.function_name".
module Something
def self.my_function
puts "Silliness"
end
end
Then in the sending file/module/class:
require '../lib/something.rb'
class MyClass
def this_crazy_thing
funct = 'my_function'
Something::send(funct)
end
end
So now the above works...by simply defining the function in module Something
as self
. Which as per my understanding has to do with how objects are instantiated in Ruby. What got me here is that, not using the dynamic send
functionality...if I knew the name of the function in the module that I wanted to call, I could call it with:
ModuleName::function_name(params)
so I expected something similar to work with send
. My guess is that since I'm trying to send to an object, the function needs to be tied to the object I'm sending to, namely the module, so that it can be called as an instance of that object. Clearly I'm still wrapping my head around the fact that everything is an object in Ruby. Hopefully this at least gets someone on the right path. If someone can explain why exactly this works:
module ModuleSomething
def a_function
puts "hi"
end
end
require '../lib/modulesomething'
class MyClass
def this_thing
ModuleSomething::a_function
end
end
but not:
require 'ModuleSomething'
class MyClass
def this_string
funct = 'a_function'
ModuleSomething::send(funct)
end
end
That might help clear it up a little more as to why my solution worked. When I find out more I will add to this. Thanks guys!
Upvotes: 1
Reputation: 20408
There are several issues with your example.
You haven't done anything apparent to make find_by_name
a class method of Blueprints
, so even skipping the send
part you should get NoMethodError
if you tried to call Blueprints::find_by_name('name.txt', 'Users/MCP/Desktop/Projects/TestDir')
To remedy this you would have to say Blueprints.extend(Settings)
which would mixin the Settings module to the Blueprints class with its instance methods becoming class methods of Blueprints.
A method name is a symbol, Object#send
expects a symbol as its first arg, if it's a string it converts it to a symbol. You're passing the string 'Blueprints::find_by_name'
which is a very odd name for a method and not one you've defined. I think what you intended is to call the method find_by_name
on the class or module Blueprints
. To do this, you can say foreign_function = 'find_by_name'; Blueprints.send(foreign_function, 'name.txt', 'Users/MCP/Desktop/Projects/TestDir')
If you need to resolve the class/module name dynamically too, you want Module#const_get
.
So this is what I think you mean to say:
module Settings
def find_by_name(name, start)
puts "find_by_name: Found me!"
end
end
class Blueprints
extend Settings
end
require 'settings'
class TestFileUtilities < MiniTest::Unit::TestCase
def test_dynamic_call_to_actual_Blueprints
klass = 'Blueprints'
func = 'find_by_name'
Object.const_get(klass).send(func, 'name.txt', 'Users/MCP/Desktop/Projects/TestDir')
end
end
Upvotes: 3
Reputation: 5670
Its not clear from your question how the class Blueprints relates to Settings. I'll assume that either Blueprints is a subclass of Settings or you meant to use Settings instead.
What I am expecting is that send() will call the "foreign_function" method, converting the string to a symbol, and then will add the two parameters to the call.
You need to call send() on the object (or class) that you want to send() to:
foreign_function = 'find_by_name'
Blueprints.send(foreign_function, 'name.txt', 'Users/MCP/Desktop/Projects/TestDir')
Upvotes: 1