Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby-2.7.5
3.1.2
61 changes: 61 additions & 0 deletions RB129/test/classes_and_objects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Classes and objects

Classes are templates from which objects are created. Classes define what states and behavior objects can have:
- States track information for individual objects and are implemented in Ruby via instance variables.
- Behaviors are what objects are capable of doing and are implemented in Ruby via instance methods.

Objects are instances of a class. The process of creating a new object or instance from a class is called instantiation. Individual objects of the same class may contain different state, but they all share the same behavior.

Classes are defined in Ruby using the `class` keyword followed by a name. The name must begin with a capital letter and by convention we use the CamelCase naming convention. The class definition is terminated by the `end` keyword.

Objects are instantiated in Ruby by calling `#new` on a class. Whenever a new object of a class is created using the `#new` method, Ruby looks for a method named `#initialize` and, if found, it is called on the newly created object with the arguments that were passed to the `#new` method. We refer to the `#initialize` method as a constructor, because it is a special method that builds the object when a new object is instantiated.

```ruby
class Greeter
def greet
puts "Hello! What is your name?"
end
end

greeter1 = Greeter.new
greeter1.greet

greeter2 = Greeter.new
greeter2.greet
```

The above code defines a `Greeter` class with one method `#greet` that outputs the string `"Hello! What is your name?"` and returns `nil`.

Next, an instance of the class `Greeter` is created by calling `Greeter.new` with no arguments and assigned to the local variable `greeter1`. The method `#greet` is called on the `greeter1` which outputs the string `"Hello! What is your name?"` and returns `nil`.

Next, another instance of the class `Greeter` is created by calling `Greeter.new` with no arguments and assigned to the local variable `greeter2`. The method `#greet` is called on the `greeter2` which outputs the string `"Hello! What is your name?"` and returns `nil`.

Since the class `Greeter` does not define any instance variables, the objects it can create cannot hold different state from one another.

```ruby
class Greeter
def initialize(name)
@name = name
end

def greet
puts "Hello! My name is #{@name}. What is your name?"
end
end

greeter1 = Greeter.new('John')
greeter1.greet

greeter2 = Greeter.new('Doe')
greeter2.greet
```

The above code defines a `Greeter` class with two methods:
- The `#initialize` method, which is always called during the initialization process, is defined with one required parameter `name`. The value of `name` is assigned to the instance variable `@name`.
- The `#greet` method that outputs the string `"Hello! My name is #{@name}. What is your name?"`, where `#{@name}` will be replaced by the value returned by `@name.to_s`. `#greet` returns `nil`.

Next, an instance of the class `Greeter` is created by calling `Greeter.new` with the argument `"John"` and assigned to the local variable `greeter1`. The method `#greet` is called on the `greeter1` which outputs the string `"Hello! My name is John. What is your name?"` and returns `nil`.

Next, another instance of the class `Greeter` is created by calling `Greeter.new` with the argument `"Doe"` and assigned to the local variable `greeter2`. The method `#greet` is called on the `greeter2` which outputs the string `"Hello! My name is Doe. What is your name?"` and returns `nil`.

Since the class `Greeter` defines an instance variable `@name`, the objects it can create can hold different state from one another: different objects may have different names.
45 changes: 45 additions & 0 deletions RB129/test/how_to_call_getter_and_setter_methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# How to call setters and getters

Setter and getter methods are methods that change or expose the state of an object.

```ruby
class Dog
attr_accessor :breed

def reset_breed
self.breed = nil
end
end

dog = Dog.new
dog.breed = 'Dalmatian'
dog.reset_breed
dog.breed
```

In the code above, a `Dog` class is defined with three methods:
- The `#breed=` setter method and the `#breed` getter method are defined automatically by calling `::attr_accessor` with the argument `:breed`.
- The `#reset_breed` method calls the setter method `#breed=` on `self` with the argument `nil`, assigning `nil` to the instance variable `@breed`.

Next, an instance of the class `Dog` is created by calling `Greeter.new` with no arguments and assigned to the local variable `dog`. Then, the method `#breed=` is called on the object referenced by `dog` which the argument `"Dalmatian"` which makes the instance variable `@breed` to point to the argument. Then, the method `#reset_breed` is called on the object referenced by `dog` which assigns `nil` to the instance variable `@breed` and returns `nil`. Then, the method `#breed` is called on the object referenced by `dog` which returns the value of the instance variable `@breed`: `nil`.

When it comes to calling a setter method inside a class definition, because Ruby allows us to use the assignment syntax when calling a setter method: `dog.breed = 'Dalmatian'` instead of `dog.breed=('Dalmatian')`, it is needed to use `self` to disimbiguate from creating a local variable.

If instead we define the class as follows:

```ruby
class Dog
attr_accessor :breed

def reset_breed
breed = nil
end
end

dog = Dog.new
dog.breed = 'Dalmatian'
dog.reset_breed
dog.breed
```

We will find that the last line `dog.breed` returns the string `"Dalmatian"`, instead of `nil`. This is because in the body of the `#reset_breed` method, instead of a call to the `#breed=` method, a local variable assignment is taking place `breed = nil`.
138 changes: 138 additions & 0 deletions RB129/test/instance_class_and_constant_variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Instance variables, class variables, and constants, including the scope of each type and how inheritance can affect that scope

## Instance variables

Instance variables are defined starting with a `@` symbol like so: `@instance_variable`. They are used to track individual object state, and do not cross over between objects.

For example, in the below code, the instance variable `@name` has a different value assigned when referenced from each object.

```ruby
class Person
attr_reader :name

def initialize(n)
@name = n
end
end

bob = Person.new('bob')
joe = Person.new('joe')

bob.name # => "bob"
joe.name # => "joe"
```

Instance variables are scoped at the object level, meaning they can be referenced from any instance method. In the example above, the instance variable `@name` is accessible in the instance method `#name` even though it was initialized inside the method `#initialize` and was not passed in as argument.

If you try to reference an uninitialized local variable, you'd get a a `NameError` exception exception. But if you try to reference an uninitialized instance variable, you get `nil`.

```ruby
class Person
attr_accessor :name
end

bob = Person.new
bob.name # => nil
```

## Class variables

Class variables are defined starting with two `@` symbols like so: `@@class_variable`. They are used to track class state, and do cross over between objects.

For example, in the below code, the class variable `@@number_of_persons` has the same value assigned when referenced from the class and each object.

```ruby
class Person
@@number_of_persons = 0

def initialize(n)
@number_of_persons += 1
end

def number_of_persons_excluding_me
@@number_of_persons - 1
end

def self.number_of_persons
@@number_of_persons
end
end

Person.number_of_persons # => 0

person1 = Person.new

Person.number_of_persons # => 1
person1.number_of_persons_excluding_me # => 0

person2 = Person.new

Person.number_of_persons # => 2
person2.number_of_persons_excluding_me # => 1
```

Class variables are scoped at the class level, meaning they can be referenced from any class method or instance method. All objects from a class share the same the class variables. In the example above, the class variable `@@number_of_persons` is accessible in the instance methods `#initialize` and `#number_of_persons_excluding_me` even though it was initialized in the `Person` class definition and was not passed in as argument.

Similarly to instance variables, if you try to reference an uninitialized class variable, you get `nil` instead of a a `NameError` exception exception.

```ruby
class Person
def self.number_of_persons
@@number_of_persons
end
end

Person.number_of_persons # => nil
```

## Constants

Constants are defined starting with an upper case letter like so: `CONSTANT`. While technically constants just need to begin with a capital letter, the convention is to make the entire variable uppercase. They are used to track state that is not supposed to change. While technically constants can be reassigned and the object assigned to them can be mutated, best practices dictate not doing so. Ruby will throw a warning when reassigning a new value to constants but will not complain at all when mutating objects assigned to constants.

For example, in the below code, the constant `DOG_YEARS` has the same value assigned when referenced from the class and each object.

```ruby
class Dog
DOG_YEARS = 7

attr_accessor :age

def initialize(age)
self.age = age
end

def dog_years
age * DOG_YEARS
end

def self.dog_years
DOG_YEARS
end
end

dog1 = Dog.new(4)
dog2 = Dog.new(2)

Dog.dog_years # => 7
dog1.age # => 4
dog1.age_in_dog_years # => 28
dog2.age # => 2
dog2.age_in_dog_years # => 14
Dog::DOG_YEARS # => 7
```

Constants have lexical scope, meaning that where the constant is defined in the source code determines where it is available. For a more in depth explanation of how constant variable scope works, [read this](../../RB120/lesson_3/constant_variable_scope.md). Unlike class or instance variables, constants initialized inside a module or a class (namespaced) can be reached from outside of the module or class using `::`, which is the namespace resolution operator.

Unlike class and instance variables, if you try to reference an uninitialized constant, you get a `NameError` exception.

```ruby
SOME_CONSTANT_THAT_DOES_NOT_EXIST # => NameError
```

Constants cannot be initialized inside a method (unless using metaprogramming), you get a `SyntaxError` exception.

```ruby
def define_constant
PI = 3.14
end # => SyntaxError (dynamic constant assignment)
```
63 changes: 63 additions & 0 deletions RB129/test/instance_methods_vs_class_methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Instance methods vs. class methods

## Instance methods

Instance methods are available to objects (or instances) of that class. Instance methods are defined inside the class body. They can be referenced from other instance methods and have access to both instance and class variables.

```ruby
class Person
def initialize(name)
@name = name
end

def short_greet
'Hello!'
end

def long_greet
"#{short_greet} My name is #{@name}. What is your name?"
end
end

person = Person.new('John Doe')
person.short_greet # => "Hello!"
person.long_greet # => "Hello! My name is John Doe. What is your name?"
```

In the code above, a `Person` class is defined with three methods:
- The `#initialize` instance method which assigns the value assigned to the `name` parameter to the instance variable `@name`.
- The `#short_greet` instance method which returns a string with value `"Hello!"`.
- The `#long_greet` instance method which references the `#short_greet` instance method and the instance variabel `@name` in its body and returns a string with the value `"#{short_greet} My name is #{name}. What is your name?"`, where `#{short_greet}` will be replaced by the return value of the method `#short_greet` and `#{name}` will be replaced by the value assigned to instance variable `@name`.

## Class methods

Class methods are available to classes. Class methods are defined inside the class body by prepending the `self` keyword. They can be referenced from other class methods and have access to class variables.

```ruby
class Person
@@number_of_persons = 0

def initialize
@@number_of_persons += 1
end

def self.number_of_persons
@@number_of_persons
end

def self.next_number_of_persons
number_of_persons + 1
end
end

Person.number_of_persons # => 0
Person.next_number_of_persons # => 1
person = Person.new
Person.number_of_persons # => 1
Person.next_number_of_persons # => 2
```

In the code above, a `Person` class is defined with three methods:
- The `#initialize` instance method which increases the value assigned to the class variable `@@number_of_persons` by one.
- The `::number_of_persons` class method which returns the value assigned to the class variable `@@number_of_persons`.
- The `::next_number_of_persons` class method which references the class method `::number_of_persons` in its body and returns the return value of the class method `::number_of_persons` plus one.
Loading