Understanding Ruby Setters - ruby

If I create a class like this:
class Player
def initialize(position, name)
#position = position
#name = name
end
end
Isn't that setting the name to an instance variable? if so, why would I need to write a setter like this
class Player
def initialize(position, name)
#position = position
#name = name
end
def name=(name)
#name = name
end
end
Basically when is it necessary to write getters in a class?

Getters and setters job is to provide you a quick implementation of read and write of instance variables that you define in your constructor:
class Player
attr_accessor :name, :position
def initialize(position, name)
#position = position
#name = name
end
end
you can also user attr_reader(for getters) and attr_writer(for setters) specifically for these variables.
Above code: attr_accessor :name, :position gives you: #name, #position, #name=, and #position= methods for instance of Player class.
However, they're not going to give you validation or a customized logic for getters/setters.
For example: you might want to show a player's full name or do not wish your code to accept a 0 or negative position, in such a case you'd have to write getter and setter yourself:
class Player
def initialize(first_name, last_name, position)
#first_name = first_name
#last_name = last_name
#position = position
end
# validation for updating position using setter of position
def position=(new_position)
raise "invalid position: #{new_position}" if new_position <= 0
#position = new_position
end
# customized getter for name method
def name
"#{#first_name} #{#last_name}"
end
end
If you do not require customization as stated above then using attr_* method for these variables makes more sense.

initialize sets attributes during the initialization of a new object.
keeper = Player.new('goalkeeper','Shilton').
What if you want to update an attribute of keeper after initialzation? Well you'll need to use your ordinary setter method:
def name=(name)
#name = name
end
like so:
keeper.name = 'Banks'
If you don't have a setter method defined for Player instances, then you can't do this. Similarly for getter methods. Also be aware you can refactor your code by using attr_accessor like so:
class Player
attr_accessor :name, :position
def initialize(position, name)
#position = position
#name = name
end
end

Getters/setters, also known as "accessors", are accessible outside the class, instance variables are not. If you want things to be able to read or change #name from outside the class, you to define accessors for it.
Additionally accessor methods allow you to perform a certain amount of sanity checking or mutate incoming/outgoing values, and otherwise protect the internal state of your objects.

Related

Ruby: how are instance variables shared between two separate classes?

To preface, I am a raw Ruby novice, and fairly new to coding as well, so please forgive my ignorance. I'm trying to figure out a hypothetical exercise involving two classes, and passing variables between an instance of one class to the other. I've been searching for answers, but can't seem to find anything that applies directly to this kind of case.
class Person
def initialize(name, age)
#name = name
#age = age
puts "Hi #{#name}, you are #{#age}."
end
end
class Town
def initialize(town_name)
#town_name = town_name
puts "Welcome to #{#town_name}!"
end
def buy_house(person)
puts "#{#name}, at age #{#age}, you bought a house in #{#town_name}!"
end
end
When I instantiate Person and Town, I can see the instance variables being set:
me = Person.new("Daniel", 38)
townville = Town.new("Townville")
My real question is: how do I pass the variables set for the instance of "me" into any methods defined in Town using something like the line below?
townville.buy_house(me)
You can't use puts "#{#name}..." inside an instance of Town to refer to a Person's #name instance variable. #variable always refers to a variable belonging to the current object. If you want to access Person's instance variables, you need to make accessors for them.
You can do so by explicitly defining methods on Person which operate on the instance variables, or using attr_reader/attr_writer/attr_accessor which do so for you:
class Person
attr_reader :name, :age
# the above line is equivalent to defining two methods:
# def name; #name; end
# def age; #age; end
def initialize(name, age)
#name = name
#age = age
puts "Hi #{#name}, you are #{#age}."
end
end
# class Town ...
def buy_house(person)
puts "#{person.name}, at age #{person.age}, you bought a house in #{#town_name}!"
end
If you want to "inject" all the instance variables of one object into another, that is absolutely possible. Whether this is useful or not is up to you to decide:
class Object
def inject_instance_variables(other)
other.instance_variables.each do |var|
self.instance_variable_set(var, other.instance_variable_get(var))
end
end
end

Ruby: Variable initialization within classes

Having some trouble when it comes to initializing variables within a class (instance variables etc.) and I was wondering if anybody could clarify the proper syntax for me.
Sample code:
Class Pets
attr_accessor :name
def initialize(name)
#name=name
end
def name=(name)
#name = name
#I believe this is where I change #name instance variable
end
#in this space I could create more <methods> for Class.new.<method>
end
My question is do I need to have attr_accessor as well as def initialize and def name=?
In addition, if I have multiple attr_accessors do I need to add them as arguments to def initialize, e.g.:
Class Pets
attr_accessor :name :age :color
def initialize(name, age, color)
#name = name
#age = age
#color = color
#and if this is the case do I need methods for each (name= age= color= etc.)
end
One last thing:
If someone could confirm or deny my thought process on the name= age= and color= type of methods within the classes. Am I correct in thinking method= is necessary to change the instance variable? I am a bit unsure about what the method= is for and why I cannot change the instance variable within initialize.
attr_accessor :symbol do the same as attr_writer :symbol and attr_reader :symbol, i.e. it creates both reader (def symbol; #symbol; end) and writer (def symbol=(value); #symbol = value; end).
Initialize is a method called every time new instance of the class is being created. It is not the same as new method as some classes may have its own custom factory methods. You don't need to define your initialize method, only problem is that then symbol reader would return nil, as the local variable would not been set.
In ruby everything is a method. In case of objects, object.attr = value is just a short for object.attr=(value) where attr= is just another method. (Similarly << operator is defined as a method on Array class, attr_accessor is a method defined on class "Class").
To piggy back on what what said earlier, recall that if you want your attributes to be accessible outside your class (you want to write over the attribute value or you want to read it) you will need to use the attr_accessor (or attr_writer or attr_reader).
If I had a class like ...
class Calendar
attr_reader :event_name, :all_events
def initialize
#event_name = event_name
#all_events = []
end
def create_event(event_name)
puts "#{event_name} has been added to your calendar."
#all_events << event_name
p #all_events
end
def see_all_events
puts "Here are your events --"
#all_events.each {|event| puts "- #{event}"}
end
end
my_calendar=Calendar.new
my_calendar.create_event("interview")
my_calendar.see_all_events
my_calendar.all_events
I can read all my events either with the method see_all_events or by calling all_events on my class Calendar object. If for some reason I did not want a see_all_events method but instead only wanted it to be seen by calling all_events on my object I can only do this because of attr_reader.
Basically the point here is to remember exactly how you want your users to interact with your object attributes. If it needs to be private and only accessed via methods then you should be weary of using attr_accessor or attr_writer or attr_reader (depending on the situation).

Setting instance variables on different objects of the same class

Let's say I have a class called Person and person has an attribute called partner. When I call partner= on one of the Person objects, I want to set the #partner instance variable of both objects. Here's an example with invalid syntax:
class Person
attr_reader :partner
def partner=(person)
# reset the old partner instance variable if it exists
partner.#partner = nil if partner
# set the partner attributes
#partner = person
person.#partner = self
end
end
Change the attr_reader to an attr_accessor and add a helper method:
class Person
attr_accessor :partner
def link_partners(person)
#partner = person
person.partner = self
end
end
Update for visibility. Based on suggestion from Frederick below. This is a little more verbose, but will prevent partner from being set directly:
class Person
protected
attr_writer :partner
public
attr_reader :partner
def link_partners(person)
#partner = person
person.partner = self
end
end
Both implementations works like this:
p1, p2 = Person.new, Person.new
p1.link_partners(p2)
# p2.link_partners(p1)
You could provide a protected helper method which gets called by your partner= method to do the actual work. Since it can't be called by "outsiders", all of your check and balances can be maintained in your implementation of partner=:
class Person
attr_reader :partner
def partner=(person)
#partner.set_partner(nil) if #partner
set_partner(person)
person.set_partner(self) if person
end
def set_partner(person)
#partner = person
end
protected :set_partner
end
Never mind, I just discovered instance_variable_set.
class Person
attr_reader :partner
def partner=(person)
# reset the old partner instance variable if it exists
partner.instance_variable_set(:#partner, nil) if partner
# set the partner attributes
#partner = person
person.instance_variable_set(:#partner, self)
end
end
In theory, you could do this as follows:
def partner=(person)
#partner = person
person.instance_variable_set(:#partner, self)
end
However, I would consider this to be magic. (That's not a good thing.) Instead, make the attr_reader into an attr_accessor and write a different method to set two persons' partners to each other.
This will solve your issue with recursive set and looks better than your solution:
class Partner
attr_reader :partner
def set_partner(person, recursive = true)
# reset previous partner
#partner.set_partner(nil, false) if recursive && #partner
# set new partner
#partner = person
#partner.set_partner(self, false) if recursive
end
alias_method :partner=, :set_partner
end

Intermingling attr_accessor and an initialize method in one class

I see code like:
class Person
def initialize(name)
#name = name
end
end
I understand this allows me to do things like person = Person.new and to use #name elsewhere in my class like other methods. Then, I saw code like:
class Person
attr_accessor :name
end
...
person = Person.new
person.name = "David"
I'm just at a loss with these two methods mesh. What are the particular uses of def initialize(name)? I suppose attr_accessor allows me to read and write. That implies they are two separate methods. Yes? Want clarifications on def initialize and attr_accessor and how they mesh.
initialize and attr_accessor have nothing to do with each other. attr_accessor :name creates a couple of methods:
def name
#name
end
def name=(val)
#name = val
end
If you want to set name upon object creation, you can do it in the initializer:
def initialize(name)
#name = name
# or
# self.name = name
end
But you don't have to do that. You can set name later, after creation.
p = Person.new
p.name = "David"
puts p.name # >> "David"
Here is the answer you are looking for Classes and methods. Read it carefully.
Here is a good documentation from the link:
Classes and methods
Now we are ready to create our very own Address class. Let's start simple. Let's start with an address that only contains the "street" field.
This is how you define a class:
class Address
def initialize(street)
#street = street
end
end
Let's go through this:
The class keyword defines a class.
By defining a method inside this class, we are associating it with this class.
The initialize method is what actually constructs the data structure. Every class must contain an initialize method.
#street is an object variable. Similar to the keys of a hash. The # sign distinguishes #street as an object variable. Every time you create an object of the class Address, this object will contain a #street variable.
Let's use this class to create an address object.
address = Addres.new("23 St George St.")
That's it. address is now an object of the class Address
Reading the data in an object
Suppose that we want to read the data in the address object. To do this, we need to write a method that returns this data:
class Address
def initialize(street)
#street = street
end
# Just return #street
def street
#street
end
end
Now the method Address#street lets you read the street of the address. In irb:
>> address.street
=> "23 St George St."
A property of an object, which is visible outside, is called an attribute. In this case, street is is an attribute. In particular, it is a readable attribute. Because this kind of attribute is very common, Ruby offers you a shortcut through the attr_reader keyword:
class Address
attr_reader :street
def initialize(street)
#street = street
end
end
Changing the data in an object
We can also define a method to change the data in an object.
class Address
attr_reader :street
def initialize(street)
#street = street
end
def street=(street)
#street = street
end
end
Ruby is pretty smart in its use of the street= method:
address.street = "45 Main St."
Notice that you can put spaces betten street and =. Now that we can change the address data, we can simplify the initialize method, and have it simply default the street to the empty string "".
class Address
attr_reader :street
def initialize
#street = ""
end
def street=(street)
#street = street
end
end
address = Address.new
address.street = "23 St George St."
This might not seem like much of a simplification, but when we add the city, state and zip fields, and more methods this will make the class definition a bit simpler.
Now, street is also a writable attribute. As before, you can declare it as such with attr_writer:
class Address
attr_reader :street
attr_writer :street
def initialize
#street = ""
end
end
Accessing data
Very often, you have attributes that are both readable and writable attributes. Ruby lets you lump these together with attr_accessor. I guess these would be called "accessible attributes", but I have never seen them be called that.
class Address
attr_accessor :street
def initialize
#street = ""
end
end
With this knowledge, it is now easy to define the entire addressbook structure. As it turns out, attr_accessor and friends all accept multiple arguments.
class Address
attr_accessor :street, :city, :state, :zip
def initialize
#street = #city = #state = #zip = ""
end
end
I think you consider initialize as a constructor. To be precise, it is not. The default constructor is the new method on the class, and initialize is called by that method. If you do not define initialize, you can still create an object with new because initialize is not the constructor itself. In that case, the default initialize does nothing. If you do define initialize, then that is called right after the object creation.
The statement #foo = ... and attr_accessor :foo are different. The former assigns a value to the instance variable #foo, whereas the latter lets you access #foo via methods foo and foo=. Without the latter, you can still access #foo by directly describing so.
Unlike C++,Java instance variables in Ruby are private by default(partially as they can be accessed by using a.instance_variable_get :#x)
eg:
class Dda
def initialize task
#task = task
#done = false
end
end
item = Dda.new "Jogging" # This would call the initializer and task = Jogging would
be set for item
item.task # would give error as their is no function named task to access the instance
variable.
Although we have set the value to item but we won't be able to do anything with it as instace variables are private in ruby.
code for getter:
def task
#task
end
#for getter
def task=task
#task = task
end
Using getter would ensure that item.task returns it's value
And using setter gives us the flexibility to provide values to instance variables at any time.

using attributes of different objects in different objects methods

what i need is basically to use variable from one file, in the method. let me explain
lets say we have
class Game
attr_accessor :number, :object
end
where number is just some number and object is object of some other class defined by me, lets name it Player class. now we make another file, which requires class Game, and which goes like this:
require './Game.rb'
require './Player.rb'
myGame = Game.new
myGame.number = 1
myGame.object = Player.new
and now the big moment. in method defined in Player class, i would like to use myGame.number attribute. eg like this
class Player
attr_accessor :some_var
def method
#some_var = myGame.number
end
end
How can i achieve this?
Your player should have a reference to the game is playing. For instance
class Game
attr_accessor :number
attr_reader :my_player
def my_player=(player)
player.my_game = self
#my_player = player
end
end
class Player
attr_accessor :some_var, :my_game
def method
#some_var = #my_game.number if #my_game
end
end
myGame = Game.new
myGame.number = 1
myGame.my_player = Player.new()
myGame.my_player.method
puts myGame.my_player.some_var
Alternatively to toch's answer, you can keep a reference to the game object when you set the player accessor. Instead of using automatic accessors you can use the get_ set_ accessor syntax to have custom code in the accessor, which would set the reference on the rvalue.

Resources