[Basics] 4 Classes

Constants in Ruby...

Classes


    Classes are constructed of variables, constants and methods which provide programmatic functionality for varying purposes. A class is the template for an object. What this means is that objects are instances of a class. Classes in ruby have a feature called inheritance. Inheritance allows for a class to copy it's template to another class as a starting ground. In turn this new class can add new functionalities to the copied template. By default all classes of objects are derived from BasicObject. BasicObject class is a template class for Object. All ruby objects are descendants of the Object class. Lets put this concept to work. Lets define a simple class called Movie.

#Defining the movie class is as simple as...
class Movie
end

    That is it! We now have a Movie class. We have not yet added any variables, constants or methods to this class, HOWEVER it already has plenty. I will call a function called methods which returns the methods of a class as an array.

#Calling the methods function on the Movie class.
>> Movie.methods
=> [:yaml_tag_subclasses?, :allocate, :new, :superclass, :to_yaml, :freeze, :, :, :=>, :, :, :, :, :to_s, :included_modules, :include?, :name, :ancestors, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :const_missing, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :module_exec, :class_exec, :module_eval, :class_eval, :method_defined?, :public_method_defined?, :private_method_defined?, :protected_method_defined?, :public_class_method, :private_class_method, :autoload, :autoload?, :instance_method, :public_instance_method, :syck_yaml_as, :yaml_as, :yaml_tag_class_name, :yaml_tag_read_class, :pretty_print_cycle, :pretty_print, :taguri, :taguri, :to_yaml_style, :to_yaml_properties, :syck_to_yaml, :po, :poc, :pretty_print_instance_variables, :pretty_print_inspect, :nil?, :, :!, :eql?, :hash, :class, :singleton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :respond_to_missing?, :extend, :display, :method, :public_method, :define_singleton_method, :__id__, :object_id, :to_enum, :enum_for, :pretty_inspect, :ri, :equal?, :!, :!, :instance_eval, :instance_exec, :__send__]


Where have all of these methods come from? Lets look at another method called ancestors. The ancestors method returns as an array the Classes which this class has inherited from.


#Calling the ancestors function on the Movie class
>> Movie.ancestors
=> [Movie, Object, Kernel, BasicObject]

So here we see that we take from Object > Kernel > BasicObject. For sanity sake lets remove Kernel, because I will discuss what that is later. Movie takes directly from Object and in turn Object takes from BasicObject. What it takes from it's ancestors are methods, variables, and constants. Actually ancestors, and methods are two examples of this.

Lets expand on our Movie class some. We can add:
  • An initialize method to create an object of Movie class.
  • A class method named count, to count the amount of movie objects that have been created so far.
  • An instance method named villain, to determine who is the villain in a small subset of popular horror movies.
Below is the code

class Movie
    attr_accessor :name, :year
        def initializ(name,year)
            @name = name
            @year = year
                if defined?(@@count)
                    @@count += 1
                else
                    @@count = 1
                end
        end

        def villain
            if @name.match(/Halloween/)
                puts "Michael Meyers"
            elsif @name.match(/Friday/)
                puts "Jason Vorhees"
            elsif @name.match(/Nightmare/)
                puts "Freddy Crougar"
            else
                puts "Whos the villain again?"
            end
        
end

        def self.count
            @@count
        end
end


Lets see our new class in action.

#Create an object of class Movie, save it to the variable a
>> a = Movie.new("Friday the 13th", "1988")
=> #<Movie:0x7fdc008ffcf8 @year="1988", @name="Friday the 13th">
#Calling the villain function on the Movie object.
>> a.villain
Jason Vorhees
#Calling the class method count on our Movie class
>> Movie.count
=> 1

    We defined a Movie class and put some functions in that class, one of these functions was initialize.

    Initialize is a special constuctor method. The initialize method creates an object of a class with all the needed variables. Once an object is created, the defined and inherited instance methods from it's class are available to that object. You may notice that I am not even calling initialize, but I am calling new to create a Movie object.

    The function new is inherited from the Object class and points to the initialize method of the calling class. Our initialize method requires two values to be passed in and will populate the local variables name and year. Our Movie constructor does the following to initialize a movie object. We first save those local variables (arguments passed in to the initialize method), as instance variables with the same names (except that they have $ sigils preceding). We then facilitate a method to count object creation by counting each object we create. We do this by first checking if the class variable @@count is defined, if it isn't this means that there are no objects created, being that we know initialize has been called, we can go ahead and declare a class variable @@count and set it to 1. If we see that the variable @@count is defined, this means objects already exist, we simply increment the count value by 1.

    We next defined a function named villain, which calls a method named match against the @name instance variable. Match takes a string or regular expression as an argument and scans the given string for this pattern. If one is found match returns true, else it returns false. I am using an if, elsif control block to test if the value of the instance variable is a certain string. If it finds a match, it outputs another string, there is catch all for if it does not find a match to output a specific string as well. This method would be called on a Movie object.

    We finally define a method which has an odd syntax. It is defined as self.count. The self references the Class Movie. The return of the self call within the scope of the Movie class would return Movie the return of a self call within a movie object would return the object. See below.

>> class Movie
>> def what_am_i
>> puts "#{self}"
>> end
>> def self.what_am_i
>> puts "#{self}"
>> end
>> end


>> Movie.what_am_i
Movie


>> a.what_am_i
#<Movie:0x7fdc008ffcf8>


I defined two functions with the same name and operation. These two functions however operate on two seperate levels of scope. by defining a method named what_am_i under the Movie class, this is an instance method. It operates on objects of the class. The second method self.what_am_i is a class method. It operates on the class in its self.


OK, lets jump back some to inheritance while we are in the spirit of shown examples. Lets say that our job was this simple in life to where we have completed my Movie class. However life suddenly again becomes hard, because our boss requires we program the concepts of TV Shows. He said Movies are good, but people watch more TV.

..Fine, what is the difference really in a movie and a video? For arguments sake TV is broadcast on airwaves and movies are shown from hard copies at theatres. (Ofcourse we have notions like prime time and cable which completely thrash this statement, but whatever). Lets use our movie class to define a TV class. We will then add features to the TV class.

>> class TVshow < Movie
>> def star
>> if @name.match(/Cosby/)
>> puts "Bill Cosby"
>> elsif @name.match(/Happy/)
>> puts "Arthur Fonzarelli"
>> elsif @name.match(/Charles/)
>> puts "Buddy Limbeck"
>> else
?> puts "Whos the villain again?"
>> end
>> end
>> end

=> nil
>> TVshow.class
=> Class
>> TVshow.ancestors
=> [TVshow, Movie, Object, Kernel]
>> show = TVshow.new("Charles in Charge", "1980")
=> #<TVshow:0x7fdc00ae0748 @year="1980", @name="Charles in Charge">
>> show.star
Buddy Limbeck

    We created a class called TVshow which inherits from the Movie class we previously created. We added a new method named star which performs the same type of action as villain does for Movie. After closing the class we created a new object which represents the show "Charles in Charge", we then called the star method on this object. Buddy Libeck was returned.

    Notice that asides from adding a star method all of our defined methods from Movie are present.

>> show.villain
Whos the villain again?
=> nil

    Seems we can't find the villain because we are not dealing with horror movies of the 80's, we are dealing with Comedy Television shows from the 80's. We actually can not find the villain because the villain function is defined in the Movie class but really has no definition in the TV class. Ruby uses a look up algorithm when executing a function. First it looks in the present class for a method definition, if not found, it then looks in the parent class for the definition, it will continue to do this until it can no longer traverse the class hierarchy. 

    What we can do in this case is OVERRIDE the definition for villain from the Movie class in or TVshow class. This is an example of Ruby and it's dynamic nature. Allowing us to redefine classes, methods and just about anything on the fly.
 
>> class TVshow
>> def villain
>> if @name.match(/Cosby/)
>> puts "Bud"
>> elsif @name.match(/Happy/)
>> puts "The teachers?"
>> elsif @name.match(/Charles/)
>> puts "Umm,, Charles was the villain i guess"
>> else
?> puts "Thanks for playing, try again"
>> end
>> end
>> end

=> nil
>> show.villain
Umm,, Charles was the villain i guess

    So we have redefined our villain function from the Movie class to work on the TVshow class.

    There we go, so now we have completed (in the simple imaginary world we are working in) our jobs.

    Something that you may notice which may be new in my previous definition of the Movie class is:

attr_accessor :name, :year

    This is what is called an attribute accessor in Ruby. What ts allows is to easily and dynamically modify instance variables exposed by an object. The symbol arguments are kept as instance variables which have getter and setter methods pre defined.

Comments