Can't get object data from class instance in ruby - ruby

How can I get the data out of my class instance? I can get this:
instance = MyModule::MyClass.new(obj1, obj2)
puts instance
#=> #<MyModule::MyClass:0x0000010120de68>
puts instance.inspect
#=> #<MyModule::MyClass:0x000001019157b0 #obj1=#<MyModule::MyOtherClass:0x00000101915b20 #obj=["stuff", "more stuff", "things"]>, #obj2=#<MyModule::MyThirdClass:0x00000101915a80 #obj=["more things", "even more"]>>
I thought I could just do instance.obj1 and get the #obj array from this. This gives me "undefined method." What's wrong here?

You can't access instance variables from the outside by default:
class Foo
def initialize(obj)
#obj = obj
end
end
foo = Foo.new(123) #<Foo:0x007fdc312205f0 #obj=123>
foo.obj # undefined method `obj' ... (NoMethodError)
You have to create a getter to do so, e.g. via attr_reader:
class Foo
attr_reader :obj
def initialize(obj)
#obj = obj
end
end
foo = Foo.new(123)
foo.obj #=> 123

You'd have to post your class definition for us to be certain what the problem is, but I'm guessing that you didn't define attribute getters/setters for your objects. Because of the way the Ruby variable scoping works, instance variables are localized to the inner scope of the object instance (meaning that they are only accessible within method definitions within the class definition).
To access these variables from outside the scope of the class definition, you must define attribute getters and setters, with are methods that expose these variables to the outer scope:
class Foo
def initialize(name)
#name = name
end
# attribute getter for #name
def name
#name
end
# attribute setter for #name
def name=(n)
#name = n
end
end
This is such a common pattern that Ruby has provided helper methods for defining setters and getters for instance variables: attr_reader, attr_writer, and attr_accessor.
attr_reader takes one or more symbols as arguments (which correspond to the instance variables you wish to target) and creates getter methods for each.
attr_writer takes the same list of symbols as attr_reader but creates setter methods for each instance variable name passed as an argument
attr_accessor generates both setter and getter methods for the instance variables named.
class Bar
attr_reader :name # only allow reading of #name
attr_accessor :rank, :age # enable both reading and writing of #rank and #age
def initialize(name, rank, age)
#name = name
#rank = rank
#age = age
end
end
b = Bar.new('Jack BeLucky', 'Corporal', 32)
b.name
=> 'Jack BeLucky'
b.age
=> 32
b.age += 1
b.age
=> 33

Related

Calling Custom Writers Within Instance Methods: Why is self not implicit? [duplicate]

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 6 years ago.
I am familiar with the conventional way of using attr_accessors within the initialize method:
class Dog
attr_accessor :name
def initialize(name)
#name = name
end
end
dog = Dog.new("Denver")
p dog.name
=> "Denver"
I was playing around with custom writers and readers. I was suprised to realize the following:
Within instance methods: you can call upon a reader method without explicitly specifying self because self is implicit.
Within instance methods: you MUST explicitly call self to call upon a writer method. self IS NOT implicit for attr_writers
Example 1 to show that self is implicit when calling attr_reader methods within instance methods:
class Dog
def name
#name
end
def name=(val)
#name = val
end
def initialize(name)
#name = name
end
def call_name
# no need to say: p self.name
p name
end
end
dog = Dog.new("Denver")
dog.call_name
=> "Denver"
Example 2 to show that self IS NOT implicit when calling upon attr_writers within instance methods. In other words: the instance variable is not getting set because I did not prepend the writer method below with self:
class Dog
def name
#name
end
def name=(val)
#name = val
end
def initialize(name_val)
# works as expected with: self.name = name_val
name = name_val
end
def change_name
# works as expected with: self.name = "foo"
name = "foo"
end
end
dog = Dog.new("Denver")
p dog.name
dog.change_name
p dog.name
=> nil
=> nil
Question: why isn't self implicit within instance methods when calling attr_writers? Why do I have to explicitly specify self for attr_writers within instance methods?
Question: why isn't self implicit within instance methods for
attr_writers?
Because defining a local variable takes precedence.
So here:
name = "foo"
you do not write an attribute, but defining a local variable name.

What happens at the background when variables are declared in Ruby?

I would like to know what happens behind the scene when variables are declared in ruby. For example, What differentiates these variables from one another?
#normal variables
name = "John"
#instant variables
#name = "John"
#class variables
##name = "John"
#class instance variables
def self.namer
#name = "John"
end
#constants
NAME = "John"
Normal variables, like name, are local. They're only available in the scope in which they were declared.
class Dog
def set_a_variable
name = "Fido"
end
def show_a_variable
name
end
end
my_dog = Dog.new
my_dog.set_a_variable
my_dog.show_a_variable
=> NameError: undefined local variable or method `name'
Instance variables, like #name, belong to the instance of a class, so every instance method for an instance of a class has access to that variable. If not set, nil is assumed.
class Dog
def set_a_variable
#name = "Fido"
end
def show_a_variable
#name
end
end
my_dog = Dog.new
my_dog.set_a_variable
my_dog.show_a_variable
=> "Fido"
my_second_dog = Dog.new
my_second_dog.show_a_variable
=> nil # not shared between different instances
Class variables, like ##legs, are accessible by all instances of a class, so every every instance has access to that variable. They're also inherited by sub-classes.
class Animal
def set_a_variable
##legs = 4
end
end
class Dog < Animal
def show_a_variable
##legs
end
end
my_animal = Animal.new
my_animal.set_a_variable
my_dog = Dog.new
my_dog.show_a_variable
=> 4
my_second_dog = Dog.new
my_second_dog.show_a_variable
=> 4
Class instance variables (#name defined in a class method) belong to the specific class, so every instance method has access to that variable, but it's not inherited by child classes.
class Animal
def self.set_a_variable
#legs = 2
end
def self.show_a_variable
#legs
end
def show_a_variable
self.class.show_a_variable
end
end
class Dog < Animal
def self.set_a_variable
#legs = 4
end
end
my_dog = Dog.new
Dog.set_a_variable
my_animal = Animal.new
Animal.set_a_variable
my_dog.show_a_variable
=> 4
Constants are NOT global, but are accessible via scoping anywhere.
class Animal
LEGS = 4
end
class Dog
def show_a_variable
Animal::LEGS
end
end
my_dog = Dog.new
my_dog.show_a_variable
=> 4
Variables are never declared in Ruby. They just spring into existence when they are first assigned.
Their scope differentiates them.

What happendd if I change the instance variable of subclass in Base class function

I wonder if I write a function of base class and modified the subclass instance variable inside that function, how can ruby distinguish them? For example:
class Parent
def change_name
#name = 'Parent'
end
end
class Subclass < Parent
def initialize
#name = "subclass"
end
def get_name
#name
end
end
sub_class = Subclass.new
sub_class.change_name
sub_class.get_name #=> Parent
What confused me is that #name is the instance variable of Subclass, how can the function change_name change it just like #name was belongs to
Base class. Is it in ruby shared the same public instance variable between Base and Sub class.
In Ruby, the parent and subclass class don't have separate objects. When you create an instance of the subclass class, it is also an instance of the parent class. There is one object and it is both classes at once.
Since there is only one object, there is only one set of instance variables. So in effect, you can think of this like the following:
class CombinedClass
def initialize
#name = "subclass"
end
def get_name
#name
end
def change_name
#name = 'Parent'
end
end
combined_class = CombinedClass.new
combined_class.change_name
combined_class.get_name #=> Parent
Hopefully it makes more sense now.

Setting not initialized instance variable

Is it elegant to use instance variables in a class which are not initialized and setting them using other methods? Or maybe there is a better way to do that?
class Klass
def initialize(a)
#a = a
end
def set_b(b)
#b = b
end
end
In contrast to other languages, If you do not initialize an instance variable it will always be nil (whereas in certain other languages you could get something undefined).
As long as other methods of Klass do not depend on the instance variable actually having a value, this should be ok.
As for getters and setters, there are attr_accessor, attr_reader and attr_writer (see the docs).
class Klass
attr_accessor :b
# there's also attr_reader and attr_writer
def initialize(a)
#a = a
end
end
k = Klass.new :foo
k.b = :bar
k.b
#=> :bar
k.a
#=> undefined method `a' for #<Klass:0x007f842a17c0e0 #a=:foo, #b=:bar> (NoMethodError)
The way you are doing it works but Ruby defined attr_accessor, attr_reader and attr_writer for that purpose.
attr_reader: create method to read 'a'
attr_writer: create method to write 'a'
attr_accessor: create methods to read and write 'a'
I think the best way to do that is to use attr_accessor:a
class Klass
attr_accessor:a
def initialize(a)
#a = a
end
end
Then you can do:
k = Klass.new "foo" #example
k.a = "bar"

Instance variables and attr_accessor

When I define #foo=3 in the initialize method, I expect to access my variable like this.
class Object
def initialize(v)
#foo = v
end
Object.new.foo
That doesn't happen though. I need to write attr_accessor :foo. Why do I need to do this even though # already does that for me?
One instance variable in Ruby is not public by default. And access should be granted based on accessors.
For read only attr_reader
For write only attr_writer
For read write attr_accessor
It is not accessible by default
# does not automatically do that for you. That's why. attr_accessor creates getters and setters for your instance variables ("#-variables").
The instance variables are private. You need accessors and mutators to access them. A common accessor/mutator pair looks like:
def foo
#foo
end
def foo=(value)
#foo=value
end
This creates an abstraction which you can now use as:
Classname.new.foo
Classname.new.foo="OOP"
Since this is such a common need and also reults in lot of boilerplate(read: unnecessary) code, ruby provides a dynamic method which literally defines these two methods for you.
attr_accessor :foo
If you want only one of accessor or mutator method, then use the corresponding from following:
attr_reader :foo
attr_writer :foo
This will save a lot of copy/paste. I hope I was clear.
All Ruby attributes are "private" and are invisible outside the class's methods. You need accessor methods to read and write an attribute. So, in your example, you need
class MyClass
def initialize(v)
#foo = v
end
def foo
#foo
end
def foo=(v)
#foo = v
end
end
Then MyClass.new(4).foo will work, and return 4.
You can also add the accessor methods using the convenience methods
attr_reader :foo
attr_writer :foo
or
attr_accessor :foo
An instance variable starts with an # character. All instance variables are private, which means you can't read them and you can't change their value. So what to do?
class Dog
def initialize(name)
#name = name
end
def name #getter
#name
end
def name=(str) #setter
#name = str
end
end
Well, that gets to be a pain to type out, so ruby provides a shortcut:
class Dog
attr_accessor :name
def initialize(name)
#name = name
end
end

Resources