Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

understanding ruby class attributes, using accessor macros and self

Tags:

ruby

So I create a class in ruby:

class User
  def initialize
  end
end

Now say I want to create a attribute that is a hash with a getter/setter, I'm confused as to the options I have doing this.

If I do this:

class User
  attr_accessor :some_hash
end

But I don't want this hash to be nil ever, always an empty hash.

I'm confused when I should do:

def some_hash
  self.some_hash ||= {}
end

and do:

def some_hash
  @some_hash ||= {}
end

What is the difference?

If I don't use the attr_accessor, I have to create both the reader and writer (or getter/setter):

def some_hash=()
end

def some_hash
end

I'm hoping someone can clear up the options I would have for creating a some_hash attribute which is a hash, and that never returns a nil if it is empty.

i.e. using attr_accessor, manually creating the methods, and finally when to use @some_hash and self.some_hash

like image 914
Blankman Avatar asked Mar 21 '12 17:03

Blankman


2 Answers

attr_accessor :some_hash defines reader and writer methods for the given attribute. It is equivalent to this:

class User
  def some_hash
    @some_hash
  end

  def some_hash=(some_hash)
    @some_hash = some_hash
  end
end

@some_hash refers to an instance variable of the object, while some_hash and some_hash= are methods. The former returns the value of the variable, the latter sets it.

The idiom self.some_hash ||= {} is equivalent to self.some_hash || self.some_hash = {}.

Boolean operators in Ruby short circuit, which means the second expression (self.some_hash = {}) will not be executed at all if the first expression (self.some_hash) returns a true value.

The method:

def some_hash
  self.some_hash ||= {}
end

Is actually recursive, since it expands to some_hash || self.some_hash = {}. It will keep calling itself until you get a stack overflow. Use the second form:

def some_hash
  @some_hash ||= {}
end

Since it sets the instance variable directly, you will not have problems with recursion, nor will you have to call a writer method. some_hash can never return nil, because if @some_hash is nil, it will be assigned an empty hash before the method returns.

By the way, this is also called lazy initialization, because the variable is initialized when it is first accessed, not when the User instance is created.

like image 69
Matheus Moreira Avatar answered Oct 06 '22 01:10

Matheus Moreira


Apart from Matheus's answer, a little more about attributes:

Attributes are methods. They define how you do with some symbols. attr_reader defines the getter, attr_writer defines the setter and attr_accessor defines both. While attributes only take symbols, it does not "declare" any instance variables, as there is no such a thing in Ruby as declaring a variable. It is always declared and initialized when the first time used.

So after you say, attr_accessor :some_hash, the runtime will know the way to access a variable called some_hash, but this variable does not exist till you use it. You can try:

class User
  attr_accessor :some_hash
  attr_reader :some_other_hash
end
usr = User.new
p usr.some_hash

usr.some_hash = {}
p usr.some_hash

p usr.some_other_hash
usr.some_other_hash = {}

You will get nil,{},nil, and an error. The error is because only getter method is defined but no setter. If you want to define a setter yourself, you can still write a method called some_other_hash=()

My suggestion for instance variable is (which I and maybe many others use):

class User
  attr_accessor :some_hash

  def initialize
    @some_hash = {}
  end

end
usr = User.new
p usr.some_hash

This way does a "psudo" declaration when you create an object for the class, and the instance variable will also be initialized.

like image 41
SwiftMango Avatar answered Oct 06 '22 01:10

SwiftMango