save instances in global variable - ruby

I need to save all instances of an object in a global variable, so I can access that instances from another object. There is no need to pass them like parameteres.
In my solution I have a mixin with a method that puts the instance in a variable, also I used an open class technique to include that mixin in Object, so other objects use that method (and not only ONE class).
class Object
include Favourite
end
module Favourite
def favourite_it
#if the variable its not initialized:
#favourites.class == Array.class ? #favourites.push(self) :
#favourites = [].push(self)
end
def get_favourites
#favourites
end
end
#this class is only an example
class Dog
def initialize age
#age = age
end
end
class Dog_T
#all instances of this class will be saved in the variable
def initialize age
#age = age
favourite_it
end
end
class Handler
def do_something
#here I need to access the variable with all the instances of favourites to do something to them
end
end
And here is a simple test
handler = Handler.new
d1 = Dog_T.new(10)
d2 = Dog_T.new(12)
all_f = Handler.get_favourites
expect(all_f[0].age).to eq 10
expect(all_f[1].age).to eq 12
d3 = Dog_T.new(15)
all_f = Handler.get_favourites
expect(all_f[3].age).to eq 15
I tried to do this, but only each instance save itself in a different list (it makes sense because I'm not using global variables yet).
How can I do to have only one list, add the instances when are created and have the ability to empty and manipulate that list from Handler?

Ruby supports using a class variable in a Module. You also need a reader method for the Dog_T object to be able to access the instance variable. Since Favourite doesn't know anything about the object, you will want to use respond_to? to guard against calling an non-existent method in the list. For example, if there were a Dog_R class that did not have a method age but did add itself, you would get a runtime error blindly calling the age method on members of the Favourite array.
module Favourite
##favourites = [] # you can use a class variable in module
def self.favourite_it(obj) # singleton method of the class
##favourites.push(obj)
end
def self.get_favourites # singleton method of the class, see below for usage example
##favourites
end
end
class Object
include Favourite
end
class Dog
def initialize age
#age = age
end
end
class Dog_T
attr_reader :age # you need a reader to able to access it
def initialize age
#age = age
Favourite.favourite_it(self)
end
end
d1 = Dog_T.new(10)
d2 = Dog_T.new(12)
all_f = Favourite.get_favourites
all_f.each do |obj|
puts "#{obj.class}: #{obj.age if obj.respond_to?(:age)}"
end
puts '-' * 20
d3 = Dog_T.new(15)
all_f = Favourite.get_favourites
all_f.each do |obj|
puts "#{obj.class}: #{obj.age if obj.respond_to?(:age)}"
end
The output of this program is:
Dog_T: 10
Dog_T: 12
--------------------
Dog_T: 10
Dog_T: 12
Dog_T: 15

Related

Ruby - How to access instance variables from classes with "self" methods?

Sorry that I have no clue how to title this, I'm having a hard time looking this up because I don't know how to say this. Anyway...
Let's say I have a class that looks like this for example:
class Run
def self.starting
print "starting..."
end
def self.finished
print "Finished!"
end
end
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting. Now let's say that I wanted to add some instance variables...
class Run
attr_accessor :starting, :finished
def self.starting
print "starting..."
#starting = true
#finished = false
end
def self.finished
print "finished!"
#starting = false
#finished = true
end
end
What if I wanted to access those instance variables from outside the class? I know that something like print "#{Run.finished}" or print "#{Run.starting}" won't do anything. Can I do that without run = Run.new? Or should I just remove self and then use run = Run.new? (Sorry if this question is a mess.)
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting
There's much more to it than this. In your case you're calling class methods. If you did runner = Runner.new - then you'd be calling instance methods (those are defined without self.
In general, if you need "the thing" to hold some kind of state (like #running = true) then you'd rather want to instantiate an object, and call those methods.
Now, #whatever are instance variables, and you don't have the access to them in class methods.
class Run
attr_reader :running
def start
#running = true
end
def stop
#running = false
end
end
runner = Run.new
runner.running # nil
runner.start
runner.running # true
runner.stop
runner.running # false
I'd recommend you doing some tutorial or basic level book on rails programming, find a chapter about objects and classes. Do some exercises.
In Ruby instance variables are just lexical variables scoped to an instance of a class. Since they are scoped to the instance they always act like a private variable.
If you want to provide access to an instance variable from the outside you create setter and getter methods. Thats what attr_accessor does.
class Person
attr_accessor :name
def initialize(name:)
#name = name
end
def hello
"Hello my name is #{#name}"
end
end
john = Person.new(name: 'John')
john.name = "John Smith"
puts john.hello # "Hello my name is John Smith"
puts john.name # "John Smith"
Methods defined with def self.foo are class methods which are also referred to as singleton methods. You can't access variables belonging to an instance from inside a class method since the recipient when calling the method is the class itself and not an instance of the class.
Ruby also has class variables which are shared by a class and its subclasses:
class Person
##count = 0
def initialize
self.class.count += 1
end
def self.count
##count
end
def self.count=(value)
##count = value
end
end
class Student < Person
end
Person.new
Student.new
puts Person.count # 2 - wtf!
And class instance variables that are not shared with subclasses:
class Person
#count = 0 # sets an instance variable in the eigenclass
def initialize
self.class.count += 1
end
def self.count
#count
end
def self.count=(value)
#count = value
end
end
class Student < Person
#count = 0 # sets its own class instance variable
end
Person.new
Student.new
puts Person.count # 1
Class variables are not used as often and usually hold references to things like database connections or configuration which is shared by all instances of a class.
You can't access instance variables from outside the instance. That is the whole point of instance variables.
The only thing you can access from outside the instance are (public) methods.
However, you can create a public method that returns the instance variable. Such a method is called an attribute reader in Ruby, other languages may call it a getter. In Ruby, an attribute reader is typically named the same as the instance variable, but in your case that is not possible since there are already methods with the names starting and finished. Therefore, we have to find some other names for the attribute readers:
class Run
def self.starting?
#starting
end
def self.finished?
#finished
end
end
Since this is a common operation, there are helper methods which generate those methods for you, for example Module#attr_reader. However, they also assume that the name of the attribute reader method is the same as the name of the instance variable, so if you were to use this helper method, it would overwrite the methods you have already written!
class << Run
attr_reader :starting, :finished
end
When you do this, you will get warnings (you always have warning turned on when developing, do you?) telling you that you have overwritten your existing methods:
run.rb:19: warning: method redefined; discarding old starting
run.rb:2: warning: previous definition of starting was here
run.rb:19: warning: method redefined; discarding old finished
run.rb:5: warning: previous definition of finished was here

Accessing Instance Variable Directly in Ruby?

Why can't I access an instance variable directly in ruby without using an accessor method or instance_variable_get?
class Foo
#my_var
end
Why shouldn't we be able to use Foo.#my_var in this example?
The example provided by the OP is a class instance variable. These can only be accessed by class methods.
"regular" attribute accessors won't allow access from outside the class. Here are a couple of ways to create accessors that work:
class A
#class_instance_var = "foo"
class << self
attr_accessor :class_instance_var
end
end
puts A::class_instance_var # Output: foo
OR
class A
#class_instance_var = "foo"
def self.class_instance_var
#class_instance_var
end
end
puts A::class_instance_var # Output: foo
Class instance variables
Class instance variable names also begin with #. However, they are defined at class level, outside any methods. Class instance variables can only be accessed by class methods. They are shared amongst all instances of a class but not its subclasses. In other words, they are not inheritable. If the value of a class instance variable is changed in one instance of the class, all other instances are affected. Earlier we saw how all classes are instances of a built-in class called Class. That is what makes class instance variables possible.
class Vehicle
#count = 0 # This is a class instance variable
def initialize
self.class.increment_count
self.class.show_count
end
def self.increment_count # This is a class method
#count += 1
end
def self.show_count # This is a class method
puts #count
end
end
class Car < Vehicle
#count = 0
end
v1 = Vehicle.new # Output: 1
v2 = Vehicle.new # Output: 2
v3 = Vehicle.new # Output: 3
car1 = Car.new # Output: 1
car2 = Car.new # Output: 2
v3 = Vehicle.new # Output: 4
Let's review the example above. A class instance variable called #count is set in the Vehicle class, with an initial value of 0. Every time the Vehicle class is instantiated, the initialize method calls self.increment_count to increment the value of #count and self.show_count to return the new value. Then, we have the Car class, which is a subclass of Vehicle and inherits all of its methods. However, it does not inherit the #count class instance variable, as this type of variable is not inheritable. That's why the counter works within the Car class, but it has its own count.
Methods prefixed with self., such as self.increment_count and self.show_count, are class methods. That is the only kind of method capable of accessing class instance variables.
That's just not how the language is built. Maybe look at openstruct
require 'ostruct'
obj = OpenStruct.new(my_var: 1)
obj.my_var
# => 1
By the way, you're method of setting up an instance variable is not correct. You should only be setting instance variables inside instance methods or initialize, otherwise use class variables or constants.
An example with constants:
class Foo
MyVar = 1
end
Foo::MyVar
# => 1
You could also make Foo.new.#my_var work with method_missing:
class Foo
def method_missing(m, *args, &block)
self.instance_variable_get(m)
end
def initialize
#my_var = 1
end
end
Foo.new.#my_var
# => 1

How to read instance variables set by accessors inside the same class?

I have a curiosity regarding which is the preferred way of accessing instance variables inside a class that has defined an accessor for that instance variable.
One way would be by referencing the instance variable directly:
class Example
attr_accessor :attribute
def meth
puts #attribute
end
end
The other way would be by calling the reader created by the accessor:
class Example
attr_accessor :attribute
def meth
puts attribute
end
end
It's a small difference, but I am curious which is the preferred approach and why. The only advantage I see on readers vs. direct instance variable access is that it is easier to stub a reader inside a test.
It's best to go through the accessors. For instance, if you access the instance variable directly, and then you later convert the value with the reader, then accessing the instance variable directly won't see that change.
Using the accessors rather than accessing the instance variables directly causes one small quirk when you want to use the accessor to set the value of the instance variable.
Normally when you write:
some_meth 10
...ruby will interpret that as:
self.some_meth(10)
But if you write:
age = 10
...ruby will not interpret that as:
self.age=(10)
Instead, ruby will create a local variable named age and set it to 10, which has no affect on an instance variable named #age.
In order to call the setter for #age, you have to explicitly write self:
self.age = 10
Here is a complete example:
class Dog
attr_reader :age
def age=(val)
#age = val * 7
end
def initialize val
self.age = val #age = val will not call the setter
end
end
d = Dog.new 10
puts d.age #=> 70

How should I define an instance variable in Ruby whose processing algorithm is defined in a super class?

In short: I want to define the algorithm in the superclass which inherits into all subclasses, but I want to define the data (on which the algorithm operates) in the subclasses as instance variables, which come into being when I call the "new" method of the given classes. What is the standard way for doing this in Ruby?
My solution is (but I am not exactly sure this is the right way):
class A
attr_accessor :var
def initialize
#var=nil #I dont know the actual value, it will be defined only in the more specific subclasses.
end
def process_data
puts #var #simply puts it out
end
end
#in my program all further classes are inherited form class A, the processing facility is inherited, only the data varies.
class B < A
attr_accessor :var
def initialize
#var=10 #specific value for class B which is always 10, no need for example b=B.new(20)
end
end
class C < A
attr_accessor :var
def initialize
#var=20 #specific value for class C which is always 20, no need for example c=C.new(20)
end
end
b=B.new
b.process_data #needs to print 10
c=C.new
c.process_data #needs to print 20
What you have works. There's just some unneeded stuff in there:
Instance variables evaluate to nil if they are uninitialized and they spring into existence as soon as they are used, so your A#initialize method is unnecessary.
You override the A#var and A#var= methods in B and C with methods that do the exact same thing. There is no need for that, just get rid of the calls to attr_accessor in the definition of B and C.
You create var and var= accessor methods but you never use them. Either get rid of the call to attr_accessor or (preferably) use the accessor methods, i.e. use self.var = in initialize and puts var in process_data.
class A
attr_accessor :var
def process_data
puts var #simply puts it out
end
end
#in my program all further classes are inherited form class A, the processing facility is inherited, only the data varies.
class B < A
def initialize
self.var = 10 #specific value for class B which is always 10, no need for example b=B.new(20)
end
end
class C < A
def initialize
self.var = 20 #specific value for class C which is always 20, no need for example c=C.new(20)
end
end
b = B.new
b.process_data #needs to print 10
c = C.new
c.process_data #needs to print 20
[Note: your coding style was also off. Indentation is 2 spaces in Ruby, not 3.]
However, if the value of #var is always the same for all instances of B, then why do you need multiple instances of B?
Why not something like this:
class A
attr_accessor :var
def initialize(val)
self.var = val
end
def process_data
puts var #simply puts it out
end
end
b = A.new(10)
b.process_data #needs to print 10
c = A.new(20)
c.process_data #needs to print 20
You are doing it in almost the right way. A minor point is that your A::initialize definition is redundant for two reasons:
An instance variable is initialized to nil automatically.
The initialize methods in the subclasses will override A::initialize when the subclass instances are created.
Also, the attr_accessor calls in the subclasses are redundant.
Now I seem to get what you wanted. You can use class-level instance variables.
class A
def initialize
#var=self.class.instance_variable_get(:#var)
end
def process_data
puts #var
end
end
class B < A
#var = 10
end
class C < A
#var = 20
end
B.new.process_data # => 10
C.new.process_data # => 20

Ruby newbie, what is the difference between #var and ##var in a class

as the title says,
what is the difference between #var and ##var in a class definition?
Also, what is the difference between self.mymethod and mymethod in defining a method?
##var is a class variable, it is shared between class and all instances of this class. You can access this variable from class methods and from instance methods.
class C
##a = 1
def self.m1 # define class method (this is similar to def C.m1, because self will evaluate to C in this context)
##a
end
def m2 # define instance method
##a
end
end
C.m1 # => 1
C.new.m2 # => 1
#var is a class instance variable. Normally you can get access to this instance variable from the class methods.
class C
#a = 1
def self.m1
#a
end
def m2
# no direct access to #a because in this context #a will refer to regular instance variable, not instance variable of an object that represent class C
end
end
C.m1 # => 1
These variables might be confusing and you should always know the context where you define instance variable #... - it might be defined in the instance of an object that represent a class or might be an instance of regular object.
self always refers to the current object.Check the following Eg:-
class Test
def test
puts "At the instance level, self is #{self}"
end
def self.test
puts "At the class level, self is #{self}"
end
end
Test.test
#=> At the class level, self is Test
Test.new.test
#=> At the instance level, self is #<Test:0x28190>
object variables are so named because they have scope within, and are associated
to, the current object.an object variable, is then accessible from any other method inside that object.
Class variables are particularly useful for storing information relevant to all objects
of a certain class.
In intuitive terms, instance vars are used to keep track of the state of each object. On the other hand, class variables are used to keep track of the state of all instances of the class. E.g. you might use ##count to keep track of the number of this class' objects that have been instantiated, like so:
class User
##count = 0
attr_reader :name
def initialize(name)
##count += 1
#name = name
end
end
User.count gives you the number of users that have been instantiated so far.
user = User.new('Peter') increases User.count by one and user.name returns Peter.

Resources