While trying to brush up my Ruby skills I keep running across this case which I can't figure out an explanation for by just reading the API docs. An explanation would be greatly appreciated. Here's the example code:
for name in [ :new, :create, :destroy ]
define_method("test_#{name}") do
puts name
end
end
What I want/expect to happen is that the name variable will be bound to the block given to define_method and that when #test_new is called it will output "new". Instead each defined method outputs "destroy" -- the last value assigned to the name variable. What am I misunderstanding about define_method and its blocks? Thanks!
Blocks in Ruby are closures: the block you pass to define_method captures the variable name itself–not its value—so that it remains in scope whenever that block is called. That's the first piece of the puzzle.
The second piece is that the method defined by define_method is the block itself. Basically, it converts a Proc object (the block passed to it) into a Method object, and binds it to the receiver.
So what you end up with is a method that has captured (is closed over) the variable name, which by the time your loop completes is set to :destroy.
Addition: The for ... in construction actually creates a new local variable, which the corresponding [ ... ].each {|name| ... } construction would not do. That is, your for ... in loop is equivalent to the following (in Ruby 1.8 anyway):
name = nil
[ :new, :create, :destroy ].each do |name|
define_method("test_#{name}") do
puts name
end
end
name # => :destroy
for name in [ :new, :create, :destroy ]
local_name = name
define_method("test_#{local_name}") do
puts local_name
end
end
This method will behave as you expect. The reason for the confusion is that 'name' is not created once per iteration of the for loop. It is created once, and incremented. In addition, if I understand correctly, method definitions are not closures like other blocks. They retain variable visibility, but do not close over the current value of the variables.
The problem here is that for loop expressions do not create a new scope. The only things that create new scopes in Ruby are script bodies, module bodies, class bodies, method bodies and blocks.
If you actually look up the behavior of for loop expressions in the Draft ISO Ruby Specification, you will find that a for loop expression gets executed exactly like an each iterator except for the fact that it does not create a new scope.
No Rubyist would ever use a for loop, anyway: they would use an iterator instead, which does take a block and thus creates a new scope.
If you use an idiomatic iterator, everything works as expected:
class Object
%w[new create destroy].each do |name|
define_method "test_#{name}" do
puts name
end
end
end
require 'test/unit'
require 'stringio'
class TestDynamicMethods < Test::Unit::TestCase
def setup; #old_stdout, $> = $>, (#fake_logdest = StringIO.new) end
def teardown; $> = #old_stdout end
def test_that_the_test_create_method_prints_create
Object.new.test_create
assert_equal "create\n", #fake_logdest.string
end
def test_that_the_test_destroy_method_prints_destroy
Object.new.test_destroy
assert_equal "destroy\n", #fake_logdest.string
end
def test_that_the_test_new_method_prints_new
Object.new.test_new
assert_equal "new\n", #fake_logdest.string
end
end
Related
I'm learning ruby, and noticed that I cannot create a class method called puts:
class Printer
def initialize(text="")
#text = text
end
def puts
puts #text
end
end
The error is:
`puts': wrong number of arguments (given 1, expected 0)
My expectation was that I could use the code like this:
p = Printer.new("hello")
p.puts
It's not just because puts is a built-in method, though. For instance, this code also gives a syntax error:
def my_puts(text)
puts text
end
class Printer
def initialize(text="")
#text = text
end
def my_puts
my_puts #name
end
end
tldr; within the scope of the instance, the puts resolves to self.puts (which then resolves to the locally defined method, and not Kernel#puts). This method overriding is a form of shadowing.
Ruby has an 'implicit self' which is the basis for this behavior and is also how the bare puts is resolved - it comes from Kernel, which is mixed into every object.
The Kernel module is included by class Object, so its methods [like Kernel#puts] are available in every Ruby object. These methods are called without a receiver and thus can be called in functional form [such as puts, except when they are overridden].
To call the original same-named method here, the super keyword can be used. However, this doesn't work in the case where X#another_method calls X#puts with arguments when it expects to be calling Kernel#puts. To address that case, see Calling method in parent class from subclass methods in Ruby (either use an alias or instance_method on the appropriate type).
class X
def puts
super "hello!"
end
end
X.new.puts
P.S. The second example should trivially fail, as my_puts clearly does not take any parameters, without any confusion of there being another "puts". Also, it's not a syntax error as it occurs at run-time after any language parsing.
To add to the previous answer (https://stackoverflow.com/a/62268877/13708583), one way to solve this is to create an alias of the original puts which you use in your new puts method.
class Printer
alias_method :original_puts, :puts
attr_reader :text
def initialize(text="")
#text = text
end
def puts
original_puts text
end
end
Printer.new("Hello World").puts
You might be confused from other (static) programming languages in which you can overwrite a method by creating different signatures.
For instance, this will only create one puts method in Ruby (in Java you would have two puts methods (disclaimer: not a Java expert).
def puts(value)
end
def puts
end
If you want to have another method with the same name but accepting different parameters, you need to use optional method parameters like this:
def value(value = "default value")
end
Below is a simple rspec example:
describe 'Emails' do
email_ids.each do |email_id|
it "should display #{email_id}" do
end
end
end
def email_ids
[
'test1#example.com',
'test2#example.com',
'test3#example.com'
]
end
The above does not work, as methods are not accessible outside the it block.
Please advise how to make the method email_ids accessible outside the it block.
describe creates a (nested) class and evaluates the given block within that class:
describe 'Foo' do
p self #=> RSpec::ExampleGroups::Foo
describe '' do
p self #=> RSpec::ExampleGroups::Foo::Bar
end
end
it blocks on the other hand are evaluated in the corresponding class' instance:
describe 'Foo' do
it 'foo' do
p self #=> #<RSpec::ExampleGroups::Foo ...>
end
end
If you define a method via def email_ids, it becomes an instance method and is therefore only available within the instance, i.e. within it.
In order to make a method available within describe, you have to define it as a class method, i.e via def self.email_ids:
describe 'Emails' do
def self.email_ids
%w[test1#example.com test2#example.com test3#example.com]
end
email_ids.each do |email_id|
it "should display #{email_id}" do
end
end
end
Output:
Emails
should display test1#example.com
should display test2#example.com
should display test3#example.com
You can also reuse the helper method across multiple tests by putting it in a module and using extend. See Define helper methods in a module for more examples.
I have better solution for this, than above.
1.using 'procs' or just local variable as below:
email_ids = ->{ %w[test1#example.com test2#example.com test3#example.com] }
email_ids = { %w[test1#example.com test2#example.com test3#example.com] }
Scope of proc & local variable will be same, but if you want to pass an argument then 'procs' are useful.
2.Define 'email_ids' method in module and include that module in spec, so that method will be accessible inside and outside the 'it' block
module EmailFactoryHelper
def email_ids
%w[test1#example.com test2#example.com test3#example.com]
end
end
include in specs as below:
require 'factories_helper/email_factory_helper'
include EmailFactoryHelper
describe 'Emails' do
email_ids.call.each do |email_id|
it "should display #{email_id}" do
page.should have_content "#{email_id}"
end
end
end
Output:
Emails
should display test1#example.com
should display test2#example.com
should display test3#example.com
Finished in 41.56 seconds
3 examples, 0 failures
I have preferred step-2
Rather than using proc or scopes,
Simply use local variables outside describe block.
email_ids = [
'test1#example.com',
'test2#example.com',
'test3#example.com'
]
describe 'Emails' do
end
The solution is to simply define your structure within scope, instead of returning it from a method call:
EMAILS = [
'test1#example.com',
'test2#example.com',
'test3#example.com'
]
EMAILS.each do |email|
it "should display #{email}" do
end
end
The method wasn't accessible because you called the method before you defined the method. This simpler script has the same problem:
p email_ids
def email_ids
[
'test1#example.com',
'test2#example.com',
'test3#example.com'
]
end
"undefined local variable or method `email_ids' for main:Object (NameError)"
You must define your methods before you call them. You can solve this problem by moving the def email_ids above the describe 'Emails'.
short version of #stefan's answer:
needs to be
def self.email_ids
# stuff
end
(def self.method for context/describe/etc; def method for it/etc)
I am trying to create a macro "has_accessor_for", that accepts a symbol which is used as a parameter for an internal object that it uses (the Accessorizer object). The problem I am having is, when multiple modules do the has_accessors_for, the parameter (scope) ends up being stuck on the last value it was assigned to.
I added a puts prior to the define_method, which shows that it's scope1, and then scope2... But inside the define_method, it's scope2 always. I am looking for a way to basically encapsulate that variable, so that when it the first module calls has_accessor_for, anytime my_wut is called, it will be bound to scope1... and anytime my_bleah is called, it will be bound to scope2. But as I said, right now, both my_bleah and my_wut are bound to scope2-- If I change the order of the includes in MyModel, then they will both be bound to scope1.
class Accessorizer
def initialize(record, scope)
#record = record
#scope = scope
end
def value_for(key)
#record.send key
end
end
module Magic
def has_accessors_for(scope)
accessors = {}
puts "initial: #{scope}"
define_method :get_value_for do |key|
puts "inside method #{scope}"
accessor.value_for key
end
define_method :accessor do
accessors[:scope] ||= Accessorizer.new(self, scope)
end
end
end
module SomeAccessor
extend Magic
has_accessors_for :scope1
def my_wut
get_value_for :wut
end
end
module SomeOtherAccessor
extend Magic
has_accessors_for :scope2
def my_bleah
get_value_for :bleah
end
end
class MyModel
include SomeAccessor
include SomeOtherAccessor
attr_accessor :wut, :bleah
end
m = MyModel.new
m.wut = 'wut'
m.bleah = 'bleah'
m.my_bleah
m.my_wut
output:
initial: scope1
initial: scope2
inside method scope2
inside method scope2
Short answer: the problem is not with the closures.
Long answer:
define_method :get_value_for do |key|
puts "inside method #{scope}"
accessor.value_for key
end
On a given class there can only be one method called get_value_for - the second definition will overwrite the first.
It doesn't matter so much because you're call accessor in both cases, however that method suffers from the same problem - you define it twice and so the second definition overwrites the first and you end up with only one Accessorizer object.
I think you'll need to rethink your design here.
Inside the body of a class, I'd like to pass a block to a method called with. For the lifetime of the block, I would like a with_value method to be available.
Otherwise, everything inside the block should behave as if it were outside the block.
Here's an example:
class C
extend M
with "some value" do
do_something_complicated
do_something_complicated
do_something_complicated
end
end
We can almost get this with:
module M
def with(str, &block)
Object.new.tap do |wrapper|
wrapper.define_singleton_method :with_value do # Here's our with_value
str # method.
end
end.instance_eval &block
end
def do_something_complicated # Push a value onto an
(#foo ||= []).push with_value # array.
end
end
but there's a problem: since we're evaluating the block passed to with inside the context of a different object, do_something_complicated isn't available.
What's the right way to pull this off?
This will make with_value available only within the block. However, _with_value will be defined within or outside of the block.
module M
def _with_value
...
end
def with(str, &block)
alias with_value _with_value
block.call
undef with_value
end
...
end
I cannot tell from the question whether this is a problem. If it is a problem, you need to further describe what you are trying to do.
Basically, the idea is to use method_missing to forward method calls from the dummy class to the calling class. If you also need to access instance variables, you can copy them from the calling class to your dummy class, and then back again after the block returns.
The Ruby gem docile is a very simple implementation of such a system. I suggest you read the source code in that repository (don't worry, it's a very small codebase) for a good example of how DSL methods like the one in your example work.
Here is a way that is closer to your attempt:
module M
def with(str, &block)
dup.tap do |wrapper|
wrapper.define_singleton_method :with_value do
...
end
end.instance_eval &block
end
...
end
dup will duplicate the class from where with is called as a class method.
I'm a newb working through some Ruby tutorials and am stumped on the use of the send method below. I can see the send method is reading the value of the attribute iterator over, but the Ruby documentation states the send method takes a method prepended with a colon. So, my confusion lies in how the send method below is interpolating the attribute variable being iterated over.
module FormatAttributes
def formats(*attributes)
#format_attribute = attributes
end
def format_attributes
#format_attributes
end
end
module Formatter
def display
self.class.format_attributes.each do |attribute|
puts "[#{attribute.to_s.upcase}] #{send(attribute)}"
end
end
end
class Resume
extend FormatAttributes
include Formatter
attr_accessor :name, :phone_number, :email, :experience
formats :name, :phone_number, :email, :experience
end
It's not "invoking the value of the iterator", but instead calling a method with that name. In this case because of the attr_accessor declaration, these methods map to properties.
Calling object.send('method_name') or object.send(:method_name) are equivalent to object.method_name in general terms. Likewise, send(:foo) and foo will call the method foo on the context.
Since the module declare a method that is later mixed in with an include, calling send in the module has the effect of calling a method on an instance of the Resume class.
send Documentation
here is simplified version of your code,to feed you what's going on:
def show
p "hi"
end
x = "show"
y = :show
"#{send(y)}" #=> "hi"
"#{send(x)}" #=> "hi"