WWW.APPSERVGRID.COM

Phorum about AppServers & Grid Technologies
It is currently Thu Dec 05, 2024 9:58 am

All times are UTC + 2 hours [ DST ]




Post new topic Reply to topic  [ 1 post ] 
Author Message
PostPosted: Mon Feb 11, 2019 3:04 am 
Offline
Site Admin

Joined: Tue Jan 25, 2011 8:51 pm
Posts: 49
Classes

You can think of classes as basic outlines of what an object should be made of and what it
should be able to do. (For example, an object of class Car should have a color, make and model, and be able to move).
A class in Ruby always starts with the keyword class followed by the name of the class.
The name should always be in initial capitals. You terminate the class definition with the keyword end.
For example:

class Person
end

The above code creates an empty class Person.
Now we need to define the variables and methods (also called data members) of the class.
These data members describe the attributes of the objects (for example, a person can be 42
years old and male - these attributes are data members of the class Person).
----------------------------------------------------------------------------------------------------------------
Classes

There is a special initialize method available for classes which gets called when an object is created.
It is defined inside a class just like any other class method:

class Person
def initialize
puts "Hi there"
end
end

The purpose of the initialize method is to initialize the class variables for a new object. (For example,
when a Car object is created, the initialize method could set the number of tires to the value 4)
The initialize method is known as the constructor in other object-oriented programming languages.
------------------------------------------------------------------------------------------------------------------
Objects

After the class and the initialize methods are defined, we can create objects of that class by using the
new method. The method new is predefined in the Ruby library and does not need to be added to your class definition.
Below is an example that creates two objects p1 and p2 of the class Person:

class Person
def initialize
puts "Hi there"
end
end

p1 = Person.new
p2 = Person.new

# Output:
# "Hi there"
# "Hi there"

Note the syntax for calling new: the class name followed by a dot and followed by the method name.
The code above outputs "Hi there" twice because we created two objects of the class, which
each call the initialize method. Now, p1 and p2 are separate objects of the class Person.

Objects are also called instances of a class. The process of creating objects of a class is called instantiation.
Note that in computing objects aren't always representative of physical items. For example,
a programming object can represent a date, a time, and a bank account.
---------------------------------------------------------------------------------------------------------------------
Instance Variables

An instance variable is one type of variable defined in a class.
Each object of a class has a separate copy of the instance variables.
Instance variables are preceded by the at sign (@) followed by the variable name (for example: @name)
We can, for example, pass a parameter to the initialize method and assign it to an instance variable for a new object:

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

In the code above, @name is an instance variable for the class Person.
Now, we can create objects of that class and pass an argument to the new method:

p1 = Person.new("David")
p2 = Person.new("Amy")

The object p1 now has an instance variable @name with the value "David" which relates only to the object p1.
Similarly, @name for the object p2 is equal to "Amy".
Each instance (object) of a class has its own unique instance variables that store values associated with that instance.
You might wonder why we don't use local variables instead of instance variables. We need instance variables because
their scope is the entire object, meaning that they are accessible inside all the methods for the object, opposed
to local variables, which are accessible only within the scope they are declared, such as a single method.
-----------------------------------------------------------------------------------------------------------------------
Instance Variables

A class can have multiple instance variables.
For example:
class Animal
@age = 0
def initialize(name, age)
@name = name
@age = age
end
end

ob = Animal.new("Jacky", 3)
-------------------------------------------------------------------------
Instance Methods

In the real world, objects behave in their own way. A car moves, a phone
rings, and so on.
The same applies to programming objects. Behavior is specific to the
object's type and is defined by methods in the class.
You can declare instance methods that are available to an object
of the class.
For example:
class Dog
def bark
puts "Woof!"
end
end

We defined a method called bark that outputs text.
Now we can instantiate an object and call the method using the dot syntax:
d = Dog.new
d.bark

# outputs "Woof"

As with any method, instance methods can include multiple parameters
and return values.
----------------------------------------------------------------------------
Accessors

An instance method can also be created to access the instance variables
from outside of the object.
For example, if we want to access the @name instance variable for a Person
object, we need a method that returns the value of that variable:

class Person
def initialize(name)
@name = name
end
def get_name
@name
end
end

p = Person.new("David")
puts p.get_name

# outputs "David"
Try It Yourself

We created an instance method get_name that returns the value of the @name
instance variable and then called it for our object p.
Getter and setter methods are called accessors.
The method that is used to retrieve the value of the variable is called
a getter method (get_name in our example).
The method that is used to modify the value of the variable is called a
setter method.
------------------------------------------------------------------------------
Setter Methods

Getter methods are used to access the instance variable. If we want to change
the value of the instance variables, we need setter methods.
Ruby provides a special syntax for defining setter methods: the method name
is followed by an equal sign (=).
For example:

class Person
def initialize(name)
@name = name
end
def get_name
@name
end
def set_name=(name)
@name = name
end
end

p = Person.new("David")
p.set_name = "Bob"
puts p.get_name

# outputs "Bob"

In the code above, set_name is a setter method that sets the value of the @name
instance variable to the value of its parameter name.
Notice the special syntax used when calling the method: p.set_name = "Bob".
Normally to call a method, you would use p.set_name=("Bob"), where the entire
set_name= is the method name, and the string "Bob" is the argument being passed
into the method.
However, for setter methods, Ruby allows us to use a more natural assignment
syntax: p.set_name = "Bob".
When you see this code, just realize there's a method called set_name= working
behind the scenes.
----------------------------------------------------------------------------------
Accessors

In Ruby it is a common practice to name the getter and setter methods using the
same name as the instance variable they are accessing.
The previous example can be rewritten as:
class Person
def initialize(name)
@name = name
end
def name
@name
end
def name=(name)
@name = name
end
end

p = Person.new("David")
p.name = "Bob"
puts p.name

# outputs "Bob"
-------------------------------------------------------------------------------------
Accessors

Imagine having a lot of instance variables and their setter and getter methods.
The code would be really long.
Ruby has a built-in way to automatically create these getter and setter methods
using the attr_accessor method.
The attr_accessor method takes a symbol of the instance variable name as an argument,
which it uses to create getter and setter methods.
We can do the following:
class Person

attr_accessor :name

def initialize(name)
@name = name
end
end

p = Person.new("David")
p.name = "Bob"
puts p.name

That one line replaced two accessor method definitions.
Ruby also provides the attr_reader and attr_writer methods in case only a getter
or setter method is needed for the instance variable.
We can pass multiple symbols to the attr_accessor, attr_reader and attr_writer
methods. For example: attr_accessor :name, :height, :weight
-------------------------------------------------------------------------------------
Accessors

The accessor methods can also be called inside the class by using the self keyword.
For example:
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def change(n, a)
self.name = n
self.age = a
end
def show_info
puts "#{self.name} is #{self.age}"
end
end

p = Person.new("David", 28)
p.change("Bob", 42)
p.show_info

# outputs "Bob is 42"

In the code above, we define a method called change that changes the instance
variables via their accessor methods. The show_info method outputs the values
of the instance variables.
self represents the current object and is used to call the instance methods
and accessors of the object.
One of the benefits of using self is for disambiguation. For example, if
you have a variable and a method both called name, self.name would make it
clear that you are referring to the method.
------------------------------------------------------------------------------------
Class Methods

Class methods are methods we can call directly on the class itself, without having
to instantiate any objects.
This can be useful when there is no logical need to create an object of the class,
such as when a class is used to group similar methods and functionality (like
mathematical operations).
One example is a Math class that includes a square method for returning the square
of its parameter. There is no logical need to create an object of the Math class
just to call the method. This is where class methods come into play.
Class methods are defined using the self keyword:

class Person
def self.info
puts "A Person"
end
end

Now, the method is a class method and can be called directly from the class,
without the need of an object:

Person.info
# outputs "A Person"

Remember, when used inside of instance methods, self is representing the current
instance (object) of that class.
When defining class methods, self is referring to the class itself, and not to
an instance of the class.
---------------------------------------------------------------------------------------
Class Variables

Class variables are accessible to every object of a class. A class variable belongs
to the class, not the objects.
You declare a class variable using two @ signs, for example @@name.
We can, for example, keep count of all Person objects created using a class variable:

class Person
@@count = 0
def initialize
@@count += 1
end
def self.get_count
@@count
end
end

p1 = Person.new
p2 = Person.new

puts Person.get_count
# outputs 2

In the code above, @@count is a class variable. Since the initialize method is called
for every object that is created, incrementing the @@count variable will keep track
of the number of objects created.
We also defined a class method called get_count to return the value of the class variable.
In the code above, we have created two objects of the Person class so the value of
the @@count variable is 2.
Class variables are usually used when you need information about the class, not the
individual objects.
--------------------------------------------------------------------------------------------
Class Constants

A class can also contain constants. Remember, constant variables do not change their
value and start with a capital letter. It is common to have uppercase names for constants, as in:

class Calc
PI = 3.14
end

You can access constants using the class name, followed by two colon symbols (::) and
the constant name, for example:

puts Calc::PI
# outputs 3.14
---------------------------------------------------------------------------------------------
The to_s Method

The to_s method comes built-in with all classes. It gets called when you output the object.
For example:
class Person
#some code
end
p = Person.new
puts p
# outputs "#<Person:0x0000000272e140>"

When we call puts p, Ruby automatically calls the to_s method for the object p, so puts
p is the same as puts p.to_s.
By default, the to_s method prints the object's class and an encoding of the object id.
-----------------------------------------------------------------------------------------------
The to_s Method

We can define our own to_s method for a class and add custom implementation to it.
For example, we can generate an informative, formatted output for our Person class:
class Person
def initialize(n, a)
@name = n
@age = a
end
def to_s
"#{@name} is #{@age} years old."
end
end
p = Person.new("David", 28)
puts p
# outputs: "David is 28 years old."

The to_s method also gets called when the object is used as a value in a string,
like "#{p}"
Defining the to_s method makes it easier and shorter to output the information
of an object in the format needed, as opposed to defining a custom method and
calling it from an object.
When you define the to_s method you call puts on your object (puts obj), where
with a custom method you have to explicitly call it from the object (puts obj.info).
-----------------------------------------------------------------------------------------------
Inheritance

Inheritance is when a class receives, or inherits, attributes and behavior from another class.
The class that is inheriting behavior is called the subclass (or derived class) and the class
it inherits from is called the superclass (or base class).
Imagine several classes, Cat, Dog, Rabbit and so on. Although they may differ in some ways
(only Dog might have the method bark), they are likely to be similar in others (all having color
and name).
This similarity can be expressed by making them all inherit from a superclass Animal, which
contains the shared functionality.
The < symbol is used to inherit a class from another class.
For example:

class Dog < Animal
#some code
end

In the code above, Dog is the subclass and Animal is the superclass.
-------------------------------------------------------------------------------------------------
Inheritance

Now, let’s define the Animal and Dog classes:
class Animal
def initialize(name, color)
@name = name
@color = color
end
def speak
puts "Hi"
end
end

class Dog < Animal
end

Dog is a subclass of Animal so it inherits Animal's methods and attributes,
making code like this possible:
d = Dog.new("Bob", "brown")
d.speak

# outputs "Hi"

Now Dog has all the methods and attributes of the Animal class, which is why we can instantiate
the object and call the speak method.
--------------------------------------------------------------------------------------------------
Inheritance

The subclass can also have its own methods and attributes. Let's define a Cat class
and inherit it from the same Animal class:

class Animal
def initialize(name, color)
@name = name
@color = color
end
def speak
puts "Hi"
end
end

class Dog < Animal
end

class Cat < Animal
attr_accessor :age
def speak
puts "Meow"
end
end

c = Cat.new("Lucy", "white")
c.age = 2
c.speak
# outputs "Meow"
Try It Yourself

In the code above, Cat inherits from Animal. It has an instance variable age and also defines
its own speak method. This is called method overriding, because the speak method in Cat
overrides, or replaces, the one in the Animal class.
When we called the speak method for our Dog object, its superclass method was called because
Dog did not override it.
The Cat object called its own speak method, because it defined its own implementation.
------------------------------------------------------------------------------------------------
Inheritance

Inheritance is a great way to remove duplication in your code by writing the shared and
common functionality in the superclass and then adding individual functionality in the subclasses.
You can have multiple levels of inheritance, for example:
class Animal
end
class Mammal < Animal
end
class Dog < Mammal
end

Here, Dog inherits from Mammal, which inherits from Animal.
This can be described as an "is a" relationship because a Dog is a Mammal, which is an Animal.
This is an example of single inheritance with multiple levels of hierarchy.
However, Ruby does not support multiple inheritance, meaning you cannot inherit a class
simultaneously from multiple classes. (A class cannot have multiple superclasses)
To achieve that behavior, Ruby supports mixins. We will learn about them in the next module.
---------------------------------------------------------------------------------------------------
super

Ruby has a built-in method called super, which is used to call methods from the superclass.
When you call super in a method of the subclass, the method of the same name gets called
from the superclass.
For example:

class Animal
def speak
puts "Hi"
end
end

class Cat < Animal
def speak
super
puts "Meow"
end
end

super calls the speak method of the Animal class.
Now, if we create an object of class Cat and call its speak method, we will get the following:

c = Cat.new
c.speak
# Outputs
# Hi
# Meow

The use of super allows us to remove duplicate code by using and extending the behavior
of the superclass in our subclasses.
-----------------------------------------------------------------------------------------------
super

super is more commonly used in the initialize method.
For example, our superclass has a initialize method that takes one argument and
initializes an instance variable:

class Animal
def initialize(name)
@name = name
end
end

Now, we need a subclass Cat that also has an @age instance variable, and we need
to define its own initialize method. Instead of repeating ourselves, and setting
the name instance variable in the Cat class, we can use its superclass with the super method
as follows:

class Cat < Animal
def initialize(name, age)
super(name)
@age = age
end
def to_s
"#{@name} is #{@age} years old."
end
end

We passed one of the arguments to the super method, which calls the initialize
method of the Animal class and sets the @name instance variable.
Now we can instantiate an object and output its info:

c = Cat.new("Bob", 3)
puts c

# outputs "Bob is 3 years old."

In the example we used super for a simple assignment. Imagine having a complex
program with complex calculations and operations being carried out. That's where
the real benefits of "not repeating yourself" come in, and calling the super where
applicable is one way of achieving it.
-----------------------------------------------------------------------------------------------------
Operator Overloading

Ruby allows us to overload operators so that we can perform operations such as adding two
objects together.
Let's say we have a class Shape, which has width and height properties. We want to be
able to add together two Shape objects and, as a result, get a new object that has
its width and height equal to the sum of the corresponding properties of the objects.
All we need to do is define the corresponding operator as a method:

class Shape
attr_accessor :h, :w
def initialize(h, w)
self.h = h
self.w = w
end

def +(other)
Shape.new(self.h+other.h, self.w+other.w)
end

end

a = Shape.new(7, 4)
b = Shape.new(9, 18)
c = a+b
puts c.h # outputs 16
puts c.w # outputs 22

As you can see, the + method takes one argument, which is another Shape object, and
returns a new Shape object with the corresponding values.
You can override almost all operators in Ruby and have any custom logic defined
in the corresponding method.
-----------------------------------------------------------------------------------------------------
Access Modifiers

Until now, all the methods that we defined in our classes were publicly available, meaning that
you could call those methods from outside of the class.
There are certain situations when methods should only be visible to the class. For example,
imagine a Banking class with methods to calculate values for internal transactions and
operations. If these methods were available outside the class, the reliability of the data
could be at risk.
To control visibility, Ruby provides the public, private, and protected access modifiers.
By default, all class methods (except initialize) are public, meaning that they are accessible
from both inside and outside of the class.
To make a method accessible only from inside the class, we can use the private access modifier:

class Person
def initialize(age)
@age = age
end
def show
puts "#{@age} years = #{days_lived} days"
end

private
def days_lived
@age * 365
end
end

p = Person.new(42)
p.show

In the code above, the method days_lived is private and is only accessible inside the class.
We called it from the show method, which is public.
If we try to call the days_lived method from an object (puts p.days_lived), we will get an error.
When the reserved word private is used in a program, anything below it in the class is private
(unless public or protected is placed after it to negate it).
Access modifiers can be applied only to methods. Instance variable are always private.
-------------------------------------------------------------------------------------------------------
Protected

An interesting thing to note about private Ruby methods is that they cannot be called with an
explicit receiver, even if that receiver is itself. When we say "receiver", we mean the
object that the method is being called from. Even if we try to call the private method
with self we will get an error.
This can be needed when, for example, overloading an operator to compare two objects
using a private method.
To demonstrate that, let's define a class Product with a private method id. If the ids
of two products are equal, then they are considered equal:

class Product
attr_accessor :name, :num
def initialize(name, num)
@name = name
@num = num
end
def ==(other)
self.id == other.id
end
private
def id
name.length*num
end
end

p1 = Product.new("PC", 5)
p2 = Product.new("Laptop", 3)
puts (p1 == p2)
# outputs "...Error: private method 'id' called..."

This code generates an error, because we tried to call the private method id on
self and the other object.
To be able to do that without making the method public, Ruby has the protected access control.
If we change the method from private to protected, the code will work:

class Product
attr_accessor :name, :num
def initialize(name, num)
@name = name
@num = num
end
def ==(other)
self.id == other.id
end
protected
def id
name.length*num
end
end

p1 = Product.new("PC", 5)
p2 = Product.new("Laptop", 3)
puts (p1 == p2)
# outputs false

So, protected methods are not accessible from outside code, just like private
methods, but can be called for an object of the same class or subclasses.
----------------------------------------------------------------------------------------------------------
A Simple Game

Object Oriented Programming is very useful when it comes to creating complex applications, such as games.
Let's create a simple fighting game, where two opponents will fight until one of them loses.
We start by creating our Player class:

class Player
attr_accessor :name, :health, :power
def initialize(n, h, pow)
@name = n
@health = h
@power = pow
end
def isAlive
@health > 0
end
def hit(opponent)
opponent.health -= self.power
end
def to_s
"#{name}: Health: #{health}, Power: #{power}"
end
end

The Player class has 3 instance variables, name, health and power, and 3 instance methods:
isAlive shows whether the player is still alive.
hit hits the opponent (decreases the opponent's health by the amount of player's power)
to_s outputs the player information.
We have also defined getter and setter accessors for the instance variables using the attr_accessor method.
-------------------------------------------------------------------------------------------------------------
A Simple Game

With the Player class defined, we can now define a method to make two opponents fight:
def fight(p1, p2)
while p1.isAlive && p2.isAlive
p1.hit(p2)
p2.hit(p1)
show_info(p1, p2)
end

if p1.isAlive
puts "#{p1.name} WON!"
elsif p2.isAlive
puts "#{p2.name} WON!"
else
puts "TIE!"
end
end

def show_info(*p)
p.each { |x| puts x}
end

The fight method uses a loop to make the players hit each other until the isAlive method
returns false for one of them. After each iteration, the information of both players
is output to the screen using the show_info method we defined.
Once the loop is over (meaning one of the opponents has lost), we check and output
the corresponding result.
--------------------------------------------------------------------------------------------------------------
A Simple Game

All that is left is to create two Player objects and call the fight method. To make the game
interesting, we can use random values for health and power of our players using the rand
method, which returns a random value in the range of 0 to its argument.
p1 = Player.new("Player 1", 1+rand(100), 1+rand(20))
p2 = Player.new("Player 2", 1+rand(100), 1+rand(20))

#show Player info
show_info(p1, p2)

puts "LETS FIGHT!"
fight(p1, p2)

We used 100 as the maximum value for health, and 20 as a maximum value for power. We add 1 to the
rand method to avoid the value 0.
Now, each time you run the program, two Players with random health and power will be created and
will fight!

The final code:

class Player
attr_accessor :name, :health, :power
def initialize(n, h, pow)
@name = n
@health = h
@power = pow
end
def isAlive
@health > 0
end
def hit(opponent)
opponent.health -= self.power
end
def to_s
"#{name}: Health: #{health}, Power: #{power}"
end
end

def fight(p1, p2)
while p1.isAlive && p2.isAlive
p1.hit(p2)
p2.hit(p1)
show_info(p1, p2)
end

if p1.isAlive
puts "#{p1.name} WON!"
elsif p2.isAlive
puts "#{p2.name} WON!"
else
puts "TIE!"
end
end

def show_info(*p)
p.each { |x| puts x}
end

#initialize Players
puts "PLAYERS INFO"
p1 = Player.new("Player 1", 1+rand(100), 1+rand(20))
p2 = Player.new("Player 2", 1+rand(100), 1+rand(20))

#show Player info
show_info(p1, p2)

puts "LETS FIGHT!"
fight(p1, p2)

This was just a simplified version.
You can easily create different subclasses of players, add more properties, define weapons, get user
input to make different decisions, and so on.
----------------------------------------------------------------------------------------------------------
Modules

Extracting common methods to a superclass, like we did in the previous section, is a great way to model
concepts that are naturally hierarchical (a Cat is an Animal which is a Mammal).
Another way of grouping methods together are modules.
A module is a collection of methods that can be used in other classes (think about them as libraries
providing common functionality).
Modules are defined using the module keyword followed by the module name, which should start with a capital letter.
For example:

module Flyable
def fly
puts "I'm flying!"
end
end

The code above declares a module called Flyable, which includes one method called "fly".
A module can include as many methods as you want.
As you can see the syntax is very similar to defining classes.
But why use modules instead of classes? Tap Continue to discover!
-------------------------------------------------------------------------------------------------------------
Modules

Now, imagine having a class hierarchy, with a superclass Vehicle and subclasses: Car, Jet, Plane.
All have some shared functionality, which they inherit from the Vehicle class, but only Jet and Plane can fly.
Instead of defining separate fly methods for both classes, we can define a module and include it
(commonly referred to as "mix" it) in the classes.

module Flyable
def fly
puts "I'm flying!"
end
end

class Vehicle
end

class Car < Vehicle
end

class Jet < Vehicle
include Flyable
end

class Plane < Vehicle
include Flyable
end

Now Jet and Plane objects can fly, but objects of other classes won't be able to:
ob = Jet.new
ob.fly

A common naming convention for Ruby is to use the "able" suffix on whatever verb describes the behavior
that the module is modeling, like Walkable, Swimmable, Runnable, etc.
Not all modules are named in this manner, however, it is quite common.
--------------------------------------------------------------------------------------------------------------
Modules

As you may recall, Ruby does not allow a class to inherit from multiple classes.
However, a class can mix in multiple modules. Modules used this way are known as "mixins".
So, basically, a class can have multiple mixins:

class Human
include Walkable
include Speakable
include Runnable
end

It is important to remember the following:
1. You can only inherit from one class. But you can mix in as many modules as you'd like.

2. If it's an "is-a" relationship, choose class inheritance. If it's a "has-a" relationship,
choose modules. Example: a plane "is a" vehicle; a plane "has an" ability to fly.

3. You cannot instantiate modules (i.e., an object cannot be created from a module).
Modules are used only for grouping common methods together.
Classes are about objects; modules are about methods.
Mixins give you a great, controlled way of adding functionality to classes.
---------------------------------------------------------------------------------------------------------------
Mixins

The true power of mixins comes out when the code in the mixin starts to interact with code in the class
that uses it. Ruby has a number of predefined mixins ready for you to use.
Let's take the standard Ruby mixin Comparable as an example. The Comparable mixin can be used to add
the comparison operators (<, <=, ==, >=, and >) to a class.
For this to work, Comparable assumes that any class that uses it defines the operator <=>.
So, as a class writer, you define the one method, <=>, include Comparable, and get six comparison
functions as a result!
Let's try this with our Cat class, by making the cats comparable based on their age.
All we have to do is include the Comparable module and implement the comparison operator <=>.

class Cat
attr_accessor :name, :age
include Comparable
def initialize(n, a)
self.name = n
self.age = a
end
def <=>(other)
self.age <=> other.age
end
end

c1 = Cat.new("Bob", 3)
c2 = Cat.new("Lucy", 7)

puts c1 < c2 # true
Try It Yourself

The Comparable module allows you to easily add comparison operators based on any custom logic to
your classes.
-------------------------------------------------------------------------------------------------------------
Namespacing

We've already seen how modules can be used to mix-in common behavior into classes. Now we'll see
two more uses for modules.
The first case we'll discuss is using modules for namespacing.
In this context, namespacing means organizing similar classes in a module. In other words,
we'll use modules to group related classes.
For example:

module Mammal
class Dog
def speak
puts "Woof!"
end
end

class Cat
def speak
puts "Meow"
end
end
end

We defined a module Mammal which groups together two classes, Dog and Cat.
Now we can call classes in the module by appending the class name to the module name with two colons(::):
a = Mammal::Dog.new
b = Mammal::Cat.new

a.speak # "Woof"
b.speak # "Meow"
Try It Yourself

The advantages of namespacing classes:
It becomes easy for us to recognize related classes in our code.
It reduces the likelihood of our classes colliding with other similarly named classes in our code. We
can have the same class names across different modules.
------------------------------------------------------------------------------------------------------------
Namespacing

Another use for modules is as containers for methods.
This allows us to group together relevant methods and use them in our code.
For example:

module MyMath
PI = 3.14
def self.square(x)
x*x
end
def self.negate(x)
-x
end
def self.factorial(x)
(1..x).inject(:*) || 1
end
end

puts MyMath.factorial(8)

The code above defines a module called MyMath, which includes a constant
called PI, and three class methods. The methods inside a module are defined
as class methods (note the self keyword), and we call them using the dot syntax.
You can call the methods using two colon syntax (::) as well (MyMath::factorial(8)),
but the dot syntax is preferred.
Again, the advantages of using modules to group methods includes preventing
name collisions, meaning that we can have the same method names across multiple modules.
---------------------------------------------------------------------------------------------------------
Structs

In some cases, there is no need for defining a fully structured class. Sometimes we need just a
group of attributes bundled together (for example, defining points in a 2D space using x and y coordinates).
We could, of course, create a separate class with all the instance variables and methods, but
Ruby provides a shortcut to bundle a number of attributes together called a Struct.

Point = Struct.new(:x, :y)

In the code above, Point is a Struct, having two attribute accessors: x and y. Struct automatically
creates its initialize method for the defined accessors, so now we can use Points just like a class,
instantiating different objects from it.

origin = Point.new(0, 0)
dest = Point.new(15, 42)
puts dest.y # 42

Struct is a built-in Ruby class and makes it shorter to define simple classes, accessors, and their
initialize methods.
------------------------------------------------------------------------------------------------------------
OStruct

OpenStruct (or OStruct) acts very similarly to Struct, except that it doesn't have a defined list
of attributes.
To use OStruct, we need to include the corresponding library using the require statement.
require "ostruct"

person = OpenStruct.new
person.name = "John"
person.age = 42
person.salary = 250

puts person.name # John

As you can see, we can define any number of attributes on the fly.
OStruct isn't as fast as Struct, but it is more flexible.
------------------------------------------------------------------------------------------------------------
OStruct

We can also initialize an OStruct using a hash.
For example:
require "ostruct"

person = OpenStruct.new(name:"John", age:42, salary:250)

puts person.name # John

Struct and OStruct provide a simple way to create data structures that have the behavior of a class.
-------------------------------------------------------------------------------------------------------------
Standard Classes

Ruby provides a number of standard built-in classes that make our life a lot easier by providing
useful methods for manipulating data.
Some of the classes in previous lessons include Array, String and Struct.
Another useful class is the Math class, which provides methods to perform mathematical operations.
For example:

# square root
puts Math.sqrt(9) # 3

# pi constant
puts Math::PI

# trigonometry (sin, cos, tan)
puts Math::cos(0) # 1
--------------------------------------------------------------------------------------------------------------
Time

The Time class represents dates and times in Ruby.
# current time
t = Time.now
puts t

# year, month, day
puts t.year
puts t.month
puts t.day

# custom date
t = Time.new(1988, 6, 10)

# day of week: 0 is Sunday
puts t.wday

# day of year
puts t.yday
----------------------------------------------------------------------------------------------------------------
Procs

Ruby provides the ability to take a block of code, wrap it up in an object (called a proc), store it in
a variable, and run the code in the block whenever you feel like (more than once, if you want).
For example:

greet = Proc.new do |x|
puts "Welcome #{x}"
end

We created a Proc that takes a parameter and outputs a greeting, and assigned it to the greet variable.
We run the code in the proc using the call method.

greet.call "David"
greet.call "Amy"

# Outputs
# "Welcome David"
# "Welcome Amy"

The code between the do and end keywords can include any number of operations.
--------------------------------------------------------------------------------------------------------------------
Procs

Procs are very similar to methods. They perform operations and can include parameters.
What make Procs really powerful and unique is the ability to pass them into methods, because procs
are actually objects.
For example:

greet = Proc.new do |x|
puts "Welcome #{x}"
end

goodbye = Proc.new do |x|
puts "Goodbye #{x}"
end

def say(arr, proc)
arr.each { |x| proc.call x}
end

We have defined two procs and a method, that takes an array and a proc as its parameters. For
each item in the array it calls the proc. Now, greet and goodbye are objects that contain the
corresponding blocks of code.
We can call the say method and pass our proc objects as parameters:

people = ["David", "Amy", "John"]
say(people, greet)
say(people, goodbye)

We can pass to our methods as many procs as we want.
Using procs gives the added flexibility to be able to reuse code blocks in more than one place
without having to type them out every time. Procs basically take blocks of code and embed
them in an object, allowing them to be reused and passed around.
--------------------------------------------------------------------------------------------------------------------
Procs

Let's create a program that counts the execution time of a block of code.
We will define a method that takes a proc as its parameter and counts the time it takes
to execute the proc.

def calc(proc)
start = Time.now
proc.call
dur = Time.now - start
end

Let's see it in action:

someProc = Proc.new do
num = 0
1000000.times do
num = num + 1
end
end

puts calc(someProc)

With that tiny calc method, we can now easily calculate the execution time of any Ruby code!
----------------------------------------------------------------------------------------------------------------------
Lambdas

Lambdas are a variation of Procs. A lambda is actually an instance of the Proc class.
To create a lambda in Ruby, you use the following syntax:
talk = lambda {puts "Hi"}

Alternatively, the following syntax can be used:
talk = ->() {puts "Hi"}

Just like with procs, you use the call method to execute the lambda:

talk.call

In other programming languages, a lambda is commonly referred to as an anonymous function.
-----------------------------------------------------------------------------------------------------------------------
Lambdas

Lambdas as very similar to procs. However, there are some important differences.
The first difference between procs and lambdas is how arguments are handled.
Lambdas check the number of arguments, while procs do not.
For example:
talk = lambda { |x| puts "Hello #{x}" }
talk_proc = Proc.new { |x| puts "Hello #{x}" }

talk.call "David"
# outputs "Hello David"

talk_proc.call "Amy"
# outputs "Hello Amy"
Try It Yourself

As you can see, the lambda and proc worked the same way, when the number of arguments is correct.
However, if we try to call them with incorrect number of arguments:
talk_proc.call
# ouputs Hello

talk.call
# outputs "Error: wrong number of arguments (given 0, expected 1)"
Try It Yourself

As you can see, when a lambda expects an argument, you need to pass those arguments
or an Error will occur. However, in the case of the Proc, if the argument is not
passed it automatically defaults to nil.
A second difference between a lambda and a Proc is how the return statement is handled.
When a lambda encounters a return statement it returns execution to the enclosing method.
However, when a Proc encounters a return statement it jumps out of itself, as well as
the enclosing method.
--------------------------------------------------------------------------------------------------------------------
Creating Files

Until now we have been performing input and output tasks using the gets and puts methods. A more permanent
form of input and output is files.
Ruby allows us to easily create and work with files by using the built-in File class.

file = File.new("test.txt", "w+")

The code above creates a file named "test.txt" with the mode w+ for read and write access to the file.
Note that the file will be created in the same directory as our program.
Now we can use our file object to perform different operations. After using a file, it must be closed
using the close method:

file.close

It is necessary to close open files so they no longer continue to occupy space in memory.
--------------------------------------------------------------------------------------------------------------------
File Modes

Ruby supports the following file modes:
r read-only, starts at beginning of file (default mode).
r+ read-write, starts at beginning of file.
w write-only, truncates existing file to zero length or creates a new file for writing.
w+ read-write, truncates existing file to zero length overwriting existing data or creates
a new file for reading and writing.
a write-only, appends to end of file if file exists, otherwise creates a new file for writing.
a+ read-write, appends or reads from end of file if file exists, otherwise creates a new file
for reading and writing.
When the open mode is read only, the mode cannot be changed to writable. Similarly, the open
mode cannot be changed from write only to readable.
---------------------------------------------------------------------------------------------------------------------
Opening Files

To open an existing file you use the File class open method:

file = File.open("filename", "w+")

As with the new method, the second argument of the open method specifies the mode.
--------------------------------------------------------------------------------------------------------------------
Writing Files

The puts and write methods can be used for writing content to a file.
The difference between the two is that puts adds a line break to the end of strings, while write does not.
For example:

file = File.new("test.txt", "w+")
file.puts("some text")
file.close

Remember to close the file after performing the file operations.
---------------------------------------------------------------------------------------------------------------------
Writing Files

If we want to write to an existing file, we can use a block of code to perform the write operation.
With this code Ruby will automatically close the file.
For example:

File.open("file.txt", "w+") {
|file| file.puts("some text")
}

This shorter way makes sure the file closes at the end of the block.
----------------------------------------------------------------------------------------------------------------------
Reading Files

To read the entire contents of a file the File.read method can be used.
For example:

f = File.new("test.txt", "w+")
f.puts("a line of text")
f.puts("another line of text")
f.close

puts File.read("test.txt")

# Outputs:
# a line of text
# another line of text
------------------------------------------------------------------------------------------------
Reading Files

We can also read the file contents line-by-line using the readlines method.
For example:
File.open("test.txt", "a+") {
|file| file.puts("a line of text")
file.puts("another line of text")
}

File.readlines("test.txt").each {
|line| puts " --- #{line}"
}

The readlines method reads the entire file based on individual lines and returns
those lines in an array.
------------------------------------------------------------------------------------------
Deleting Files

We can delete a file using the File.delete method.
For example:
File.delete("test.txt")

Be careful, as this command deletes the file permanently.
-----------------------------------------------------------------------------------------------
Working with Files

When working with files, it is important to first check if the file exists in
order to prevent an error.
The file? method provides an easy way of checking if the file exists.
For example:

File.open("test.txt") if File.file?("text.txt")

The code above tests whether a file exists before opening it.
File.file? returns true if the file exists, and false if it does not.
------------------------------------------------------------------------------------------
File Info

Ruby provides some useful methods to get relevant information about a file.
The size method returns the size of the file.
The zero? method returns true if the named file exists and has a zero size (is empty).
For example:

# create a file
f = File.new("test.txt", "w+")
f.puts("some file content")

puts f.size # 19

f.close

puts File.zero?("test.txt") # false
---------------------------------------------------------------------------------------------
File Info

We can also check whether the file is writable, readable or executable:
f = File.new("test.txt", "w+")
f.puts("some content")
f.close

puts File.readable?("test.txt") # true
puts File.writable?("test.txt") # true
puts File.executable?("test.txt") # false
------------------------------------------------------------------------------------------------


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 1 post ] 

All times are UTC + 2 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron