I'm struggling with code that looks like the example below (but actually does something useful). The block that is passed to def_my_method is of course created in the context of the class, when I want to evaluate it in the context of the instance that has the instance method. How do I do this?
module Formatters
# Really useful methods, included in several classes
def format_with_stars(str)
return "*** #{str} ***"
end
end
class Test
include Formatters
STRINGS = ["aa", "bb"]
def self.def_my_method(method_name, another_parameter, &format_proc)
define_method(method_name) do
# In reality, some more complex code here, then...
return STRINGS.map(&format_proc)
end
end
def_my_method(:star_strings, "another_parameter") { |str| format_with_stars(str) }
# Define other methods
end
tt = Test.new
puts tt.star_strings
# Throws undefined method `format_with_stars' for Test:Class (NoMethodError)
You can use instance_exec to execute the passed block in the right context. Instead of passing &format_proc directly to the call to map, pass a block that calls it using instance exec.
Something like this:
def self.def_my_method(method_name, another_parameter, &format_proc)
define_method(method_name) do
# In reality, some more complex code here, then...
return STRINGS.map{|str| instance_exec(str, &format_proc)}
end
end
This produces this:
$ ruby tt.rb
*** aa ***
*** bb ***
for me (where tt.rb is the arbitary name I gave the file), which I think is what you want for this example.
...
class Test
- include Formatters
+ extend Formatters
...
should do the trick.
Related
I have a function let's say A whose output and functionality I have to test, A calls another function B which takes a lot of time to compute the output. So I am trying to use stubs to mimic all the values that B returns.
def A
#do something
output = B
#do something with output
end
Now the test files
describe "check what A returns" do
ClassName.stub(:B) do
[0, 1]
end
test_values = TestClass.A(input parameters)
#checks on test values
end
My aim is to pass the expected output of B to function A. I am using RSpec. How do I go about it?
With RSpec you can do:
allow(ClassName).to receive(:B).and_return([1,2,3,4,5])
After this you can call B function and it will return [1,2,3,4,5]
You can find more info at RSpec documentation: https://relishapp.com/rspec/rspec-mocks/v/3-4/docs/configuring-responses/returning-a-value
I've attempted to write some classes and test cases for what it seems like you want to test. The key here is to use allow to stub out the return value for a method.
Just note here that I've changed the methods in your class to be class methods to fit what seems to be your test case, but you can obviously change them back to instance methods to fit your purpose. Also, accepted Ruby style is to have lowercase method names.
class ClassName
def self.B
# returns something that we're going to stub out
end
end
class TestClass
def self.A
# do something
output = ClassName.B
# do something with output
# eg in this case, add a value to it
output << 2
end
end
describe TestClass do
describe '.A' do
before do
allow(ClassName).to receive(:B).and_return([0, 1])
end
it 'does something with output' do
expect(described_class.A).to eq([0, 1, 2])
end
end
end
There's ways as mentioned in other posts but I'll give you another: you might want to make that dependency explicit.
Here's how it could look like:
# test_class.rb
class TestClass
# The default will be automatically setup to be an object of type ClassName
def initialize(some_collaborator: ClassName.new)
self.some_collaborator = some_collaborator # Some people will probably also insert some guard (to make sure it responds to `b`
end
def a
# your code calling `some_collaborator.b`
end
private
attr_accessor :some_collaborator
end
# test_class_spec.rb
describe TestClass do
let(:stub_b) { stub("Some instance of ClassName", b: [...] }
subject { TestClass.new(some_collaborator: stub_b) }
it "whatever" do
expect(subject.a).to ...
end
end
The default collaborator should be a sensible default (and if you can't instantiate it there's ways to encapsulate it anyways). Not only it will be easier to read, but it will be easier to maintain.
I've been writing a DSL, and I'm trying to get a dynamically defined method to be accessible from a lambda. This works fine unless you try to do a setter something= in which case the lambda invocation just sets a local variable instead.
A simplified example:
class Caller
attr_accessor :cmd
def callme
self.class.send(:define_method, "something") { puts "Retrieve Something" }
self.class.send(:define_method, "something=") {|val| puts "Set Something = #{val}" }
instance_exec &cmd
end
end
c = Caller.new
c.cmd = lambda { something = 1 }
c.callme
This also works fine if I use self.something=. However that's less than ideal in the case of a DSL.
Is it possible to get this to work without self. in front of the method?
This has absolutely nothing to do with blocks or dynamically defined methods. It's just simple basic Ruby syntax:
foo = bar
is local variable assignment. Always.
self.foo = bar
is a method call.
Is it possible to get this to work without self. in front of the method?
No.
This is just basic Ruby syntax. The define_method metaprogramming, the instance_exec, the blocks in your code sample are just a red herring, the problem can be demonstrated with a much simpler example:
def foo=(*)
puts 'I was called!'
end
foo=('bar') # even removing spaces and adding parentheses won't help!
self.foo = 'bar'
# I was called!
Note also that foo= is private but was actually called with an explicit receiver (which is illegal for private methods). That's a special exception for setter methods in the rule for private methods, because they otherwise couldn't be called at all, precisely because they would always be interpreted as a local variable assignment.
I know that I can capture the moment of a method definition by using set_trace_func.
set_trace_func ->event, file, line, method, binding, klass{
if event == "c-call" and method == :method_added
# The moment of method definition
end
}
Is it possible to capture the name of the method being defined at such moment? I know that the class can be captured by eval("self", binding). What code can I put inside the block shown above to capture the method name?
Is it further possible to get the format of the arguments for the method being defined (the required arguments, the rest of the arguments, and their names as is in the source)?
Outside of set_trace_func, you could use Module.method_added:
class Test
def self.method_added(method_name)
puts "#{method_name} added to #{self}"
end
def foo
"foo"
end
end
$ ruby test.rb
# => foo added to Test
Check the documentation.
The Kernel.set_trace_func proc allows you catch an id parameter. This—most times—is the function name.
However, learning from your example, you can also get the current running method using eval("__method__", binding) …but I think this only gets the methods you have defined in your classes.
I have seen here and there code like this one
Specification.new do |s|
s.name = %q{casein}
s.version = "3.1.11"
....
Can someone explain me what is the idea behind "do |s|" ?
This is an example of using blocks in ruby. Block is a chunk of code that you can pass to a method (new is just a regular method).
class Foo
def initialize
puts "doing some work"
yield('Foo') if block_given?
end
end
Foo.new do |s|
puts "output from #{s}"
end
# >> doing some work
# >> output from Foo
Here initializer in Foo calles a block (if one is given) and passes it a parameters. Block then can receive that parameter and do its work.
Educational reading here.
it's an argument that gets exposed to block by initializer of Specification
things probably look like this in that class:
class Specification
def initialize
# ...
yield self
# ...
end
end
that yield executes a block and passes a single argument to it - self (it might be any other object so better go check the source)
code block usually looks like this:
do |arguments, list|
# code here
end
but arguments are optional(unless it's lambda and you will get exception for too few or too many)
I understand that method_missing is something of a last resort when Ruby is processing messages. My understanding is that it goes up the Object hierarchy looking for a declared method matching the symbol, then back down looking for the lowest declared method_missing. This is much slower than a standard method call.
Is it possible to intercept sent messages before this point? I tried overriding send, and this works when the call to send is explicit, but not when it is implicit.
Not that I know of.
The most performant bet is usually to use method_missing to dynamically add the method being to a called to the class so that the overhead is only ever incurred once. From then on it calls the method like any other method.
Such as:
class Foo
def method_missing(name, str)
# log something out when we call method_missing so we know it only happens once
puts "Defining method named: #{name}"
# Define the new instance method
self.class.class_eval <<-CODE
def #{name}(arg1)
puts 'you passed in: ' + arg1.to_s
end
CODE
# Run the instance method we just created to return the value on this first run
send name, str
end
end
# See if it works
f = Foo.new
f.echo_string 'wtf'
f.echo_string 'hello'
f.echo_string 'yay!'
Which spits out this when run:
Defining method named: echo_string
you passed in: wtf
you passed in: hello
you passed in: yay!