question about parameter passing in Ruby - ruby

Comparing the following two code snippets:
class Logger
def self.add_logging(id_string)
define_method(:log) do |msg|
now = Time.now.strftime("%H:%M:%S")
STDERR.puts "#{now}-#{id_string}: #{self} (#{msg})"
end
end
end
class Song < Logger
add_logging "Tune"
end
song = Song.new
song.log("rock on")
class Logger
def self.add_logging(id_string)
def log(msg)
now = Time.now.strftime("%m")
puts "#{now}-#{id_string}: #{self}(#{msg})"
end
end
end
class Song < Logger
add_logging "Tune"
end
s = Song.new
s.log("can't smile with you")
#=> NameError: undefined local variable or method `id_string' for #<Song:0x000001018aad70>
I can't figure out why the second case gets the NameError error, and why the id_string can't be passed to it.

A def creates a new scope; a block does not. A new scope cuts off the visibility of the surrounding variables. ruby has two other 'new scope creators': class and module.
x = 10
3.times do |i|
puts i * x
end
def do_stuff
puts x * 10
end
do_stuff
--output:--
0
10
20
`do_stuff': undefined local variable or method `x'

id_string is local to method add_logging. In your latter implementation, log-method can not see it, hence the error. In the former implementation, you dynamically define log-method within add_logging.
In other words, local variable is visible within the scope it is defined in (in this case, a method). In that latter implementation, you have nested scopes (=a method declaration within a method), and inner scope can not access variables that are local to outer scope.
As suggested in answer by #stef, you might get around this my widening the scope of the variable. I would recommend keeping variable scopes as 'tight' as possible, and therefore prefer your first implementation.

Try this with a class variable?
class Logger
def self.add_logging(id_string)
##my_id = id_string
define_method(:log) do |msg|
now = Time.now.strftime("%H:%M:%S")
STDERR.puts "#{now}-#{##my_id}: #{self} (#{msg})"
end
end
end

Class variables should be avoided in ruby due to their problematic nature. The ruby way is to use 'class instance variables' instead.

Related

Ruby - How to access instance variables from classes with "self" methods?

Sorry that I have no clue how to title this, I'm having a hard time looking this up because I don't know how to say this. Anyway...
Let's say I have a class that looks like this for example:
class Run
def self.starting
print "starting..."
end
def self.finished
print "Finished!"
end
end
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting. Now let's say that I wanted to add some instance variables...
class Run
attr_accessor :starting, :finished
def self.starting
print "starting..."
#starting = true
#finished = false
end
def self.finished
print "finished!"
#starting = false
#finished = true
end
end
What if I wanted to access those instance variables from outside the class? I know that something like print "#{Run.finished}" or print "#{Run.starting}" won't do anything. Can I do that without run = Run.new? Or should I just remove self and then use run = Run.new? (Sorry if this question is a mess.)
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting
There's much more to it than this. In your case you're calling class methods. If you did runner = Runner.new - then you'd be calling instance methods (those are defined without self.
In general, if you need "the thing" to hold some kind of state (like #running = true) then you'd rather want to instantiate an object, and call those methods.
Now, #whatever are instance variables, and you don't have the access to them in class methods.
class Run
attr_reader :running
def start
#running = true
end
def stop
#running = false
end
end
runner = Run.new
runner.running # nil
runner.start
runner.running # true
runner.stop
runner.running # false
I'd recommend you doing some tutorial or basic level book on rails programming, find a chapter about objects and classes. Do some exercises.
In Ruby instance variables are just lexical variables scoped to an instance of a class. Since they are scoped to the instance they always act like a private variable.
If you want to provide access to an instance variable from the outside you create setter and getter methods. Thats what attr_accessor does.
class Person
attr_accessor :name
def initialize(name:)
#name = name
end
def hello
"Hello my name is #{#name}"
end
end
john = Person.new(name: 'John')
john.name = "John Smith"
puts john.hello # "Hello my name is John Smith"
puts john.name # "John Smith"
Methods defined with def self.foo are class methods which are also referred to as singleton methods. You can't access variables belonging to an instance from inside a class method since the recipient when calling the method is the class itself and not an instance of the class.
Ruby also has class variables which are shared by a class and its subclasses:
class Person
##count = 0
def initialize
self.class.count += 1
end
def self.count
##count
end
def self.count=(value)
##count = value
end
end
class Student < Person
end
Person.new
Student.new
puts Person.count # 2 - wtf!
And class instance variables that are not shared with subclasses:
class Person
#count = 0 # sets an instance variable in the eigenclass
def initialize
self.class.count += 1
end
def self.count
#count
end
def self.count=(value)
#count = value
end
end
class Student < Person
#count = 0 # sets its own class instance variable
end
Person.new
Student.new
puts Person.count # 1
Class variables are not used as often and usually hold references to things like database connections or configuration which is shared by all instances of a class.
You can't access instance variables from outside the instance. That is the whole point of instance variables.
The only thing you can access from outside the instance are (public) methods.
However, you can create a public method that returns the instance variable. Such a method is called an attribute reader in Ruby, other languages may call it a getter. In Ruby, an attribute reader is typically named the same as the instance variable, but in your case that is not possible since there are already methods with the names starting and finished. Therefore, we have to find some other names for the attribute readers:
class Run
def self.starting?
#starting
end
def self.finished?
#finished
end
end
Since this is a common operation, there are helper methods which generate those methods for you, for example Module#attr_reader. However, they also assume that the name of the attribute reader method is the same as the name of the instance variable, so if you were to use this helper method, it would overwrite the methods you have already written!
class << Run
attr_reader :starting, :finished
end
When you do this, you will get warnings (you always have warning turned on when developing, do you?) telling you that you have overwritten your existing methods:
run.rb:19: warning: method redefined; discarding old starting
run.rb:2: warning: previous definition of starting was here
run.rb:19: warning: method redefined; discarding old finished
run.rb:5: warning: previous definition of finished was here

Ruby variable from outer scope undefined within a block - why?

This part of code dynamically creates several classes:
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB) do
def initialize(trg)
super(trg, act_id)
end
end
Object.const_set("Act#{act_id}", klass)
end
In this case, the common base class (ActB) has a constructor with two parameters, while the child classes have a constructor with one parameter.
Running this code works well, but when I later try to instantiate one of these classes, for example
Act3.new(4)
I get the error message
NameError: undefined local variable or method `act_id' for #<Act3:0x00000006008b7990>
The error message must refer to the line
super(trg, act_id)
because this is the only place in my program where I am using this variable. However, this variable is defined a few lines above, when it says
(1..MAX_ACT).each do |act_id|
I had expected, that the do...end block creates a closure for the constructor, where act_id is bound. However, this doesn't seem to be the case.
Why does my example not work? How do I have to do it correctly?
def (and class and module) creates a fresh local scope, which doesn't inherit any locals from outside.
So you're right that the Class.new do .. end creates a closure... but the inner def doesn't share it.
If you need standard block behaviour, you can use define_method instead:
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB) do
define_method :initialize do |trg|
super(trg, act_id)
end
end
Object.const_set("Act#{act_id}", klass)
end
Just out of curiosity, there is a hack, allowing to fool scoping and still use def initialize :)
class ActB
def initialize(trg, act_id)
puts "ActID: #{act_id}"
end
end
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB) do
#act_id = act_id
def initialize(trg)
super(trg, self.class.instance_variable_get(:#act_id))
end
end
Object.const_set("Act#{act_id}", klass)
end
Act1.new :foo
#⇒ ActID: 1
Act2.new :foo
#⇒ ActID: 2
The problem here is that the block passed to Class.new is executed in the context of that class. In the context of that class, act_id is not defined. So, to fix this, you can move the method definition outside of the class initialization, like so:
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB)
klass.define_method(:initialize) do |trg|
super(trg, act_id)
end
Object.const_set("Act#{act_id}", klass)
end

Stack level too deep in Ruby

class MyClass
def method_missing(name, *args)
name = name.to_s
10.times do
number = rand(100)
end
puts "#{number} and #{name}"
end
end
Hello, I am exercising ruby but in this nonrecursive function i am getting stack level too deep error when use this piece of code.
x = MyClass.New
x.try
The problem with your code is the number variable defined inside times() falls out of method_missing() scope. Thus, when that line is executed Ruby interprets it as a method call on self.
In normal cases you should be getting NoMethodError exception. However, since you have overriden method_missing() method for MyClass, you do not get this exception. Instead until the stack overflows method number() is called.
To avoid such problems,try to specify the method names that are allowed. For instance, lets say you only need try, test, and my_method methods to be called on MyClass, then specify these method names on method_missing() to avoid such problems.
As an example :
class MyClass
def method_missing(name, *args)
name = name.to_s
super unless ['try', 'test', 'my_method'].include? name
number = 0
10.times do
number = rand(100)
end
puts "#{number} and #{name}"
end
end
If you do not really need method_missing(), avoid using it. There are some good alternatives here.

Defining a method that uses an out-of-scope variable in Ruby

I want to make a Test::Unit test_helper method that I can call to wipe a bunch of tables after the tests execute. Here's the general idea I have:
def self.wipe_models(*models)
def teardown
models.each do |model|
model = model.to_s.camelize.constantize
model.connection.execute "delete from #{model.table_name}"
end
end
end
However, when teardown runs, I get:
undefined local variable or method `models'
To me it looks like the "def" block doesn't obey usual rules for closures; I can't access variables defined outside of its scope.
So, how do I access a variable that's defined outside of a "def" method declaration?
You can do it as a closure with define_method:
def self.wipe_models(*models)
define_method(:teardown) do
models.each do |model|
model = model.to_s.camelize.constantize
model.connection.execute "delete from #{model.table_name}"
end
end
end
Now the method body is a block and can access models.
Method definitions are not closures in Ruby. The class, module, def, and end keywords are all scope gates. In order to maintain scope across a scope gate you have to pass a block; blocks are closures and thus run in the scope in which they were defined.
def foo
# since we're in a different scope than the one the block is defined in,
# setting x here will not affect the result of the yield
x = 900
puts yield #=> outputs "16"
end
# x and the block passed to Proc.new have the same scope
x = 4
square_x = Proc.new { x * x }
foo(&square_x)
Use a class instance variable:
cattr_accessor :models_to_wipe
def self.wipe_models(*models)
self.models_to_wipe = models
end
def teardown
self.class.models_to_wipe.each do |model|
model = model.to_s.camelize.constantize
model.connection.execute "delete from #{model.table_name}"
end
end

Explicitly specify default method target in Ruby?

Is there a best practice in Ruby when it comes to explicitly specifying method targets even when unnecessary?
class Foo
def meth1
puts "bar"
end
def meth2
# is this better?
self.meth1
# or this?
meth1
end
end
No, it's just a question of style.
The only thing to keep in mind is that you always have to specify a target for setter methods.
foo_count = 4 #creates a local variable named foo_count and sets it to 4
self.foo_count = 4 #sends foo_count=(4) to self
The same rule applies if you happen to have a local variable with the same name as a method of your class, though I would say that is a bad practice in itself.
As Chuck said earlier, it is mostly a matter of style with the exception he pointed out, and when using private methods. Any time you use a private method from within an object, you must leave off the self. business.
Eg:
class Tze
def meth2
meth1
end
def meth3
self.meth1
end
private
def meth1
puts "method 1 invoked"
end
end
Calling Tze.new.meth2 produces the expected output; however, calling Tze.new.meth3 raises an error because of meth1 is being invoked as self.meth1.

Resources