Ruby Classes: Initialize Self VS. @Variable

Ruby classes: initialize self vs. @variable

In general, no, self.stuff = stuff and @stuff = stuff are different. The former makes a method call to stuff= on the object, whereas the latter directly sets an instance variable. The former invokes a method which may be public (unless specifically declared private in the class), whereas the latter is always setting a private instance variable.

Usually, they look the same because it is common to define attr_accessor :stuff on classes. attr_accessor is roughly equivalent to the following:

def stuff
@stuff
end

def stuff=(s)
@stuff = s
end

So in that case, they are functionally identical. However, it is possible to define the public interface to allow for different results and side-effects, which would make those two "assignments" clearly different:

def stuff
@stuff_called += 1 # Keeps track of how often this is called, a side effect
return @stuff
end

def stuff=(s)
if s.nil? # Validation, or other side effect. This is not triggered when setting the instance variable directly
raise "Argument should not be nil"
end
@stuff = s
end

ruby should I use self. or @

When you use @lines, you are accessing the instance variable itself. self.lines actually goes through the lines method of the class; likewise, self.lines = x goes through the lines= method. So use @ when you want to access the variable directly, and self. when you want to access via the method.

To directly answer your question, normally you want to set the instance variables directly in your initialize method, but it depends on your use-case.

Instance variable: self vs @

Writing @age directly accesses the instance variable @age. Writing self.age tells the object to send itself the message age, which will usually return the instance variable @age — but could do any number of other things depending on how the age method is implemented in a given subclass. For example, you might have a MiddleAgedSocialite class that always reports its age 10 years younger than it actually is. Or more practically, a PersistentPerson class might lazily read that data from a persistent store, cache all its persistent data in a hash.

Initializing class instance variables in Ruby

Short answer: instance variables don't get inherited by subclasses

Longer answer: the problem is that you wrote @bar = [] in the body of the class (outside any method). When you set an instance variable, it is stored on whatever is currently self. When you're in a class body, self is the class object Foo. So, in your example, @foo gets defined on the class object Foo.

Later, when you try to look up an instance variable, Ruby looks in whatever is currently self. When you call add_bar from Baz, self is Baz. Also self is STILL Baz in the body of add_bar (even though that method is in Foo). So, Ruby looks for @bar in Baz and can't find it (because you defined it in Foo).

Here's an example that might make this clearer

class Foo
@bar = "I'm defined on the class object Foo. self is #{self}"

def self.get_bar
puts "In the class method. self is #{self}"
@bar
end

def get_bar
puts "In the instance method. self is #{self} (can't see @bar!)"
@bar
end
end

>> Foo.get_bar
In the class method. self is Foo
=> "I'm defined on the class object Foo. self is Foo"

>> Foo.new.get_bar
In the instance method. self is #<Foo:0x1056eaea0> (can't see @bar!)
=> nil

This is admittedly a bit confusing, and a common stumbling point for people new to Ruby, so don't feel bad. This concept finally clicked for me when I read the 'Metaprogramming' chapter in Programming Ruby (aka "The Pickaxe").

How I'd solve your problem: Look at Rails' class_attribute method. It allows for the sort of thing you're trying to do (defining an attribute on a parent class that can get inherited (and overidden) in its subclasses).

Understanding the access of a variable assigned in initialize in Ruby

For most method calls on self, self.method_name is equivalent to just method_name. That's not the case for methods whose name ends with an =, though.

The first thing to note, then, is that self.blogs = etc doesn't call a method named blogs and then somehow 'assign etc to it'; that line calls the method blogs=, and passes etc to it as an argument.

The reason you can't shorten that to just blogs = etc, like you can with other method calls, is because blogs = etc is indistinguishable from creating a new local variable named blogs.

When, on the previous line, you see a bare blogs, that is also a method call, and could just as easily have been written self.blogs. Writing it with an implicit receiver is just shorter. Of course, blogs is also potentially ambiguous as the use of a local variable, but in this case the parser can tell it's not, since there's no local variable named blogs assigned previously in the method (and if there had been, a bare blogs would have the value of that local variable, and self.blogs would be necessary if you had meant the method call).

As for using @blogs = instead of self.blogs =, in this case it would have the same effect, but there is a subtle difference: if you later redefine the blogs= method to have additional effects (say, writing a message to a log), the call to self.blogs = will pick up those changes, whereas the bare direct access will not. In the extreme case, if you redefine blogs= to store the value in a database rather than an instance variable, @blogs = won't even be similar anymore (though obviously that sort of major change in infrastructure will probably have knock-on effects internal to the class regardless).

Ruby class instance variable vs. class variable

Instance variable on a class:

class Parent
@things = []
def self.things
@things
end
def things
self.class.things
end
end

class Child < Parent
@things = []
end

Parent.things << :car
Child.things << :doll
mom = Parent.new
dad = Parent.new

p Parent.things #=> [:car]
p Child.things #=> [:doll]
p mom.things #=> [:car]
p dad.things #=> [:car]

Class variable:

class Parent
@@things = []
def self.things
@@things
end
def things
@@things
end
end

class Child < Parent
end

Parent.things << :car
Child.things << :doll

p Parent.things #=> [:car,:doll]
p Child.things #=> [:car,:doll]

mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new

[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]

With an instance variable on a class (not on an instance of that class) you can store something common to that class without having sub-classes automatically also get them (and vice-versa). With class variables, you have the convenience of not having to write self.class from an instance object, and (when desirable) you also get automatic sharing throughout the class hierarchy.


Merging these together into a single example that also covers instance variables on instances:

class Parent
@@family_things = [] # Shared between class and subclasses
@shared_things = [] # Specific to this class

def self.family_things
@@family_things
end
def self.shared_things
@shared_things
end

attr_accessor :my_things
def initialize
@my_things = [] # Just for me
end
def family_things
self.class.family_things
end
def shared_things
self.class.shared_things
end
end

class Child < Parent
@shared_things = []
end

And then in action:

mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new

Parent.family_things << :house
papa.family_things << :vacuum
mama.shared_things << :car
papa.shared_things << :blender
papa.my_things << :quadcopter
joey.my_things << :bike
suzy.my_things << :doll
joey.shared_things << :puzzle
suzy.shared_things << :blocks

p Parent.family_things #=> [:house, :vacuum]
p Child.family_things #=> [:house, :vacuum]
p papa.family_things #=> [:house, :vacuum]
p mama.family_things #=> [:house, :vacuum]
p joey.family_things #=> [:house, :vacuum]
p suzy.family_things #=> [:house, :vacuum]

p Parent.shared_things #=> [:car, :blender]
p papa.shared_things #=> [:car, :blender]
p mama.shared_things #=> [:car, :blender]
p Child.shared_things #=> [:puzzle, :blocks]
p joey.shared_things #=> [:puzzle, :blocks]
p suzy.shared_things #=> [:puzzle, :blocks]

p papa.my_things #=> [:quadcopter]
p mama.my_things #=> []
p joey.my_things #=> [:bike]
p suzy.my_things #=> [:doll]

In Ruby when you initialize a class do you set an instance variable equal to a variable?

We do that so that newly-created object (not a class! with initialize and @vars, you initialize the object that was just created with new() method!) remembers the value of number.

Try using this one:

def initialize(number)
end

This gets a number, but does nothing with it. When this inializer ends, the object created will not remember what was the 'number'.

Here:

def initialize(number)
@foo = 5
@bar = number
end

the newly-created object will remember a 5 in @foo and the number in @bar.

The idea to name the @variable just like the parameter is just to make it easier. In the example above, it's hard to guess what the bar is about. Instead, if I rename the @bar into @number, it wil be obvious that it holds .. the number.

def initialize(number)                         def initialize(number)
@bar = number <-same thing-> @number = number
end just different name end

Initializing class instance variables of subclasses from the superclass

I want all subclasses to have a variable on their singleton class / eigenclass.

Sorry, that is not what you are doing here:

puts SomeSuperClass.variable # => 'This only works for the superclass'
puts SubClass.variable # => '

Why would you think that writing

SomeSuperClass.variable 

is equivalent to the pseudo code:

SomeSuperClassSingletonClass.variable

or the real code:

SomeSuperClass.singleton_class.variable

A class and it's singleton class are two different Classes.

In addition, this code:

  class << self
attr_accessor :variable
@variable = ': )' # This does't seem to have any effect
end

does not create an accessor for that @variable, the same way that this code:

class Dog
attr_accessor :x

@x = 'hello'
end

puts Dog.x

...does not create an accessor for that @x variable:

--output:--
undefined method `x' for Dog:Class (NoMethodError)

What attr_accessor() does is this:

class Dog
def x
@x
end

def x=(val)
@x = val
end

#=====

@x = 'hello'
end

Those methods have nothing to do with the class instance variable @x, which was defined outside all the defs. @variables are looked up (or set) on whatever object is self at that instant. The only objects that can call those defs are instances of class Dog, and therefore x will be looked up (or set) on a Dog instance--not the Dog class.

Also note that when the line @x = 'hello' executes, self is equal to the Dog class, therefore @x attaches itself to the Dog class.

I don't think you have a use case for setting an instance variable on a singleton class. Here is what it seems like you are trying to do:

class SomeSuperClass
class << self
attr_accessor :variable
end

self.variable = 'hello'

def self.inherited(subclass)
subclass.singleton_class.instance_eval do
attr_accessor :variable
end

subclass.variable = "Hi"
end

end

class SubClass < SomeSuperClass
end

puts SomeSuperClass.variable
puts SubClass.variable

--output:--
hello
Hi

That code creates what are known as class instance variables. If you think you have a use case for singleton class instance variables, let's hear it.



Related Topics



Leave a reply



Submit