I've been playing around with Ruby mostly in the top level and I typically write code like:
#x = 'foo'
def show_var
puts #x
end
show_var # => foo
I thought that instance variables were available to both the Class and the Object, based off how this example works.
Today I ran into this, and it looks like my understanding of instance variables is incorrect:
class Test
#x = "foo" #you would need to define this inside 'initialize' for this to be available to show_var
def show_var
puts #x
end
end
Test.new.show_var # => nil
It looks like the second example is how instance variables work. If you define an instance variable inside the Class, then it only exists inside that scope, and does not exist for instance methods.
Then my question is... why does the first case output 'foo' when the variable #x shouldn't exist inside the scope of an instance method? Also, what is the proper way for defining variables in the top-level Class that you want to use for your top-level methods?
The method in the first example is at the top level. Which means it belongs to a special top level object main. You can't really create more copies of main, so self inside and outside of that method is the same. Check this out:
self # => main
def show_var
self # => main
end
class Test
self # => Test
def show_var
self # => #<Test:0x007fdf9c892d70>
end
end
Look below:
#x = 'foo'
def show_var
puts #x,self
p defined? #x
end
show_var
# >> foo
# >> main
# >> "instance-variable"
In the first case you defined #x in the main scope. And then when call the method show_var,from the main,thus you got the output of #x,which is not the case of the other.
class Test
#x = "foo"
def show_var
p #x,self
p defined? #x
end
end
Test.new.show_var
# >> nil
# >> #<Test:0x9b6fcd4>
# >> nil
Related
I'm learning ruby and my opinion about attr_reader is bad. Because you can change the value of particular instance variable from outside of class. For example:
class X
attr_reader :some_string
def initialize
#some_string = "abc"
end
end
tmp = X.new
puts tmp.some_string
tmp.some_string[0] = "aaaaaaaaa"
puts tmp.some_string
When you run this code you can see that the instance variable some_string has changed. So to avoid this I'm always making my own getter, and returning frozen object. For example:
class X
def initialize
#some_string = "abc"
end
def some_string
#some_string.freeze
end
end
tmp = X.new
puts tmp.some_string
tmp.some_string[0] = "aaaaaaaaa"
puts tmp.some_string
Now when you run the code, it throws an error saying can't modify frozen String: "abc", which is what I wanted. So my question is should I use attr_reader and is always returning frozen objects from getters bad practice?
attr_reader is not "bad", it simply does what it does, which is to return the value of the instance variable, which is a reference. Unlike attr_writer or attr_accessor it will not allow you to change the value of the instance variable (ie you can't change what the instance variable refers to)
The question is do you really want or need the reference. For example say you want to convert the value of some_string to upper case. You could use attr_reader to get the reference to some_string and then call upcase! on it like below. But having the reference allows you to call any method on the object including the []= method which maybe you don't want to allow. Note: []= is a method that manipulates the content of what some_string references it does not set #some_string to a new value, it still points to the same object, but the object it points to was manipulated.
class Foo
attr_reader :some_string
def initialize()
#some_string = "abc"
end
end
puts "Foo"
foo = Foo.new
some_string = foo.some_string
puts some_string #=> abc
some_string.upcase!
p foo # => #<Foo:0x0000563c38391ac8 #some_string="ABC">
puts some_string.object_id # => 60
some_string[0] = "x"
p foo # => #<Foo:0x0000563c38391ac8 #some_string="xBC">
puts some_string.object_id # => 60 ... ie same object different content
# foo.some_string = "ABC" is a runtime error
If you don't want to allow arbitrary methods to be called on an instance variable then you should not expose it using attr_reader, rather you should manipulate the instance variable via methods in your class. For example below I "delegate" the upcase! method to the instance variable #some_string, and I provide a string_value method to return a new string with the same value as the instance variable.
class Bar
def initialize()
#some_string = "abc"
end
def upcase!()
#some_string.upcase!
end
def string_value()
"#{#some_string}"
end
end
puts "Bar"
bar = Bar.new
p bar # => #<Bar:0x0000563c383915a0 #some_string="abc">
bar.upcase!
p bar # => #<Bar:0x0000563c383915a0 #some_string="ABC">
some_string = bar.string_value
p some_string # => "ABC"
some_string[0] = "x"
p bar # => #<Bar:0x0000563c383915a0 #some_string="ABC">
p some_string # => "xBC"
So I would say attr_reader is not bad, you might argue it is over used, I know I will often use it to "get" an instance variable when all I really need is some property on the instance variable.
A lot of developers try to use private attr_reader and use it inside the class or avoid using it at all
A good conversations about attr_reader are here and here
I'm trying to do something like this in Ruby:
class A
def b(c)
Class.new do
def d
c
end
end
end
end
class B < A.b('whoa'); end
#
# What I want:
#
B.new.d # => 'whoa'
#
# What I get:
#
# NameError: undefined local variable or method `d' for B:0xfdfyadayada
Is there any way to do this?
The context is I'm trying to get some code reuse by constructing classes that are mostly similar except for some minor config.
I'm looking for an API similar to Sequel::Model where you can do something like:
class Order < Sequel::Model(:order)
The contents of a method definition using the def keyword are not lexically scoped the way blocks are. In other words you can’t do something like this:
foo = 7
def bar
# Code here inside the method definition can't
# "see" foo, so this doesn't work.
foo
end
However you can use the define_method method to dynamically define a method using a block, and the block will be able to refer to local variables in its outer scope. So in your case you could do this (I’ve also changed def b to def self.b, which I think is what you meant):
class A
def self.b(c)
Class.new do
define_method(:d) do # N.B. the d here is a symbol
c
end
end
end
end
class B < A.b('whoa'); end
B.new.d # => 'whoa'
This would also work if you created other classes:
class C < A.b('dude'); end
C.new.d # => 'dude'
# B still works
B.new.d # => 'whoa'
Assuming that b is a class method (i.e def self.b instead of def b), you could store c in a class variable.
class A
def self.b(c)
Class.new do
##c = c
def d
##c
end
end
end
end
class B < A.b('whoa'); end
puts B.new.d # => whoa
There could be a better solution, I haven't looked at the source code of Sequel. This is just the first thing that came to my mind.
My understanding of instance_eval was that if I have module M then the following were equivalent:
module M
def foo
:foo
end
end
class C
class << self
include M
end
end
puts C.foo
equivalent to:
module M
def foo
:foo
end
end
class C
end
C.instance_eval do
include M
end
puts C.foo
However, the first example prints :foo and the second throws a NoMethodError? (Ruby 2.3.0)
In both cases above, if I had replaced:
include M
with:
def foo
:foo
end
ie directly defining the method rather than including a module then both cases would have resulted in a C.foo method being defined. Should I be surprised at this difference between include and defining the method directly?
Or does it ever even make sense to call include within the context of instance_eval? Should it only ever be called within a class_eval?
In each of these cases, what object are you calling include on? In your first example, you're calling include on C's singleton class:
class C
class << self
p self == C.singleton_class
include M
end
end
# => true
p C.foo
# => :foo
...so your include line is equivalent to C.singleton_class.include(M).
In your second example, however, you're calling include on C itself:
class C
end
C.instance_eval do
p self == C
include M
end
# => true
p C.foo
# => NoMethodError: undefined method `foo' for C:Class
p C.new.foo
# => :foo
...so you're doing the equivalent of C.include(M), which is the same as:
class C
p self == C
include M
end
# => true
p C.new.foo
# => :foo
What would work like you want would be to call instance_eval on C's singleton class:
class D
end
D.singleton_class.instance_eval do
p self == D.singleton_class
include M
end
# => true
p D.foo
# => :foo
Module#class_eval() is very different from Object#instance_eval(). The instance_eval() only changes self, while class_eval() changes both self and the current class.
Unlike in your example, you can alter class_instance vars using instance_eval though, because they are in the object scope as MyClass is a singleton instance of class Class.
class MyClass
#class_instance_var = 100
##class_var = 100
def self.disp
#class_instance_var
end
def self.class_var
##class_var
end
def some_inst_method
12
end
end
MyClass.instance_eval do
#class_instance_var = 500
def self.cls_method
##class_var = 200
'Class method added'
end
def inst_method
:inst
end
end
MyClass.disp
#=> 500
MyClass.cls_method
#=> 'Class method added'
MyClass.class_var
#=> 100
MyClass.new.inst_method
# undefined method `inst_method' for #<MyClass:0x0055d8e4baf320>
In simple language.
If you have a look in the upper class defn code as an interpreter, you notice that there are two scopes class scope and object scope. class vars and instance methods are accessible from object scope and does not fall under jurisdiction of instance_eval() so it skips such codes.
Why? because, as the name suggests, its supposed to alter the Class's instance(MyClass)'s properties not other object's properties like MyClass's any object's properties. Also, class variables don’t really belong to classes—they belong to class hierarchies.
If you want to open an object that is not a class, then you can
safely use instance_eval(). But, if you want to open a class definition and define methods with def or include some module, then class_eval() should be your pick.
By changing the current class, class_eval() effectively reopens the class, just like the class keyword does. And, this is what you are trying to achieve in this question.
MyClass.class_eval do
def inst_method
:inst
end
end
MyClass.new.inst_method
#=> :inst
When you have a class with a attr_accessor, can't you omit the # symbol when calling your instance variables since self.instance_variable would be implied if you don't use the # symbol?
attr_accessor :foo is basically just a macro that generates two methods (a getter and a setter) for you:
def foo
#foo
end
def foo=(foo)
#foo = foo
end
Can you omit the # when calling your instance variable? Kind of – calling the instance variable name without the # means you are calling the instance method generated by attr_accessor instead of calling the instance variable. This works as long as the getter method is not overridden or extended.
But when you would try to set an instance variable without the #, it would not work that way, because then Ruby would set a local variable with that name. To set the instance variable through the instance method generated by attr_accessor you need to write self. (or another receiver) instead of the #:
#foo = 'bar' # assigns 'bar' to the instance variable `#foo`
foo = 'bar' # assigns 'bar' to a local variable `#foo`
But to use the setter method generated by attr_accessor:
self.foo = 'bar' # passes 'bar' to the instance method `foo=`
In the case where you have an attr_accessor and you want to read the value, you'll get the same value. It isn't exactly that you're omitting the # but that you're calling a method with the same name that returns the instance variable.
Omitting the # when trying to set the value will not work the same way; it will just set a local variable with the same name. Using an object's setter within the object requires that you precede it with self..
class Foo
attr_accessor :bar
def getter_equivalent?
bar.equal?(#bar) # Returns true. They are the same object.
end
def set_with_at(value)
#bar = value # Will set the instance variable
end
def set_without_at(value)
bar = value # Will not set the instance variable
end
def set_with_self(value)
self.bar = value # Will set the instance variable
end
end
A class example with attr_accessor expanded for clarity. The usual initialize method has been omitted to focus on the task at hand:
class Foo
def bar
#bar
end
def bar=(bar)
#bar = bar
end
#the above two methods represent attr_accessor :bar
def call_method
bar
#same as self.bar
#self refers to the current object i.e. the current instance of Foo
#bar refers to the method bar defined above
end
def call_instance_var
#bar
#refers directly to the the instance variable
end
end
You can use either one, I personally prefer calling the method rather than the instance variable.
Example
foo = Foo.new
foo.bar = "bar"
foo.call_method #=> "bar"
foo.call_instance_var #=> "bar"
Although it's possible to use accessor methods within the class, it's better practice to use the # symbol to refer to instance variables.
Here's an example where using a reader method within a class would produce unexpected results:
class A
def var
#var
end
def defined_using_method?
defined?(var)
end
def defined_using_at?
defined?(#var)
end
end
A.new.defined_using_method? # => "method"
A.new.defined_using_at? # => nil
I would like to create a class that does the following:
Its instance accepts a block.
During instance initialization, it executes certain actions, then calls the block, then executes more actions.
Within the block, another method from that class should be available.
Here is how i want it to work:
Foo.new do
puts "Hell, I can get you a toe by 3 o'clock this afternoon..."
bar
puts "...with nail polish."
end
I have managed to achieve it with the following class:
class Foo
def initialize(&block)
puts "This represents a beginning action"
instance_eval &block
puts "This symbolizes an ending action"
end
def bar
puts "I should be available within the block."
end
end
As you see, i use the instance_eval trick. It enables using bar within the block.
It works fine, but the problem here is that instance_eval makes current local context unavailable. If i use it from within another class, i lose access to that class' methods. For example:
class Baz
def initialize
Foo.new do
bar # -> Works
quux # -> Fails with "no such method"
end
end
def quux
puts "Quux"
end
end
The question is: how do i allow executing bar within the block without losing access to quux?
The only way that comes to my newbie mind is passing bar as an argument into the block. But that requires more typing, so i would like to aviod that if possible.
instance_eval does not consider the scope of where the block is called, so every method call is only relative to what is defined inside Foo.
So you have 2 options. Either
def initialize
baz = self
Foo.new do
bar # -> Works
baz.quux # -> Works
end
end
or
def initialize
puts "This represents a beginning action"
yield self
puts "This symbolizes an ending action"
end
....
def initialize
Foo.new do |b|
b.bar # -> Works too
quux # -> Works too
end
end
I am not sure which one would be better performance wise, but the option you pick is based on your own preference.
It works fine, but the problem here is that instance_eval makes
current local context unavailable
instance_eval() does no such thing. The code inside all blocks, i.e something that looks like:
{ code here }
can see the variables that existed in the surrounding scope at the time the block was CREATED. A block cannot see the variables in the surrounding scope at the time the block is EXECUTED. In computer science jargon, a block is known as a closure because it 'closes over' the variables in the surrounding scope at the time it is created.
What instance_eval does do is assign a new value to the self variable that the block closed over. Here is an example:
puts self #=>main
func = Proc.new {puts self}
func.call #=>main
class Dog
def do_stuff(f)
puts self
f.call
end
end
d = Dog.new
d.do_stuff(func)
--output:--
#<Dog:0x000001019325b8>
main #The block still sees self=main because self was equal to main when the block was created and nothing changed the value of that self variable
Now with instance_eval:
class Dog
def do_stuff(f)
puts self
instance_eval &f
end
end
d = Dog.new
d.do_stuff(func)
--output:--
#<Dog:0x000001011425b0>
#<Dog:0x000001011425b0> #instance_eval() changed the value of a variable called self that the block `closed over` at the time the block was created
You also need to realize that when you call a method and you don't specify a 'receiver', e.g.
quux()
...then ruby converts that line to:
self.quux()
So, it is important to know the value of the variable self. Examine this code:
class Dog
def do_stuff(f)
puts self #Dog_instance
instance_eval &f #equivalent to self.instance_val &f,
#which is equivalent to Dog_instance.instance_eval &f
end
end
Because instance_eval() sets the value of the self variable inside the block to instance_eval()'s 'receiver', the value of self inside the block is set equal to a Dog_instance.
Examine your code here:
puts self #=> main
Foo.new do
puts self #=>main
bar #equivalent to self.bar--and self is not a Foo or Baz instance
#so self cannot call methods in those classes
end
Examine your code here:
class Foo
def initialize(&block)
instance_eval &block #equivalent to self.instance_eval &block
end
end
And inside Foo#initialize() self is equal to the new Foo instance. That means inside the block self is set equal to a Foo instance, and therefore if you write the following inside the block:
quux()
That is equivalent to:
self.quux()
which is equivalent to:
Foo_instance.quux()
which means quux() must be defined in Foo.
In this answer:
class Baz
def initialize
puts self #=>Baz_instance
baz = self
Foo.new do
bar # -> Works
baz.quux # -> Works
end
end
def quux
puts "Quux"
end
end
b = Baz.new
...the bar and baz lines seem to have identical 'receivers':
puts self #=>Baz_instance
baz = self #To evaluate that assignment ruby has to replace the variable self
#with its current value, so this is equivalent to baz = Baz_instance
#and baz no longer has any connection to a variable called self.
Foo.new do
bar #=> equivalent to self.bar, which is equivalent to Baz_instance.bar
baz.quux #=> equivalent to Baz_instance.quux
end
But when instance_eval() executes that block, which is everything between the do and end, instance_eval() changes the value of self:
Foo.new do #instance_eval changes self inside the block so that self = Foo_instance
bar #=> equivalent to self.bar which is now equivalent to Foo_instance.bar
baz.quux #=> the block still sees baz = Baz_instance, so equivalent to Baz_instance.bar
end