In general, object is the a structure with its own properties (it can carry the data which represents and describes the object) and methods (functions or procedures associated with the object). For example, you may want to have object
Server, represents the machine in the network you manage. In this case, this object is a model of reality: it represents the physical box standing deep in the vault of your server room. Such object may contain data fields like
domain and methods like
get_ip (the function returning IP of the machine, based on name and domain). Notice there is no need to pass the server name and domain to
get_ip method, because the method itself has an access to the object properties.
Object must have the defnition - it is called the class. You may think about the class as a type of the object. In Ruby class is a contant, so must begin with uppercase letter and, by convention, it is good to use CamelCase for class names.
class Server def get_ip 'unknown IP' end end
The class contains only one method. Note the
get_ip is a mock method for now, it returns constant string, not the proper IP address.
In Ruby, there is no need to define class in one piece. You can extend definition of a class wherever you want, just remember that new method definition overwrites the previous one.
class Server def get_ip 'unknown IP' end def name 'unknown server name' end end class Server def domain 'unknown domain' end def name 'unknown name' end end
This code defines class Server with three methods:
name will return 'unknown name' string, as this version was defined later and overwrites the definition from lines 5 - 7.
If Server is something like a type, can we create a variable with this type? First, we need to allocate the memory for the object and create an object there. This representation in the memory is called the instance of the object, and the process of constructing it by language interpreter is called object creation. To create a new, empty instance and assign it to variable we can use the class method called
server = Server.new()
Class Definitions in Irb
For practice and debug, you can write a script, run it and observe the result. But you can use Irb as well - it is possible to define class or methods directly there:
class Server def get_ip 'unknown ip' end end #=> nil server = Server.new #=> #<Server:0x007fc6bc1d4978> server.get_ip #=> "unknown ip"
irb is changing the continuation level number in a prompt (the last number), to help you remember you are inside a definition of class or a method.
Another way to debug a script is to load it to Irb. To do it, use
load command - this will run the given file, so every definition of classes, methods, variables will be accessible from Irb session.
load 'server_object.rb' #=> true server = Server.new #=> #<Server:0x007fb76bb0a548> server.get_ip #=> "unknown IP"
When you check the value of
server variable in Irb, it will show human readable representation of the instance, such like
#<Server:0x007fb76bb0a548>. This is the object's class name with the instance identifier (hexadecimal representation of the memory address).
For now, we defined the
Server object with only few methods: it is not storing any data in it. The purpose of the object is to represent (to model) the real world. Real servers have some properties like the name and the domain. Such information should be stored in the instance variable, so every instance of Server class can have its own name properties. Instance variables syntax is the same as normal, local variables: lowercase, snake_case symbol but followed by character
@. In the example below there are two new methods to set server name and server address:
class Server def set_name(name) @name = name end def set_domain(domain) @domain = domain end def get_name @name end def get_domain @domain end end server = Server.new server.set_name 'vader' server.set_domain '.starwars.com' puts server.get_name + server.get_domain # prints 'vader.starwars.com'
Methods to set and get instance variables are called setters and getters.
Try to construct an object with instance properties in Irb. When you inspect it, you will see the representation of the object changed. You will get now something like
#<Server:0x007fc688e2ebd0 @name="vader", @domain=".starwars.com"> - it is showing the instance variable content for better readability.
Setters and getters like above are not very elegant. It would be much better to just have a direct access to instance variable, to set its value with
server.name = 'vader' and get it with simple
server.name. In Ruby there is no direct access to the instance variables, but why not to write the setters and getters in more readable format:
class Server def name=(name) @name = name end def domain=(domain) @domain = domain end def name @name end def domain @domain end end server = Server.new server.name = 'obi' server.domain = '.starwars.com' puts server.name + server.domain # prints 'obi.starwars.com'
It looks like a direct access to object properties, but it is not - when calling
object.some_method = ... Ruby invokes method called
some_method=, the one with equal sign at the end. For getter, it is much easier: the method name is just the same as the instance variable name.
attr_accessor, attr_writter and attr_reader
In a big projects writing getters and setters for every instance variable in the way like above might be quite boring. Why don't let Ruby do it for us? There are special functions for creating setters and getters for the given variable names:
attr_writter creates the setter,
attr_reader produces a getter and
attr_accessor - both setter and getter.
class Server attr_reader :name # this two lines could be replaced by one attr_writer 'name' # -- attr_accessor :name attr_accessor :domain # you may replace the three lines above but just one, # passing all method names separated by commas # attr_accessor :name, :domain end server = Server.new server.name = 'yoda' server.domain = '.starwars.com' puts server.name + server.domain # prints 'yoda.starwars.com'
attr_* methods are taking the list of variable names as the arguments. You can give this name as a string (like 'name' in the example), but much better is to use Ruby symbols. Symbols are the kind of strings, written with colon at the beggining (
:name in the example), but unlike the strings they are not mutable - you can't change it. They are commonly used to pass the method names, variable named etc.
We now know how to create a new instance with
new class method. It is possible to control the object creation code: if Ruby found a method called
initialize it lauches it while creating an object. In the example below the instance variables are set to default values while constructing an object:
class Server attr_accessor :name, :domain def initialize @name = 'default' @domain = '.domain.com' end end server = Server.new server.name = 'yoda' # changing the value of @name puts server.name + server.domain # prints 'yoda.domain.com'
You can pass the arguments to
new class method - Ruby will pass them to the
initialize instance method. This is commonly use for passing the initialization values for the class variables. Notice that the number of arguments in
new must equal the number of arguments in
class Server attr_reader :name, :domain def initialize(name, domain) @name = name @domain = domain end end server = Server.new 'kirk', '.startrek.com' puts server.name + server.domain # prints 'kirk.startrek.com'
Class Methods and Variables
So far we have created the instance methods, which are functions running on object instance, having an access to all its properties - but only in the scope of the current object. Class methods are different - they are running on the class, not on the object instance, and they do not need any instance initialized. You know one of the class method - the object constructor,
new. It is invoked on the class, like
Server.new. This method must be a class method - before the first initialization with
new there is no instance to run the function on. To define class method, use
self. syntax before method name.
Class variables are like the instance variables, but with the scope on the class, not on the specific object. That means all of the objects of specific class have an access to this values - it is shared between them. Class variables are defined with double 'at' symbol
@@ before the variable name.
Class methods are commonly used for object initialization, besides
Object.new there are many of this kind, for example
File.open(filename) which creates an object representing the file with the given path. Class variables might be useful when you need to do something on all the object of a kind, for example to count all defined servers:
class Server @@server_count = 0 # initial value for class method attr_accessor :name, :domain def initialize(name, domain) @name = name @domain = domain @@server_count += 1 # increment the class method end def self.count # definition of class method @@server_count # returns the value of @@server_count class variable end end puts Server.count # prints '0' yoda = Server.new 'yoda', '.starwars.com' kirk = Server.new 'kirk', '.startrek.com' puts Server.count # prints '2'
self is a keyword to represent a current object instance. You can use it in the class definition to access the instance itself. In the example below, method
url calls the method
full_name on the same object.
class Server def initialize(name, domain) @name = name @domain = domain end def full_name # returns name and domain of the object @name + @domain end def url # returns full name followed by 'http://' 'http://' + self.full_name end end yoda = Server.new 'yoda', '.starwars.com' puts yoda.full_name # prints 'yoda.starwars.com' puts yoda.url # prints 'http://yoda.starwars.com' puts Server.new('kirk', '.startrek.com').url # prints 'http://kirk.startrek.com'
Just to call the method in the example above you may skip the
self keyword. Ruby is searching for a method in the current object instance first, so this will work as well:
def url 'http://' + full_name end
Inheritance is a very important concept in the object-oriented programming. When the class (we will call it subclass) inherits another class (called superclass or ancestor), it is getting the whole structure of the parent class, all the code: variables, methods etc. This provides the class hierarchy, where most general classes are at the top, and the detailed ones at the bottom of hierarchy tree.
The syntax for inheritance in Ruby is
class SubClassName < SuperClassName.
Assume we want to have two new classes, adding more details to our servers model. We can create two classes,
UnixServer, which we want to behave the same as a general Server class:
class Server # Server is a superclass for both classes below def initialize(name, domain) @name = name @domain = domain end def full_name @name + @domain end end class WindowsServer < Server # WindowsServer inherits from Server end class UnixServer < Server # UnixServer inherits from Server as well def execute_via_ssh # to be implemented in a future 'not implemented yet' end end win = WindowsServer.new('yoda', '.starwars.com') unix = UnixServer.new('kirk', '.startrek.com') puts unix.full_name # prints 'kirk.startrek.com' puts win.full_name # prints 'yoda.starwars.com' puts unix.class # prints 'UnixServer' puts win.class # prints 'WindowsServer' puts unix.execute_via_ssh # prints 'not implemented yet' puts win.execute_via_ssh # raises exception (an error): "undefined method `execute_via_ssh'"
Lines 1 - 9 provides us the well know implementation of the
Server object. Then comes the definition of
WindowsServer class comes, which is a subclass of the
Server. That means this class now have the same implementation like its superclass - it has the same methods and variables like the
Server. So we can create it with arguments (line 20) just like the
Server, run the method from the superclass (line 24) - all of this code
WindowsServer inherits from the ancestor.
The similar is for
UnixServer class - it has all the methods and variables inherited from
Server. In addition, this class have one more instance method -
execute_via_ssh (now it is not doing nothing, except the nasty information). You can call this method, but only on
UnixServer object! It is not available in objects of
WindowsServer or even
The method from the superclass can be overwritten in the subclass. For example, you may want to change behaviour of the
full_name method - in case it is Windows server, you want to be the server name only, without a domain, upper-case.
class WindowsServer < Server def full_name @name.upcase end end
upcase is an instance method of String object to change all the characters of the string to uppercase.
Ruby searches for a method first in the current object and only when not found the interpreter start searching in the superclasses. So after the changes above the method gives the desired server name for
irb(main):025:0> win = WindowsServer.new('yoda', '.starwars.com') #=>#<WindowsServer:0x007fb83a0c7318 @name="yoda", @domain=".starwars.com"> irb(main):027:0> win.full_name #=>"YODA"
Inheritance (and similar techniques, like mixins) is really important in Ruby. Remember the method
new to create a new instance? It magically appears in the newly defined
Server class. Well, this particular method comes from
Class class. Refer to following documentation http://ruby-doc.org/core-2.0.0/Class.html to learn more.
In Ruby, most operators are actually the method calls on the objects. Thus
a + b means run the method called
+ on object
a with argument
b - this is another way to write
a.+ b. You can try it in Irb, instead of typing
2 + 2 you can calculate this by directly calling the method
+ on object instance
2 with the argument of object instance
2.+(2). It works!
Sometimes the operator does not makes sense for the specified kind of object. We can add numbers, strings (to concatenate), but we cannot add servers one to each other. Thats why we need to find out another operator to play with the examples. Lets discuss the equality operator
==. Can we check if two servers are equal? Yes, if the servers have the same name and domain, they must be the same machines. By default, we have the
== defined for every object, but it does not work as we expect:
server1 = Server.new('yoda','.starwars.com') #=> #<Server:0x007fdf22830af0 @name="yoda", @domain=".starwars.com"> server2 = Server.new('yoda','.starwars.com') #=> #<Server:0x007fdf219f34d8 @name="yoda", @domain=".starwars.com"> server1 == server2 #=> false
This is because we defined two object instances and, from the Ruby point of view, these are a different constructs. Just observe the identificator (the memory address) of
server2. To fix this behaviour it is time to override the equality operator:
class Server attr_accessor :name, :domain def initialize(name, domain) @name = name @domain = domain end def ==(other) name == other.name && domain == other.domain # && is a logical AND operator # it is true if both arguments are true end end s1 = Server.new('yoda', '.starwars.com') s2 = Server.new('yoda', '.starwars.com') s3 = Server.new('kirk', '.starwars.com') puts s1 == s2 # true ! puts s2 == s3 # false, different names
Line 7 defines operator as an instance method on object
Server. It is the only one line of code (line 8), which means: "return true if my name equals the other name AND if my domain equals the other domain". In this line operator
== checks equality of String objects (@name and @domain are the character strings). To better understanding, we can write this line in different way:
(self.name == other.name) && (self.domain == other.domain) or with if-then-else statement:
if name == other.name && domain == other.domain return true else return false end
But because the method always returns the value of the last statement, there is no need to bother with if-then-else. Simplicity first!
By default objects in
irb are shown by the class name, the instance identifier (the unique identifier for all objects) and the instance variables, if any. This is quite handy, but we can do better. To show this value,
irb runs method
inspect on the specified instance. If you define your own, you can output better human-readable description of the object. In this case, we want our Server object to be shown as
* [class name]: [server and domain] *
server = Server.new('yoda', '.starwars.com') #=> #<Server:0x007ff231124fa0 @name="yoda", @domain=".starwars.com"> # default inspect value class Server def inspect # our own inspect "* " + self.class.to_s + ": " + @name + @domain + " *" # * [class name]: [server and domain] * end end server #=> * Server: yoda.starwars.com * # works as expected!
Hacking the Math
At the end of this long and boring chapter, let's have some fun! Who said that "2 + 2" must always give "4"? Well, by default, Ruby claims it is "4":
2 + 2 #=> 4
But as we said, everything in Ruby is an object and have its class. Number 2 is an instance of class
2.class #=> Fixnum
So why not to redefine the
+ operator, which is in fact the instance method on
class Fixnum def +(other) 42 end end
Voila! What is the result of "2 + 2" now?
2 + 2 #=> 42