Should we add attr_* methods in Ruby when creating a Class? - ruby

I create class in Ruby without any attr_* method like attr_reader, or attr_writer or attr_accessor.
So something like this
class DocumentIdentifier
def initialize( folder, name )
#folder = folder
#name = name
end
end
I mean it can be instantiated like this right
di = DocumentIdentifier.new(folder, 'image')
But i read some codes in some ruby books they also add attr_* to the instance variable like this
class DocumentIdentifier
attr_reader :folder, :name
def initialize( folder, name )
#folder = folder
#name = name
end
end
My question is, should we use attr_* in every class definition and why is the reason?

attr_reader will create methods that will return your instance variables with matching names.
class Foo
attr_reader :bar
def initialize(bar)
#bar = bar
end
end
This means that instances of Foo will both internally and externally have the method bar, that will respond with the value of the instance variable #bar:
foo = Foo.new(1)
foo.bar
=> 1
It's better to depend on behavior (a method) than on data (direct instance variable).
If you access the data through the methods generated by attr_reader you're ensuring you'll make your code slightly more "future proof" by encapsulating behavior in the same place, so it's always best to access all your instance variables through them.
Another interesting aspect is that you can limit your interface to match your needs. There's no need to expose bar to the outside world if you only use internally for instance, so:
class Foo
def initialize(bar)
#bar = bar
end
def zoo
bar
end
private
attr_reader :bar
end
Will behave like:
foo = Foo.new(1)
foo.zoo
=> 1
But:
foo.bar
NoMethodError (private method `bar' called for #<Foo:0x00005581544b83d8 #bar=1>)
Please let me know if that makes sense, otherwise I'll update the answer with more details.

attr_reader is a "getter method". In other words, it allows us to declare that a data attribute will be publicly read-able.
When you save a data attribute on an instance of a class, that data can't be viewed from outside of the class by default. With attr_reader set, we can access that attribute from anywhere.
Here's an example using the "name" field from your question.
# without "attr_reader"
class DocumentIdentifier
def initialize(folder, name)
#folder = folder
#name = name
end
end
first_example = DocumentIdentifier.new(nil, 'test')
first_example.name
This will give an error:
Traceback (most recent call last):
4: from /Users/ibell/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `<main>'
3: from /Users/ibell/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `load'
2: from /Users/ibell/.rvm/rubies/ruby-2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
1: from (irb):9
NoMethodError (undefined method `name' for #<DocumentIdentifier:0x00007fe0160956e8 #folder=2, #name="l">)
Now, if you add the attr_accessor, the same code will work.
class DocumentIdentifier
attr_reader :name
def initialize(folder, name)
#folder = folder
#name = name
end
end
second_example = DocumentIdentifier.new(nil, 'test')
second_example.name
This will give you the string "test" back.
Just as we need to read data, we might also want to write it. attr_writer works similarly to attr_reader, but it allows us to "set" data.
class DocumentIdentifier
attr_writer :name
def initialize(folder, name)
#folder = folder
#name = name
end
end
second_example = DocumentIdentifier.new(nil, 'test')
second_example.name = 'we can change this text'
second_example.name
This will give you back: 'we can change this text'
Lastly, attr_accessor is a shortcut for when you want attr_reader and attr_writer on the same attribute.

Related

What is the difference between an instance method used to rename an object and a setter method?

If I want to rename my jedi object below, why would I create an instance method named rename that uses the setter method name=? Why not just use the setter method `name=' directly?
Why do this:
class Skywalker
attr_accessor :name
def initialize(name)
#name = name
end
def rename(new_name)
self.name = new_name
end
end
jedi = Skywalker.new('Anakin')
puts jedi.name
jedi.rename('Luke')
puts jedi.name
When you could just do this:
class Skywalker
attr_accessor :name
def initialize(name)
#name = name
end
end
jedi = Skywalker.new('Anakin')
puts jedi.name
jedi.name = 'Luke'
puts jedi.name
Both code snippets above do the same thing, so I'm wondering if there is a situation where it would be useful to have the instance method rename in addition to the setter method name=. Because to me it looks like they are redundant.
#rename hides the implementation details. You expose a clean and explicit interface - an object can be renamed, but the caller doesn't have to care how it's done. I would recommend to use attr_reader :name instead of attr_accessor :name to avoid exposing the setter.
If you expose just #name= you let the caller to change object internals. It may cause the future changes harder (e.g. if you move name to a separate object).

Accessing an instance variable within the `initialize` method

Given this basic class in Ruby:
class TestClass
def initialize(name)
#name = name
end
end
How do I then access the instance variable name directly from within the initialize method without creating a getter function? is this even possible? (i.e. using dot notation) or does the initialize method cease to exist once a class is instantiated, hence the need to define a getter method?
I think what I'm trying to ask is initialize a class or instance method?
The "getter method" is defined so that you can use the variable from outside the class:
class TestClass
attr_reader :name
def initialize(name)
#name = name
end
end
# TestClass.new('Ben').name # => 'Ben'
If you don't need to access it from outside the class, you can just use #name:
class TestClass
def initialize(name)
#name = name
end
def greet
puts "Hello, %s" % #name
end
end
# TestClass.new('Ben').greet # outputs: Hello, Ben
You can use the #name inside initialize:
class TestClass
def initialize(name)
#name = name
puts "Name backwards: %s" % #name.reverse
end
end
# TestClass.new('Ben') # outputs neB
Initialize is a special method, when you define initialize instance method, it is automatically marked private. The class method new calls it after creating an instance of your class.
Nothing stops you from calling private methods from inside the class:
class TestClass
def initialize(name)
#name = name
puts "Name is now %s" % #name
end
def flip_name
initialize(#name.reverse)
end
end
# t = TestClass.new('Ben') # outputs "Name is now Ben"
# t.flip_name # outputs "Name is now neB"
# t.instance_variable_get(:#name) # => 'neB'
The flip_name method that calls initialize works just fine, but of course this is quite unconventional and almost never used, because it does not make much sense.
It's possible to call private methods from outside the class using send:
# t.send(:initialize, 'Bill') # outputs "Name is now Bill"
# t.instance_variable_get(:#name) # => 'Bill'
Without send, you get NoMethodError:
> t.initialize('Jack')
NoMethodError: private method `initialize' called for #<TestClass:0x00007fa2df8e4570>
Ruby 1.9 beta releases changed send to act like public_send does today, allowing access to only public methods and there was going to be funccall for calling private methods if you really want to, for unit testing purposes for example. I think it caused too much compatibility issues and the change was reverted.
So in conclusion, yes, you can call initialize again and it does not cease to exist, but it is almost never done because it makes very little sense. To access instance variables from inside the class, you use # notation like #name, to access them from outside of the class, you define a getter.
Yes, there is a way. But it's not a traditional one. It's more like querying the object to know
TestClass.new("foo").instance_variable_get(:#name)
=> "foo"
The initialize method does not "cease to exsist". It's executed, that's it. What your method do, in your case, is that the variable is set.

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).

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.

Ruby initialize and self

I found an interesting problem: http://rubeque.com/problems/fixing-bad-code-the-wrong-way/solutions
Generally we have a simple class (notice that we don't have attr_accessor here):
class Foo
def itnialize(name)
self.foo = name
end
def set_bar
self.bar = 'it will fail..'
end
end
I thought that ruby will raise no method error when I call Foo.new but it passes without any problems. The code will fail when I try Foo.new.bar
How is it possible and how to access Foo.new.foo variable?
You have a typo and have miss-spelt initialize as itnialize so it won't be being called - so no error.
It looks like you're trying to create an instance variable - to do so you need, somewhere, to define it with the # prefix. So you might do:
def initialize(name)
#foo = name
end
which would then mean you are able to access #foo inside the class.
self.foo can only ever refer to a method foo, so you need to define that method if you want to call it, either explicitly or by using one of the attr variants.
However, in this case, you could just do
def set_bar
#bar = 'it will succeed!'
end

Resources