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

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.

Related

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.

Avoiding initialize method in ruby

I am writing the Ruby program found below
class Animal
attr_reader :name, :age
def name=(value)
if value == ""
raise "Name can't be blank!"
end
#name = value
end
def age=(value)
if value < 0
raise "An age of #{value} isn't valid!"
end
#age = value
end
def talk
puts "#{#name} says Bark!"
end
def move(destination)
puts "#{#name} runs to the #{destination}."
end
def report_age
puts "#{#name} is #{#age} years old."
end
end
class Dog < Animal
end
class Bird < Animal
end
class Cat < Animal
end
whiskers = Cat.new("Whiskers")
fido = Dog.new("Fido")
polly = Bird.new("Polly")
polly.age = 2
polly.report_age
fido.move("yard")
whiskers.talk
But when I run it, it gives this error:
C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `new'
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `<main>'
My investigations shows that I should create objects like this
whiskers = Cat.new("Whiskers")
Then there should be an initialize method in my code which will initialize the instance variable with the value "Whiskers".
But if I do so then what is the purpose of attribute accessors that I am using? Or is it like that we can use only one and if I have to use attribute accessors then I should avoid initializing the instance variables during object creation.
initialize is the constructor of your class and it runs when objects are created.
Attribute accessors are used to read or modify attributes of existing objects.
Parameterizing the constructor(s) gives you the advantage of having a short and neat way to give values to your object's properties.
whiskers = Cat.new("Whiskers")
looks better and it's easier to write than
whiskers = Cat.new
whiskers.name = "Whiskers"
The code for initialize in this case should look like
class Animal
...
def initialize(a_name)
name = a_name
end
...
end
All attr_reader :foo does is define the method def foo; #foo; end. Likewise, attr_writer :foo does so for def foo=(val); #foo = val; end. They do not do assume anything about how you want to structure your initialize method, and you would have to add something like
def initialize(foo)
#foo = foo
end
Though, if you want to reduce boilerplate code for attributes, you can use something like Struct or Virtus.
You should define a method right below your class name, something like
def initialize name, age
#name = name
#age = age
end

Can't get object data from class instance in 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

Ruby metaprogramming: adding #variables to existing 'initialize' method (using class_eval) [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Ruby.Metaprogramming. class_eval
I have this little project, the goal is to create a 'attr_accessor_with_history' method, that will keep a record of every single value assigned to variables created by it. Here's the code :
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s # make sure it's a string
attr_reader attr_name # create the attribute's getter
attr_reader attr_name+"_history" # create bar_history getter
a = %Q{
def initialize
##{attr_name}_history = [nil]
end
def #{attr_name}
##{attr_name}
end
def #{attr_name}=(new_value)
##{attr_name}=new_value
##{attr_name}_history.push(new_value)
end }
puts a
class_eval(a)
end
end
Now, when I test the script for one variable. It works fine. But when I try to create two or more variables (like this) ....
class Foo
attr_accessor_with_history :bar
attr_accessor_with_history :lab
end
a = Foo.new
a.bar = 45
a.bar = 5
a.bar = 'taat'
puts a.bar_history
b = Foo.new
b.lab = 4
b.lab = 145
b.lab = 'tatu'
puts b.lab_history
....Ruby gives out a "no-existing 'push' method for (class_eval) bar_history.push(new_value)". I think that 'initialize' method gets overriden on the second call of attr_accessor_with_history, so the record for the first variable gets destroyed.
I have no idea how to get around this. I already tried calling 'super' . Any clue ?
In your setter method just check if the the history instance variable is already initialized:
def #{attr_name}=(new_value)
##{attr_name}=new_value
##{attr_name}_history ||= [nil]
##{attr_name}_history.push(new_value)
end
You'll need another getter for your history variable that sets your default value if it was not set before:
def #{attr_name}_history
##{attr_name}_history ||= [nil]
end
Then you could remove your initialize method, that was btw vulnerable to be overwritten.

In Ruby, when should you use self. in your classes? [duplicate]

This question already has answers here:
When to use `self.foo` instead of `foo` in Ruby methods
(3 answers)
Closed 9 years ago.
When do you use self.property_name in Ruby?
Use self when calling a class's mutator. For example, this won't work:
class Foo
attr_writer :bar
def do_something
bar = 2
end
end
The problem is that 'bar = 2' creates a local variable named 'bar', rather than calling the method 'bar=' which was created by attr_writer. However, a little self will fix it:
class Foo
attr_writer :bar
def do_something
self.bar = 2
end
end
self.bar = 2 calls the method bar=, as desired.
You may also use self to call a reader with the same name as a local variable:
class Foo
attr_reader :bar
def do_something
bar = 123
puts self.bar
end
end
But it's usually better to avoid giving a local variable the same name as an accessor.
self references the current object. This lends itself to many uses:
calling a method on the current object
class A
def initialize val
#val = val
end
def method1
1 + self.method2()
end
def method2
#val*2
end
end
Here running A.new(1).method1() will return 3. The use of self is optional here - the following code is equivalent:
class A
def initialize val
#val = val
end
def method1
1 + method2()
end
def method2
#val*2
end
end
self is not redundant for this purpose though - operator overloading makes it neccessary:
class A
def initialize val
#val = val
end
def [] x
#val + x
end
def method1 y
[y] #returns an array!
end
def method2 y
self.[y] #executes the [] method
end
end
This shows how self must be used if you want to call the current object's [] method.
referencing attributes
You can generate the methods to read and write to instance variables using attr_accessor and co.
class A
attr_accessor :val
def initialize val
#val = val
end
def increment!
self.val += 1
end
end
Using self is redundant here because you can just reference the variable directly, eg. #val.
Using the previous class, A.new(1).increment! would return 2.
method chaining
You can return self to provide a form of syntactical sugar known as chaining:
class A
attr_reader :val
def initialize val
#val = val
end
def increment!
#val += 1
self
end
end
Here, because we are returning the current object, methods can be chained:
A.new(1).increment!.increment!.increment!.val #returns 4
creating class methods
You can define class methods using self:
class A
def self.double x
x*2
end
def self.quadruple x
self.double(self.double(x))
end
end
This will enable you to call A.double(2) #= 4 and A.quadruple(2) #=8. Note that in a class method, self references that class because the class is the current object.
how the value of self is determined
The current value of self in a particular method is set to the object that that method was called upon. Normally this uses the '.' notation. When you run some_object.some_method(), self is bound to some_object for the duration of some_method, meaning that some_method can use self in one of the ways mentioned above.
Using self is used will reference the current object accessible within a program. Therefore, self.property is used when accessing a variable through a attr_accessor of some sort. In must cases, it can be used in place of #property from within an object.

Resources