wiki:SummaryRuby

Version 5 (modified by Thanatermesis, 10 years ago) ( diff )

--

#!/usr/bin/env ruby

$SAFE = 4 # set the security value at the highest prior unless we need more freedom
require 'a'  # will load a.rb, only one time
load 'a.rb'  # will load a.rb, every time called this line

#
# Structure
#

# Classes:
class Animal
    # this sets a public id/varible that can be used for our objects,
    # independently of being or not declared in the class, we can already use them
    attr_accessor :lenght, :color  # both read and write
    attr_reader :lenght, :color    # read-only
    attr_writer :lenght, :color    # write-only (setter)

    # this method will be called when creating a new object, it requires
    # arguments if we state them
    def initialize(given_name="Unknown", given_age)

        # the funny here is that if you call "dog = Animal.new(5)", it will
        # understand that you are passing the second parameter and set Unknown
        # to name automatically
        @name = given_name  # create variables for the objects that we can use,
                            # from the given arguments
        @age = given_age

        # variables with @@ instead of @ are accessible only for classes as a
        # whole, not by the instance of the objects, useful for store
        # information relevant to all objects of the same class
        #
        # WARNING: there's an important side-effect behaviour important to consider: http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
        if defined?(@@number_of_squares)
            # variable already exists for all objects when the first one is created
            @@number_of_squares += 1
        else
            # create the variable first time when the first object is created
            @@number_of_squares = 1
        end
    end

    def self.count
        @@number_of_squares  # will return the number of objects when Animal.count is called
    end

    # this is a simple method, used to perform actions to objects (like a function)
    def name
        # when calling "dog.name" will return "lassie" for example, "return" is optional
        return @name
    end

    # if you want to set the age in another moment than when initialized, you
    # need to use this setter function
    def age=(new_age)
        # new_age is the parameter given to the call, like "dog.age = 10",
        # adding = in the name of the method means that the method is used for
        # assign values
        @age = new_age
    end

    def some_error_handled_case
        # raise an error type with a message if a specific case
        raise ArgumentError, "Age is too young" if @age < 2
    end

    # using self here is only accessible when calling the class, instead the
    # object, like: Animal.show_name
    def self.show_name  # self acts like an alias to Animal, so:  def Animal.show_name ...
        puts self  # when called, will print "Animal" (class name)
    end

    # create an alias to whatever thing to another name
    alias show_name list_name


    private
    # things from here only accessible from the object itself (instances)
    # calls: method of an object can call its private methods (inside's class code)
    # cannot call:  dog.method_private
    # note: using 'send' you can do it, it invokes methods by name: dog.send(:method_private)
    def privated_area
        puts "I will only be shown when another method of this class/object calls this private method"
    end

    protected
    # same as private, but callable from different objects of the same class
    # allowing to communicate between them, like for do comparisons between objects
    def age_difference_with(other_person)
        (self.age - other_person.age).abs
    end
    # example:  fred = Person.new(34) ; chris = Person.new(25) ;  puts chris.age_difference_with(fred)

    def compare(c)
        if c.age > age
            "The other object's age is bigger."
        else
            "The other object's age is the same or smaller."
        end
        # example:  chris = Person.new(25) ; marcos = Person.new(34) ; puts chris.compare_age(marcos)
    end

    # back to the elements of code which are public
    public
    def summary
        puts "I can be called normally"
    end

    private :method1, :method2  # we can also define them on this way
end



# Inheritance:
# create a superclass called Doctor from the Person's class (inheritance)
class Doctor < Person
    # we have already defined the method "name" in the Person's class, so we extend it:
    def name
        # when we call .name method from a Doctor's object, will append like: "Dr. Name"
        # "super" walks in inheritance of the same methods in their parents
        "Dr. " + super
    end
end

somebody = Person.new("John")
puts somebody.name # John
who = Doctor.new("James")
puts who.name # Dr. James
puts who.instance_variables # @name



#
# Polymorfism:
#   - calling the method "talk" in Dogs or Cats they do different things, but
#   you can use it in the same way this is very useful when you need to do the
#   same action for different objects, for a example a loop of talking animals
#
class Dog < Animal
    def talk
        puts "Woof!"
    end
end

class Cat < Animal
    def talk
        puts "Meeew"
    end
end



#
# Nested classes:
#   - useful for improve structure/grouping/dependency
#
class Drawing
    class Line
    end

    class Circle
    end

    # you can call circle subclasses directly from Drawing too on this way:
    def Drawing.give_me_a_circle
        Circle.new
    end
end

# the way to call a class inside another class is by using ::
# trying to use directly Circle will fail, so:
my_circle = Drawing::Circle.new



#
# Extensions
#   - already-defined classes (any of them) can be extended or replaced for
#     example to the Fixnum objects we can use .minutes to translate a number to
#     minutes
class Fixnum
    # we can extend any class with improvings or modifications
    def minutes
        self * 60
    end
end
# and so we can use now:
puts 5.minutes # 300



#
# Modules
#
#   They allows to separate things to avoid clashes, similar to classes, but
#   they doesn't allows you to instantiate objects, their methods can be
#   directly used without the need to create objects, they are more like
#   functions that just processes things, let's say toolboxes of features

module Country
    class Ruler
        attr_accessor :name
    end

    def hello
        puts "I can say hello without the need to create an object"
    end
end

module Toolbox
    class Ruler
        attr_accessor :lenght
    end

    # Mix-in: this will print the name of the class which loaded this module
    # useful for introducing methods in other's classes
    #
    # basically is a method defined outside of any class form, so when
    # integrating a module inside an external class it will simply work on it
    def class_name
        self.class.to_s
    end
end

# direct call of the module with its contents
a = Toolbox::Ruler.new
a.lenght = 50
b = Country::Ruler.new
b.name = "Ghengis Khan"

# get the module features and directly use it
include Country
governor = Ruler.new

# you can use the method hello from the module Country, without the need to
# create or use of objects
hello



#
# Flow examples
#

# loops
loop do
    i += 1
    break if i > 100  # break da loop
end

while (i == 10)
    puts "i = 10"
end

until (i > 10)
    puts "i finally reached 10"
end


# if
if 2 == 1
    puts "universe is broken"
end

unless 2 == 2
    puts "universe is broken"
end


# case
fruit = "orange"
case fruit
    when "orange"
        color = "orange"
    when "apple"
        color = "green"
    else
        color = "unknown"
end


# ternary operator
puts  x > 10 ? "higher than 10" : "lower or equal to 10"



#
# Structures:
# it is an easy and fast way to define attributes and hold data, same as to
# create an entire class with defined initializators (but without require
# strictly to set all the parameters)
Person = Struct.new(:name, :gender, :age)
fred = Person.new("Fred", "male", 50)



#
# variable substitution ways:
#
foo = "12345678901234567890"
# puts the first 10 chars in different ways:
puts foo[1,10]
puts foo[0..9]
puts foo.slice(0,10)
puts foo.match("^..........").to_s



#
# Arrays:
#
a = [1, 2, 3, "four"]
a << 5      # append an item
a.push 6    # append an item
puts a.empty? # false
puts a.include?("four") # true



#
# Hashes (associative arrays)
#
dictionary = { 'cat' => 'feline animal',
               'dog' => 'canine animal',
               'tags' => ['books',
                   'libraries',
                   'knowledge'] }
puts dictionary['cat'] # shows description
puts dictionary['tags'].first # shows the first array element for the subarray tags
dictionary.delete("cat")
dictionary.delete_if { |key, value| value =~ /feline/ }



#
# Code blocks
#
def each_vowel
    %w{a e i o u}.each { |vowel| yield vowel }
end

each_vowel { |v| puts v + " is a vowel" }
# the delimiter of "each" can be modified passing an argument like .each(',') or
# changing the input delimiter variable $/
#
# More exactly, yield runs "in the inverse way" including its parameters, what you pass to a call to the function that includes a yield, this is a more clear example:
def test
   yield 5
   puts "method test"
   yield 100
end
test {|i| puts "You are in the block #{i}"}

# so you got:
# You are in the block 5
# method test
# You are in the block 100




#
# Errors handling
#
class GetWeb
    begin
        # block of code where can happen errors
        a = (10 / 0)  # if this doesn't works...
    rescue => e
        a = 10  # instead of just exiting we can simply assign a default behaviour and continue
        puts "class of exception is: " + e.class.to_s  # show some debug reason of our error
    end
end

# another example:
data = ""
begin
    #<..code to retrieve the contents of a Web page..>
    #data = <..content of Web page..>
rescue ZeroDivisionError
    #... code to rescue the zero division exception here ...
rescue YourOwnException
    #... code to rescue a different type of exception here ...
rescue
    puts "The Web page could not be loaded! Using default data instead."
    #data = <..load data from local file..>
end
puts data



#
# Debug
#
# If you want to run a script in debug mode, "ruby -r debug script.rb", use
# step, break, watch, cont, etc... 
# you can found some basic example in the book "beginning ruby" pag. 190



# Catch & Throw:
# note: catch & throw is useless and confusing, avoid it
# catch & throw works in a similar way as rescue/raise, but using symbols rather than exceptions
catch(:finish) do
    1000.times do
        x = rand(1000)
        # we will exit the loop "catch" containing the symbol :finish if we got this case
        throw :finish if x == 123
    end

    puts "Generated 1000 random numbers withoutgenerating 123!"
end

# another catch & throw example
def routine(n)
    puts n # final result will produce: 3 2 1 0
    throw :done if n <= 0 # exit inifite nested loop if case happens
    routine(n-1) # nested itself, infinitely
end

catch(:done) { routine(3) }



#
# Dynamic Code Execution
#

# executes the code created dynamically
eval "puts 2 + 2"

# similar:
my_number = 15
my_code = %Q{#{my_number} * 2}
puts eval(my_code)

# String interpolation makes the eval methods powerful tools for generating
# different features on the fly. This ability is a power unseen in the majority
# of programming languages
# - this method adds an accessor to another class,
# dynamically using class_eval and interpolating a name given externally
def add_accessor_to_person(accessor_name)
    Person.class_eval %Q{
    attr_accessor :#{accessor_name}
    }
end

# It’s possible to take the previous example a lot further and add an
# add_accessor method to every class by putting your class_eval cleverness in a
# new method, defined within the Class class (from which all other classes descend):
class Class
    def add_accessor(accessor_name)
        self.class_eval %Q{
            attr_accessor :#{accessor_name}
        }
    end
end

# You can eval code in instances of objects too:
class MyClass
    def initialize
        @my_variable = 'Hello, world!'
    end
end

obj = MyClass.new
obj.instance_eval { puts @my_variable }  # add extra code to an existing object and execute it

# In the same way you can define methods for existing objects
obj.instance_eval do def new_method ; puts 'born new method in an alive object' ; end ; end
# better way to define a method for an existing object, if you dont want to use
# dynamic code execution of eval
obj.new_method do def new_method ; puts 'born new method in an alive object' ; end ; end


# example of an "attr_accessor" equivalent writed in dynamic code, which will work in the same way:
class Class
    def add_accessor(accessor_name)
        self.class_eval %Q{
        def #{accessor_name}
            @#{accessor_name}
        end
        def #{accessor_name}=(value)
            @#{accessor_name} = value
        end
        }
    end
end

# You can use this technique to create a multitude of different “code
# generators” and methods that can act as a “macro” language to perform things
# in Ruby that are otherwise lengthy to type out.



#
# Forks & Processes
#

# process-ID is returned in the parent but 'nil' in the child, use this to
# determine which process a script is in
if fork.nil?
    exec "ruby other-file.rb"
end

# fork a child and wait for it
child = fork do
    sleep 3
    puts "Child says 'hi'!"
end
puts "Waiting for the child process..."
Process.wait child
puts "All done!"



#
# Unit Testing
#
#   - default in stdlib is Minitest, but there's also: rspec, minitest,
#     test/unit, minispec, bacon, testrocket... rspec is also very used

# create a method for test it later
class String
    def titleize
        # we want to use this method for print results like: "This Is A Test"
        self.capitalize
        # and we will end later in improving the code like these next commented
        # lines, because we haven see that the previous test failed in some cases
        #self.gsub(/\b\w/) {|letter| letter.upcase }
        #self.gsub(/\s\w/) {|letter| letter.upcase }
        #self.gsub(/(\A|\s)\w/) {|letter| letter.upcase }
    end
end

puts "this is a test".titleize # this will print "This is a test", but we don't want this result, so for avoid bugs:

# method 1 for testing (manual testing)
# let's add some "assertion" checkers
#
# if our method doesn't give us what we exactly expect, fail
raise "Fail 1" unless "this is a test".titleize         == "This Is A Test"
# what happens with numbers? let's add this because this is what we expect in any case
raise "Fail 2" unless "another test 1234".titleize      == "Another Test 1234"
# and with other chars like ' ?
raise "Fail 2" unless "We're testing titleize".titleize == "We're Testing Titleize"

# method 2 for testing
require 'test/unit'
# there's also "Minitest" (included in stdlib) and "Bacon" for unit testing
class TestTitleize < Test::Unit::TestCase
    def test_basic
        assert_equal("This Is A Test", "this is a test".titleize)
        assert_equal("Another Test 1234", "another test 1234".titleize)
        assert_equal("We're Testing", "we're testing".titleize)
    end
end

# method 3 for testing: use Minitest instead, seems like more easier and fast to use option
module DataStore::AdapterSpec
    it "returns nil for an invalid key" do
        @adapter.get(:invalid).must_equal nil
    end

    it "can set a value" do
        @adapter.set(:foo, 42)
        @adapter.get(:foo).must_equal 42
    end
end
# from:  http://wojtekmach.pl/blog/2013/07/17/sharing-examples-in-minitest/
# more: http://blog.arvidandersson.se/2012/03/28/minimalicous-testing-in-ruby-1-9



#
# Misc
#
# run when finish
at_exit { puts "#{Dir.pwd}" } # enqueue a job for when the application has finished to run
# trap
trap("SIGINT") do
    puts "signal INT trapped!, what we should do?"
end



# Using Files
puts "It exists" if File.exist?("text.txt")

File.open("text.txt") { |f| puts f.gets }  # show file contents (cat)

f = File.new("text.txt", "r")
puts f.gets
f.close

data = File.read("text.txt")
array_of_lines = File.readlines("text.txt")

f = File.open("text.txt")
puts f.gets
puts "position in file is: " + f.pos
f.pos = 8
puts "position in file has moved to: " + f.pos
f.close # close the file when finish!

# by using File.open in a block, is not needed to close the file when finish
File.open("text.txt", "w") do |file| file.puts "Hi" end
# note that the next line is invalid, if you put a space before ( then is not an argument list what you pass to it
#File.open      ("text.txt", "w") do |file| file.puts "Hi" end

# appending
f = File.open "text.txt", "a"
f.puts Time.now
f.close

# See the modified time of a file
f = File.mtime "text.txt"
puts f.hour # since the object returned (f) is a Time object, we can treat him as it



#
# Documentation (generating your own)
#
# Similar to doxygen, just write comments using this syntax and by running
# "rdoc file.rb" (or yard?) you will obtain nicely html's generated
#
# In documentations, "Array#sample" means the instance method of a class

#= RDoc Example
#
#== This is a heading
#
#* First item in an outer list
# * First item in an inner list
# * Second item in an inner list
#* Second item in an outer list
# * Only item in this inner list
#
#== This is a second heading
#
#Visit www.rubyinside.com
#
#== Test of text formatting features
#
#Want to see *bold* or _italic_ text? You can even embed
#+text that looks like code+ by surrounding it with plus
#symbols. Indented code will be automatically formatted:
#
# class MyClass
#   def method_name
#     puts "test"
#   end
# end
#
#--
# This section is hidden from RDoc and could contain developer
# notes, private messages between developers, etc.
#++
# RDoc begins processing again here after the ++.
#

# by adding in a class or element "#:nodoc: all" there's no docs generated (for
# the entire class)




# SAFE levels
#
# Value of $SAFE         Description
# 0     No restrictions. This is the default safe level.
# 1     Potentially unsafe methods can’t use tainted data. Also, the current
#       directory is not added to Ruby’s search path for loading libraries.
# 2     The restrictions of safe level 1, plus Ruby won’t load any external
#       program files from globally writable locations in the filesystem. This is to
#       prevent attacks where hackers upload malicious code and manipulate existing
#       programs to load them. Some potentially dangerous methods are also
#       deactivated, such as File#chmod, Kernel#fork, and Process::setpriority.
# 3     The restrictions of level 2, plus newly created objects within the
#       program are considered tainted automatically. You also cannot untaint objects.
# 4     The restrictions of level 3, plus nontainted objects created prior to
#       the safe level being set cannot be modified. You can use this to set up an
#       execution environ- ment in a lower safe mode, and then provide a way to
#       continue execution while protecting the original objects and environment.





#
# MORE:
#
# regex:
# Try regular expressions in realtime!  http://rubular.com/
#
# references:
# http://www.zenspider.com/Languages/Ruby/QuickRef.html
# http://www.tutorialspoint.com/ruby/ruby_quick_guide.htm
# "Begining Ruby - book": useful classes and methods, errors, regex, variables, etc...  from pag. 575
#
# standard lib:
# http://www.ruby-doc.org/stdlib-2.1.0/

Note: See TracWiki for help on using the wiki.