How to define a Class method like MyObject[:test]= 'test' - ruby

This is what I want do create, but it doesnt work:
class MyObject
def self.[]=(key, value)
##internal_hash[key] = value
end
end
I don't understand why overriding the self dot bracket doesnt work.

You need to initialize the class variable.
class MyObject
##internal_hash = {}
def self.[]=(key, value)
##internal_hash[key] = value
end
end
This is for two reasons.
Instance variables can be used without initialization, but class variables cannot.Programming Ruby:Class Variables and Class Methods:Class Variables:1st paragraph
Even if a class variable was able to be used without initialization, it would be implicitly initialized to nil, and you cannot suddenly use the Hash#[] method.

Related

Declaring an instance variable outside of `initialize` method

I have been taught to declare my instance variables with def initialize. I have been under the impression that I could declare instance variables only within my initialize methods.
Nevertheless, I declared an instance variable #foo outside my initialize method, and made it work as I intended:
class FooBar
def initialize(bar)
#bar = bar
end
def foo_as_instance_var
#foo = #bar.split(' ')
#foo
end
end
x = "something wicked this way comes"
y = FooBar.new(x)
puts y.foo_as_instance_var
Why am I able to declare an instance variable outside of initialize method? Since I can declare instance variables in any method, is there a best practices rule I should follow, regarding where to declare instance variables (i.e., declare them within initialize) or does it not matter?
I have been taught to declare my instance variables with def initialize
Since initialize is the first instance method call in an object's life cycle, you typically declare your instance variables right there in order to ensure properly initialized variables. It's also the first place I'd expect instance variables to be defined when reading code.
I have been under the impression that I could declare instance variables only within my initialize methods.
There's no such restriction. You can declare instance variable anywhere within your instance.
A common use is memoization:
class FooBar
def foo
#foo ||= expensive_operation
end
end
On the first call, this would evaluate expensive_operation and assign the result to #foo. On subsequent calls, #foo is returned.
Another popular example is Rails which uses instance variables to pass data from the controller to its view:
class FooController < ApplicationController
def index
#foos = Foo.all
end
end
is there a best practices rule I should follow, regarding where to declare instance variables
It depends on their purpose (see above examples). As a general rule, declare them in a way that avoids undefined variables (nil errors) and structure your code so it is easy to read / follow.
Just to add to Stefan's excellent answer
I have been taught to declare my instance variables with def initialize
A common mistake that ruby newbies make is something like this:
class Person
#name = "John"
def introduce
puts "Hi, my name is #{#name}"
end
end
And then they wonder why their names are not printed. To make this work, one can set the variable #name in the initializer, just as the instruction says.
Lets start with the biggest misnomer - in Ruby there is no separate step of declaring variables - Variables are declared as you set them.
What the difference? Look at Java for example:
public class Bicycle {
private int cadence;
private int gear;
private int speed;
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
}
We have to declare all the instance variables before we set them in the initializer (Bicycle). The same code in Ruby reads:
class Bicycle
def initialize(cadence, speed, gear)
#cadence = cadence
#speed = speed
#gear = gear
end
end
There is no declaration - only assignment. Ruby will even let you access instance variables which have not been set without error.
irb(main):003:0> #not_set
=> nil
You can't do that (generally) in languages where variables must be defined*.
I have been taught to declare my instance variables with def
initialize. I have been under the impression that I could declare
instance variables only within my initialize methods.
Nonsense. You can assign instance variables anywhere. Its commonly done in everything from setters and mutators (methods that alter an object) to factory methods (class methods that return an instance) or anywhere that you are altering the state of an object.
class Book
def initialize(title, author)
#title = title
self.author = author # calls the setter.
end
# A factory method
def create_from_csv(filename)
# ...
end
# A very contrived setter
def author=(author)
#author = "#{author.forename.upcase}. #{author.surname}"
end
# a mutator
def out_of_print!
#out_of_print = true
#last_printed = Date.today
end
end
However the initialize method is where you should handle initializing your objects (duuh) and is thus the obvious place to set initial values.

Saving instance list in class variable

I want to store newly created Person instance inside the class variable objects, but not sure how to reference the current instance from the constructor.
class Person
##objects = {}
def initialize(key)
##objects[key] = something
end
Ideally, the result is to be able to access the dictionary of Person objects through Person.objects
Simply, in constructor function, self will refer to the current instance.
class Person
##objects = {}
def initialize(key)
##objects[key] = self
puts self # it will print the id of the current instance
end
end
Same way, if you write self in a class method, it will refer to the the class.
But from your question, you seem to be doing something like Person.objects, and it won't work, and will output the following line:
NoMethodError: undefined method `objects' for Person:Class
So, you need to write a class method for it to let the outside world access objects.
def self.objects
##objects
end
Well, there are other ways as well to access the class variables, please have a look at this question.

Difference between creating a Class regularly and using Class.new

I can create a class using a constant name with class keyword:
class MyClass1; end
I can also create a class with Class.new and assign that to a constant or a variable:
MyClass2 = Class.new do; end
myClass3 = Class.new do; end
but I cannot create a class using class keyword with a name that begins in lowercase:
class myclass4; end # => Error
Is there a fundamental difference between these four? Isn't myclass3 a regular class?
The first method (class MyClass; end) is an explicit part of the language syntax (in that class is a keyword), and the class’s name must be a constant.
The second method (Class.new) is just a normal method call, and returns an anonymous instance of Class. This anonymous instance can then be treated like any other object.
Other than that, there are no differences between the two methods. Note that you can still assign the first type into a non-constant, but it must also first be assigned into a constant:
class MyClass; end
my_class = MyClass
Similar to Andrew Marshall's answer, but also different.
Think of it this way:
The class Foo ... syntax does more than defining a class; in addition to defining a class, it necessarily names it when called for the first time, and a class name must be a constant. Note that, in principle, a class can be nameless (see #3).
On the other hand, assignment can be done to a variable or a constant. There is no restriction to that.
A purer way to create a class is to use Class.new. Using this syntax, you do not have to name it. So it is okay to assign it to a variable:
foo = Class.new
# => #<Class:0x007f36b23159a8>
Only when it is assigned to a constant for the first time does it get named:
Foo = foo
# => Foo

What can't I access class variable from object in ruby?

I wan't to set a class variable of a class from the outside(via attr_accessor), and then access it from inside one of its objects. I'm using ruby 1.9.2. This is my code:
class Service
def initialize(id)
#my_id = id
end
class << self
attr_accessor :shared_id
end
def system_id
#my_id + ##shared_id
end
end
If I set Service.shared_id = "A2", and then call Service.new("A").system_id, this doesn't return "AA2". It displays the following error:
uninitialized class variable ##shared_id in Service
The behaviour is like if I didn't set the Service.service_id. Can someone please explain why this happens?
attr_accessor creates methods to manipulate instance variables — it does not create instance or class variables. To create a class variable, you must set it to something:
##shared_id = something
There's no helper method to generate accessor for class variables, so you have to write them yourself.
However, class variables, because of their weird lookup rules, are rarely used — avoided, even. Instead, instance variables at class-level are used.
class Service
#shared_id = thing
class << self
attr_accessor :shared_id
end
def system_id
# use self.class.shared_id; you could add a shared_id helper to generate it, too.
end
end
How about cattr_accessor?
Remember that ##class_var is global for all classes.

How to access instance variables from one class while inside another class

I'm really new to Ruby. And by new - less than 16 hours, but my boss gave me some Ruby code to add to. However, I found it was one giant file and not modular at all, so I decided to clean it up. Now that I've broken it up into several files/classes (generally speaking, 1 class per file,) I'm having problems piecing it together for it to work again. Originally everything was part of the same class, so the calls worked, but it looked ugly and it took an entire work day just to figure it out. I want to avoid that for the future as this code will grow much larger before it is done.
My main issue looks like the following (simplified, obviously):
class TestDevice
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(identifier, level, section, message)
...
#loghash[identifier] = { level => { section => message }}
...
end
end
device = TestDevice.new
After that, it calls out to other class methods, and those class methods reference back to the class Log for their logging needs. Of course, Log needs to access "device.loghash" somehow to log the information in that hash. But I can't figure out how to make that happen outside of passing the contents of "loghash" to every method, so that they, in turn, can pass it, and then return the value back to the origination point and then logging it at the end, but that seems really clumsy and awkward.
I'm hoping I am really just missing something.
To create accessors for instance variables the simple way, use attr_accessor.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
You can also manually define an accessor.
class TestDevice
def loghash
#loghash
end
def loghash=(val)
#loghash = val
end
end
This is effectively what attr_accessor does behind the scenes.
how about passing the device object as a parameter to the msg function? (I'm assuming that there can be many devices in your program, otherwise you can use singleton pattern).
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(device, identifier, level, section, message)
...
device.loghash[identifier] = { level => { section => message }}
...
end
end
So you need to learn the rules of ruby scoping.
Ruby variables have different scope, depending on their prefix:
$global_variables start with a $, and are available to everyone.
#instance_variables start with a single #, and are stored with the current value of self. If two
scopes share the same value of self (they're both instance methods, for example),
then both share the same instance variables
##class_variable start with ##, and are stored with the class. They're
shared between all instances of a class - and all instances of subclasses
of that class.
Constants start with a capital letter, and may be all caps. Like class
variables, they're stored with the current self.class, but they also
trickle up the hierarchy - so if you have a class defined in a module,
the instances of the class can access the module's constants as well.
Constants defined outside of a class have global scope.
Note that a constant variable means that which object is bound to the constant
won't change, not that the object itself won't change internal state.
local_variables start with a lowercase letter
You can read more about scope here.
Local variables scoping rules are mainly standard - they're available in
all subscopes of the one in which they are defined except when we move into
a module, class, or method definition. So if we look at your code from your
answer
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
device = TestDevice.new
class Somethingelse
def self.something
device.loghash='something here' # doesn't work
end
end
The scope of the device local variable defined at the toplevel does not include the Somethingelse.something
method definition. So the device local variable used in the Somethingelse.something method definition is a different (empty) variable. If you want the scoping to work that way, you should use a constant or a global variable.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
DEVICE = TestDevice.new
$has_logged = false
class Somethingelse
def self.something
DEVICE.loghash='something here'
$has_logged = true
end
end
p DEVICE.loghash # prints `{}`
p $has_logged # prints `false`
Somethingelse.something
p DEVICE.loghash # prints `"something here"`
p $has_logged # prints `true`

Resources