Why Use Ruby'S Attr_Accessor, Attr_Reader and Attr_Writer

Why use Ruby's attr_accessor, attr_reader and attr_writer?

You may use the different accessors to communicate your intent to someone reading your code, and make it easier to write classes which will work correctly no matter how their public API is called.

class Person
attr_accessor :age
...
end

Here, I can see that I may both read and write the age.

class Person
attr_reader :age
...
end

Here, I can see that I may only read the age. Imagine that it is set by the constructor of this class and after that remains constant. If there were a mutator (writer) for age and the class were written assuming that age, once set, does not change, then a bug could result from code calling that mutator.

But what is happening behind the scenes?

If you write:

attr_writer :age

That gets translated into:

def age=(value)
@age = value
end

If you write:

attr_reader :age

That gets translated into:

def age
@age
end

If you write:

attr_accessor :age

That gets translated into:

def age=(value)
@age = value
end

def age
@age
end

Knowing that, here's another way to think about it: If you did not have the attr_... helpers, and had to write the accessors yourself, would you write any more accessors than your class needed? For example, if age only needed to be read, would you also write a method allowing it to be written?

When would you use attr_writer?

What if you want a trivial writer and a custom reader? It's common to want to memoize a default value when calling a reader if the ivar isn't already set.

class Oof
attr_writer :rab

def rab
@rab ||= "rab, of course!"
end
end

oof = Oof.new
oof.rab # => "rab, of course!"
oof.rab = "zab"
oof.rab # => "zab"

What is attr_accessor in Ruby?

Let's say you have a class Person.

class Person
end

person = Person.new
person.name # => no method error

Obviously we never defined method name. Let's do that.

class Person
def name
@name # simply returning an instance variable @name
end
end

person = Person.new
person.name # => nil
person.name = "Dennis" # => no method error

Aha, we can read the name, but that doesn't mean we can assign the name. Those are two different methods. The former is called reader and latter is called writer. We didn't create the writer yet so let's do that.

class Person
def name
@name
end

def name=(str)
@name = str
end
end

person = Person.new
person.name = 'Dennis'
person.name # => "Dennis"

Awesome. Now we can write and read instance variable @name using reader and writer methods. Except, this is done so frequently, why waste time writing these methods every time? We can do it easier.

class Person
attr_reader :name
attr_writer :name
end

Even this can get repetitive. When you want both reader and writer just use accessor!

class Person
attr_accessor :name
end

person = Person.new
person.name = "Dennis"
person.name # => "Dennis"

Works the same way! And guess what: the instance variable @name in our person object will be set just like when we did it manually, so you can use it in other methods.

class Person
attr_accessor :name

def greeting
"Hello #{@name}"
end
end

person = Person.new
person.name = "Dennis"
person.greeting # => "Hello Dennis"

That's it. In order to understand how attr_reader, attr_writer, and attr_accessor methods actually generate methods for you, read other answers, books, ruby docs.

Why do we need attr_accessor?

Because it makes the code easier to maintain.

This way its clear from reading the class code what you expect to have happen, and that you expect other classes to access these variables.

Without this outside code could simply access your internal variables, and then if you refactor your code and remove or rename such a variable the outside code breaks.

Thus the accessor methods make it clear you intend for other classes to access these methods

FYI: Ruby is very powerful, and if you want you can go ahead and make it work the way you want. I don't recommend this but am pointing it out so you will understand that its a explicit choice being made for the good of keeping your code readable. But if you want to see how you could do this read on, and try running the code...

<script type="text/ruby">
def puts(s) # ignore this method, just dumps to the display Element['#output'].html = Element['#output'].html + s.to_s + "<br/>"end
class OpenKimono def method_missing(name, *args) if name =~ /=$/ instance_variable_set("@#{name[0..-2]}".to_sym, args[0]) else instance_variable_get("@#{name}") end endend
class MyClass < OpenKimonoend
foo = MyClass.new
foo.bar = 12
puts "foo.bar just set to 12, it now = #{foo.bar}"
foo.baz = 13
puts "foo.baz just set to 13, it now = #{foo.baz}"
puts "foo.manchu has never been set... what does it equal? #{foo.manchu}"
</script>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><script src="https://rawgit.com/reactive-ruby/inline-reactive-ruby/master/inline-reactive-ruby.js"></script><div id="output" style="font-family: courier"></div>

attr_accessor vs attr_reader & instance variables

attr_accessor defines getter and setter.attr_reader defines only getter.

class Car
attr_reader :engine
def initialize(engine)
@engine = engine
end
end

Car.instance_methods(false) # => [:engine]

With the above code you defined only def engine; @engine ;end.

class Car
attr_accessor :engine
def initialize(engine)
self.engine = engine
end
end

Car.instance_methods(false) # => [:engine, :engine=]

With the above code you defined only def engine; @engine ;end and def engine=(engine) ;@engine = engine ;end.

What does it mean that `attr_accessor` / `attr_reader` create an instance variable?

That's a bug/misleading wording in documentation. The attr_reader/attr_accessor themselves don't create any variables. How can they? They work outside of class instance lifecycle. And even read access don't make the instance variables come to life. Only write access creates them.

class Foo
attr_accessor :bar
end

foo = Foo.new
foo.instance_variables # => []
foo.bar # try read ivar
foo.instance_variables # => [], nope, not yet
foo.bar = 2 # write ivar
foo.instance_variables # => [:@bar], there it is

Why is attr_accessor necessary in Rails?

attr_accessor is a core feature of Ruby and is used to generate instance variables with getter and setter methods. Its use is never required in basic Ruby (it's a convenience).

In the case of ActiveRecord models, getters and setters are already generated by ActiveRecord for your data columns. attr_accessor is not needed or desirable.

If you have additional instance data you don't need to persist (i.e. it's not a database column), you could then use attr_accessor to save yourself a few lines of code.

The similarly-named attr_accessible — which is frequently seen in Rails code and confused with attr_accessor — is a deprecated method of controlling mass assignment within ActiveRecord models. Rails 4 doesn't support it out of the box; it has been replaced by Strong Parameters, which allows more granular control.

Is it better practice to use an attr_accessor here or not?

I think your use of attr_accessor here is fine, though depending on how much functionality you continue to add, it will likely be clearer to restrict functionality of that class to the class itself.

Regarding your question on using a method within QuestionList, this would come down to readability. Something to note first, however: you used QuestionsList.add_question(question) as an example. This would create a class method. What you really want here is an instance method, which would read as list.add_question(question), since you already have an instance of the list created. This blog post has some good information on the difference between class and instance methods.

I personally find instance methods would most clearly communicate your intent. I would write out QuestionList as follows:

class QuestionList

def initialize
@questions = []
end

def add_question(question)
@questions << question
end

def print_all_questions_in_list
@questions.each do |question|
puts "#{question.ask}"
end
end

end

This SO post has some excellent information on Ruby's attr methods, if you'd like additional info there.

How using in Ruby's attr_reader and attr_writer for variable @@?

There is no such thing in Ruby. But it is not that hard to write your own:

class Module
def mattr_writer(*attrs)
attrs.each do |attr|
define_singleton_method(:"#{attr}=") do |val|
class_variable_set(:"@@#{attr}", val)
end
end
end

def mattr_reader(*attrs)
attrs.each do |attr|
define_singleton_method(attr) do
class_variable_get(:"@@#{attr}")
end
end
end

def mattr_accessor(*attrs)
mattr_reader(*attrs)
mattr_writer(*attrs)
end
end

class Foo; end
Foo.mattr_accessor(:bar)
Foo.bar = 42

Foo.bar #=> 42

Foo.bar = 23

Foo.bar #=> 23

Or, you could use ActiveSupport, which already provides these methods.

However, since most style guides discourage usage of class hierarchy variables, there is not much point in having accessors for them.



Related Topics



Leave a reply



Submit