What Is Attr_Accessor in Ruby

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.

usage of attr_accessor in Rails

attr_accessor can be used for values you don't want to store in the database directly and that will only exist for the life of the object (e.g. passwords).

attr_reader can be used as one of several alternatives to doing something like this:

def instance_value
"my value"
end

Attr_accessor on class variables

attr_accessor defines accessor methods for an instance. If you want class level auto-generated accessors you could use it on the metaclass

class Parent
@things = []

class << self
attr_accessor :things
end
end

Parent.things #=> []
Parent.things << :car
Parent.things #=> [:car]

but note that this creates a class level instance variable not a class variable. This is likely what you want anyway, as class variables behave differently than you might expect when dealing w/ inheritance. See "Class and Instance Variables In Ruby".

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.

ruby about attr_accessor, instance variables, local varibles

First, read this answer, which is the most-upvoted Ruby post in StackOverflow history. It will help you understand attr_accessor and its cousins attr_reader and attr_writer.

Besides that, your code has many problems.

First, you should not name an Array with a singular variable name like item. Use a plural items to make its purpose clear.

Second, the name item2 is not good. For your attribute, use something descriptive like counter, and for the variable passed as an argument to initialize it, let's use something descriptive like initial_count.

Third, your increment method takes an optional argument but then ignores it. Wouldn't it be surprising if someone called box.increment(2) and the attribute was incremented by only 1? The intent of this method is to use counter += n instead of counter += 1.

Fourth, to set counter from within the class, we need to use self. So instead of counter += n, we have to do self.counter += n.

Finally, consider whether you want the attributes to be readable and writable from an outside source, or whether you want to reserve write privileges to the object itself. Because you have methods to add things to items and to increment counter, you probably want to conceal write privileges. I would use attr_reader publicly and attr_writer privately.

Incorporating these suggestions, here's the resulting code:

class Box
attr_reader :counter, :items

def initialize(initial_count)
@counter = initial_count
@items = []
end

def add(product)
items << product
end

def empty?
items.empty?
end

def increment(n = 1)
self.counter += n
end

private

attr_writer :counter, :items
end

Now you can do this, all of which makes sense, more or less:

>> cart = Box.new(123)
>> cart.increment(2)
>> cart.counter
#> 125
>> cart.add('A product')
>> cart.add('Another product')
>> cart.items
#> ["A product", "Another product"]

But if you try to set counter or items directly, you'll get an error:

>> cart.counter = 1
#> NoMethodError: private method `counter=' called for #<Box:0x007fc13e17dc50>

Difference between attr_accessor and attr_accessible

attr_accessor is a Ruby method that makes a getter and a setter. attr_accessible is a Rails method that allows you to pass in values to a mass assignment: new(attrs) or update_attributes(attrs).

Here's a mass assignment:

Order.new({ :type => 'Corn', :quantity => 6 })

You can imagine that the order might also have a discount code, say :price_off. If you don't tag :price_off as attr_accessible you stop malicious code from being able to do like so:

Order.new({ :type => 'Corn', :quantity => 6, :price_off => 30 })

Even if your form doesn't have a field for :price_off, if it's in your model it's available by default. This means a crafted POST could still set it. Using attr_accessible white lists those things that can be mass assigned.

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


Related Topics



Leave a reply



Submit