Ruby

From bibbleWiki
Jump to navigation Jump to search

Introduction

  • Thoroughly object oriented
  • Dynamic typing (Type errors are reported at runtime)
  • Duck typing (Code requires that an object supports the operations that are used)
  • Multi-paradigm (Supports procedural, functional and generic approaches)
  • Reflection (Like RTTI in C++)
  • Meta programming (Manipulating the underlying mechanics of the language)
  • Byte code Interpreted (Like java but not like C++)

Interactive Shell

You can start this using. You can type exit to well exit

irb

This allows you to work with ruby similar to python.

  • _ Provides the value of the last evaluated expression
  • Up arrow to go through history

Data Types

The following are the built in data types

  • Boolean
  • Numbers
  • Strings
  • Symbols
  • Hashes
  • Arrays
  • Enumerable

Boolean

Boolean data type represents only one bit of information either true or false.

Numbers

Numbers are immutable in ruby
Fixnum - Normal number e.g. 1
Bignum - Big numbers e.g. 111111111111
Float - Decimal number e.g. 3.142 (15 digits of precision)
Complex - Imaginary number e.g. 4 + 3i
Rational - Fractional number e.g. 9/4
BigDecimal - Precision number e.g. 6.0

Strings

Intro

A string is a group of letters that represent a sentence or a word. Strings are defined by enclosing a text within single (') or double (") quote.

Escaping

 'Serenity'  = Serenity
 '\'Serenity\''  = 'Serenity'
 'Serenity\\'  = 'Serenity\'

Or choose your own

 'Serenity' transport
 %q('Serenity' transport)
 %q['Serenity' transport]
 %q*'Serenity' transport*

Interpolation

put "Lander count: #{myVar}"

Heredoc

Ruby support this

message = <<EOS
  There's no place like home
  There's no place like home
EOS

Symbols

Symbols are like strings. A symbol is preceded by a colon (:). For example,

 :abcd

They do not contain spaces. Symbols containing multiple words are written with (_). One difference between string and symbol is that, if text is a data then it is a string but if it is a code it is a symbol.
Symbols are unique identifiers and represent static values, while string represent values that change.

Hash

A hash assign its values to its keys. They can be looked up by their keys. Value to a key is assigned by => sign. A key/value pair is separated with a comma between them and all the pairs are enclosed within curly braces. For example,

 {"Akash" => "Physics", "Ankit" => "Chemistry", "Aman" => "Maths"}

Arrays

An array stroes data or list of data. It can contain all types of data. Data in an array are separated by comma in between them and are enclosed by square bracket. For example,

 ["Akash", "Ankit", "Aman"]

These seem to work similar to python with the exception that these are inclusive

# slicing
arr = [1,2,3,4,5]

arr[1..3]
=> [2,3,4]

arr[1..-2]
=> [2,3,4]

Enumerable

This provides functions well known to python

Map

[1,2,3].map { |v| v * 10}
# => [10,20,30]

Reduce

[1,2,3].reduce(0) { |sum, v| sum + v}
# => 6

Sort

[3,1,3].sort
# => [1,3,3]

Select

[1,2,3,4,5].select{ |n| n.even?}
# => [2,4]

Each Cons

[1,2,3,4,5].each_cons(2) {|n| p v)
# [1,2]
# [2,3]
# [4,5]
#=> nil

Ranges

You can define ranges are inclusive of exclusive by using two or three dots.

1..5 # 1 to 5
1...5 # 1 to 4

They can be used in case statements

puts case sample_reading
     when 0..100 then "below normal"
     when 101..150 then "normal"
     else "excessive"
     end

Operators

Arithmetic Operators

+ Addition
- Subtraction
* Multiplication
/ Division
% Modulus i.e. returns remainder
** Exponent e.g. 10**20 is 10 to the power of 20

Comparison Operators

== Check if values are equal
!= Check if values are not equal
> Check if value of left greater than value on right
< Check if value of left less than value on right
>= Check if value of left greater than or equal to value on right
<= Check if value of left less than or equal to value on right
<=> Combined comparison operator. Returns 0 if first operand equals second, 1 if first operand is greater than the second and -1 if first operand is less than the second.
=== Used to test equality within a when clause of a case statement. e.g. (1...10) === 5 returns true.
.eql? True if the receiver and argument have both the same type and equal values.e.g. 1 == 1.0 returns true, but 1.eql?(1.0) is false.
.equal?True if the receiver and argument have the same object id. e.g if aObj is duplicate of bObj then aObj == bObj is true, a.equal?bObj is false but a.equal?aObj is true.

Assignment Operators

These are =, +=, -=, *=. /=, %= and **=. None of which are surprising in how they work. There is a parallel operators as well which swaps variables

 a, b = b, c

Bitwise Operators

These are &. |, ^, ~, <<, >> operators which behave like most languages

Logical Operators

and, or, &&, ||, ! and not are available.

Flow Control

Branching

if else

Some examples and no surprises

if a == "abc"
   doFoo1
elseif a == "def"
   doFoo2
else
   doFoo3
end

myVar = if a == "123" then "123" else "not 123" end

unless

Strange but OK I suppose

unless a == "abc"
   doFoo1
end

myVar unless a == "123"

Elvis

Thank you very much

iainisace == true ? doFoo1 : doFoo2

Conditional Initializing

The ||= is used often in ruby. Here is an example with an expanded version

ship ||= Spaceship.new 
#
ship = Spaceship.new unless ship

and and or vs && and ||

  • and and or have much lower precedence than && and ||
  • && has higher precedence than ||
  • and and or have the same precedence

and and or are used for flow control generally where && and || are used or if

# 
# and Continues when statement is returns true and not nil
#
lander = Lander.locat(lander_id) and lander.recall
# same as
lander = Lander.locate(lander_id)
lander.recall if lander

# 
# or Continues if the first statement returns false or nil
#
if engine.cut_out?
    engine.restart or enable_emergency_power
end
# same as
if engine.cut_out?
    enable_emergency_power unless engine.restart
end

Case Statement

Note case statements do not fall through. Below are some samples

case distance_to_dock
when "far away"
  lander.maintain_thrust
when "coasting time"
  lander.kill_thrust
when "collision imminent"
  lander.reverse_thrust
end

Assign value

thrust_power = case distance_to_dock
               when "far away" then 100
               when "coasting time" then 0
               when "collision imminent" then -100
               end

Case on type

case unit
when Lander
  lander.park
when Probe 
  probe.retrieve
  probe.save
else
  do_something_else("Default")
end

Looping Constructs

While

while am_i_still_true?
    music_center.play
end
# Or
while am_i_still_true? do music_center.play end
# Or
music_center.play end while am_i_still_true?

Unless

until am_i_still_true?
    music_center.play
end
# Or
until am_i_still_true? do music_center.play end
# Or
music_center.play end until am_i_still_true?

Begin/End

begin
   music_center.intro
   music_center.play
end while am_i_still_true?

begin
   music_center.intro
   music_center.play
end until am_i_still_true?

For Loop

Not surprises

for i in [3,2,1]
    puts i
end

for i in(1..10)
    puts i
end

Looping with Iterators

Looping

Examples or loops

# Forward
10.upto(20) { |i| puts i}
# Reverse
20.upto(10) { |i| puts i}
# Print 3 times
3.times {puts "This is great"}
# Odd numbers 1-10
1.step(10,2) {|i| puts i}

Controlling Looping

Next

Sane as continue in C++

while message = comms.get_message
    # next means continue to next item if true
    next if message.type == "sync"
    message.process
end

Break

Sane as break in C++

while message = comms.get_message
    message.process
    # break means break out of loop 
    break if message.type == "voice"
end

# Will return the message.text upon break
text = while message = comms.get_message
    message.process
    # break means break out of loop 
    break message.text  if message.type == "voice"
end

Redo

Bit insane but here it is

i = 0
while i < 3
   print "Please enter a positive number"
   input = gets.to_i
   redo if input <= 0
   i += 1
end

Blocks, Procs and Lambdas

Blocks

Introduction

Blocks are either single lines of code inside curly braces

ships = Spaceship.all
ships.each{ |ship| put ship.name}
end

Or multi-line inside a do

[1,2,3].each do
    puts "This is a loop"
end

Example

When you pass a block the result is evaluated at the yield

class Spaceship
   def debug_only
       return nil unless @debug
       return nil unless block_given
       puts "Running debug code..."
       yield
   end
end

ship.debug_only # No output because block not given

ship.debug_only do
    puts "This is debug output"
end

Block arguments

Here is an example of a do..end block

def produce
  yield :spaceship, :freighter, :yacht, size: :s, seats: :leather
end

produce do | what = :spaceship, *types, 
             size: :m, engine_count: 2, **custom_components|

   puts "Producing #{what}"
   print "Types:"
   p types
   puts "Size: #{size}"
   puts "Engine count: #{engine_count}"
   print "Custom Components"
   p custom_components
end

#>Producing spaceship
#>Types: [:freighter, : yacht]
#>Size: s
#>Engine count: 2
#>Custom components: {:seats=>:leather}

Block Context

When you invoke a block the context which it was invoked in is present. That is to say the available parameters are still available in the yield.

$debug = true

def  debug_only
    yield if $debug && block_given
end

class Spaceship 

    def initialize
        @some_attribute = {containment_status: :ok, core_temp: 350}
    end

    def launch
        debug_only { p @some_attribute}
    end
end

ship1 = Spaceship.new
ship1.launch

Example Usage

Here is an example usage of block where timings are acquired and perhaps makes more sense for the approach

def with_timing
    start = Time.now
    if block_given?
        yield
        puts "Time taken: #{Time.now - start} seconds"
    end
end

def run_operation_1
    sleep(1)
end
def run_operation_2

with_timing do
   run_operation_1
   run_operation_2
end

# output: Time taken: 1.000057 seconds

Procs

Procs are named blocks. Adding an & to the argument will denote a block is a proc. You can create a proc from a block.

p = Proc.new {|bla|} puts "I am a proc that says #{bla}!" }
p = proc {|bla|} puts "I am a proc that says #{bla}!"} 

# You can invoke them with
p.call "Yay"!
p.yield "Wow"
p.("nothing")
p["hello"]

Lamdas

Lambda can also be created from blocks. You must pass the right number of arguments to a lamba

lmb = lambda {|bla| "I am a proc that says #{bla}!" }

# Stabby Lambdas
also_lmb = ->(bla) {"I am a proc that says #{bla}!" }

Exceptions

Handling Exceptions

Ruby provides many exception classes. The main 3 are

  • Exception
  • StandardError
  • RuntimeError
def launch
   batten_hatches
   light_seatbelt_sign
   true
rescue LightError
   put "None Fatal so returning true"
   true
rescue StandardError => e
   put e.message
   false
end

Raising Exceptions

Raising can be done simply

def batten_hatches
#
    raise "Stuffed me heaties"
end

Ensure and Else (finally)

To make sure that things are tickerty-boo we after can add an else cause after the rescue to perform code when no exceptions and a ensure clause to manage resources on exception e.g.

def batten_hatches
    hatch_file = File.open("bat.txt")
#
    raise HatchError, "Stuffed me heaties" if door.jammed?
#
    true
rescue SystemCallError => e
    false
else
    puts "No Exception raised"
ensure
    hatch_file.close if hatch_file
end

Retrying

This does not seem like the right way to do this but

def batten_hatches
   hatch_list = API.request("/flamingos")
rescue RuntimeError => e
    attempts ||= 0
    attempts += 1
    if attempts < 3
        puts e.message + ". Retrying request."
        retry
    else
        puts "Request failed"
        raise
    end
end

Rescue Modifier

This allows you to ignore errors on functions. If you want to make sure code reviews are working properly then use the in abundance.

# bad
do_something rescue nil

Throw Catch

This is maybe a different syntax to c++. The arguments for the catch must match the throw. The syntax is as follows

 catch :lable_name do
 # matching catch will be executed when the throw block encounter
 
 throw :lable_name condition
 # this block will not be executed 

 end

For example

gfg = catch(:divide) do
  # a code block of catch similar to begin
  100.times do
    100.times do
      100.times do
        number = rand(10_000)
        # comes out of all of the loops
        # and goes to catch statement
        throw :divide, 10 if number == 0
      end
    end
  end
  number # set gfg = number if
  # no exception is thrown
end
puts gfg

Object vs Variables

In ruby variables are reference i.e.

a = "abc"
b = a;

b is a reference to a not a copy. i.e. if we go

a.upper!

b will be upper too. To get a copy you need to use the clone method. i.e. b = a.clone. Note this is not a deep copy.

Classes

Introduction

You can create a class like this

class Shapeship

  # Multiple props
  attr_accessor :prop1, :prop2

  def use_prop
     prop1 = "Hi I am local and broken" # Won't work.
     self.prop1 = "Must use self like this!"
  end

  def launch(destination)
    @destination = destination
  end

  # Function for Getter
  def destination
    @destination
  end

  # Read/Write accessor
  attr_accessor :destination

  # Readable accessor
  attr_reader :destination

  # Writable accessor
  attr_writerr :destination
end

ship = Spaceship.new
ship.launch("Earth")

Initialize

Initialize is called when a new instance is created

class Shapeship
    def initialize(name, cargo_module_cound)
        @name = name
        @cargo_hold = CargoHold.new(cargo_module_count)
        @power_level = 100
    end
end

ship = Spaceship.new("Dreadnought", 4)

Inheritance

Nothing strange here

class Probe
   def deploy(deploy_time,return_time)
      puts "Deploying"
   end
end

class MineralProbe < Probe
   def deploy(deploy_time)
      puts "Preparing sample chamber"
      super(deploy_time, Time.now + 2 * 60 * 60) # Call base function
   end
end

Class Method (static)

class Shapeship
    def self.thruster_count
        2
    end
end
==Class Variables (static)==
<syntaxhighlight lang="ruby">
class Shapeship
    @@thruster_count = 2
end

Method Visibility

Standard Methods

Two approaches you can add the private word without the # or provide a list of private functions e.g. private:foo1, foo2

class Shapeship
    def launch
        batten_hatches
    end

# private
    def batten_hatches
       put "batten_hatches"
    end

    private:batten_hatches
end

class SpritelyShapeship < Spaceship
    def initialize
        batten_hatches # Ok from derived class
    end
end

Class Methods (statics)

Different for statics oops class methods

class SpaceShip
   def self.disable_engine
     # Blah,  Blah, Blah
   end
   private :disable_engine # Does not work
   
   private_class_method:disable_engine   
end

Summary

  • public is the default
  • private mean "can't be called with an explicit receiver"
  • private_class_method is private for class methods
  • protected means "allow access for other objects of the same class"
  • private and protected are not used much

Monkey Patching

Gosh this looks awful, ruby replaces the function whilst running.

class SpaceShip
   def insane
     put "insane"
   end
end

spaceShip = SpaceShip.new

class SpaceShip
   def insane
     put "insane 2"
   end
end

puts spaceShip.insane

Modules

Namespaces

This is used to create namespaces for methods and classes and can be nested

module API
   def self.hatch_list
# blah      
   end
end

hatches = API.hatch_list

module Spacestuff
   class Spaceship
   end
end

ship = SpaceStuff::Spaceship.new

module A
   module B
      class Test
      end
   end
end

testy = A::B::Test.new

Mixins

Instance Mixins

This is a way of combining functionality into different classes. Define a module and then include it in your class.

module AirControl
   def measure_oxygen
# blah
   end
end

class Spaceship
   include AirControl
# blah
end

ship = Spaceship.new
ship.measure_oxygen

Class Method (static method) Mixins

Using extend can be used to mixin class methods

class Spaceship
   extend AirControl
# blah
end

Spaceship.measure_oxygen

Including Static and Non Static Mixins

You can provide the hook method included in your module to reduce the need to include and extend.

module Docking 
   module ClassMethods
      def get_default_config
#
      end
   end
   
   def self.included(base)
      base.extend(ClassMethods)
   end
   
   def dock
   end
end

class Spaceship
   include Docking
end

Methods

Default parameters

def make_cake(type = :type_of_cake, size = calculate_cake_size())
end

def make_cake(type = :type_of_cake, size = (type == :type_of_cake ? :x : :m))
end

Variable parameters

Works similar to python

def produce_fleet(days_to_complete, *type)
end

product_fleet(10, :freighter, :freighter,, :explorer)

# or 
types = [:freighter, :freighter, :explorer]
product_fleet(10, types)

Keyword Arguments

Straight forward

def produce_fleet (days: days_to_complete, types: *type)
end

Like python double ** will be a dictionary

def produce_spaceship(type = :freighter sizxe: :m, **custom_components)

 components = {engine:    :standard,
               seats:     :standand,
               subwoofer: :none}

 components.merge!(custom_components)
#
end

# engine and seats are custom
produce_spaceship(:yacht, 
                  size: :s, 
                  engine: :rolls_royce, 
                  seats: :leather)

Operator Overloading

Introduction

For the equals operator you can overload your class

class SpaceShip
   attr_reader :name

   def initialize(name
       @name = name
   end

   def ==(other)
     name == other.name
   end
end

Further Examples

Here we overload [], << and +

class Spacehsip

 attr_reader :name 
 attr_reader :speed

 def initialize(name)
     @name = name
     @cargo = []
     @speed = 0
     @vessels = Hash.new { [] }  

 def []=(type, vehicles)
     @vessels[type] = vehicles
 end

 def [](type)
     @vessels[type]
 end

 def <<(cargo)
     @cargo << cargo
 end

 def +@
     @speed +10
 end

end 

ship1 = Spaceship.new("Serenity")

# Example of []
class Landers; end

ship1[:landers] = [Lander.new, Lander.new]
 puts "Landers: #{ship1[:landers].inspect}"

# Example of <<
class CargoPod; end

cargo_pod = CargoPod.new  
ship1 << cargo_pod
p ship1

# Unary + Note the @ sign
+ship1
put "Speed: #{ship1.speed}"

send and delegates

You can use send to call a method on a class eg.

myclassInstance.send(myclassMethod())

Given this you can use it as a delegate eg.

case input
when :up_arrow then ship.tilt_up
when :down_arrow then ship.tilt_down
when :left_arrow then ship.tilt_left
when :right_arrow then ship.tilt_right
end

# could be written
handlers = {
 up_arrow: tilt_up
 down_arrow: tilt_down
 left_arrow: tilt_left
 right_arrow: tilt_right}

ship.send(handlers[input])

method_missing

When you send a message to an object, the object executes the first method it finds on its method lookup path with the same name as the message. If it fails to find any such method, it raises a NoMethodError exception - unless you have provided the object with a method called method_missing. The method_missing method is passed the symbol of the non-existent method, an array of the arguments that were passed in the original call and any block passed to the original method.

method_missing is in part a safety net: It gives you a way to intercept unanswerable messages and handle them gracefully. See the example - p012zmm.rb below.

class Dummy  
  def method_missing(m, *args, &block)  
    puts "There's no method called #{m} here -- please try again."  
  end  
end  
Dummy.new.anything

The output is:

>ruby p012zmm.rb  
There's no method called anything here -- please try again.  
>Exit code: 0  

You are also responsible for maintaining the method_missing signature. It's possible to write a hook that captures only a missing method's name while ignoring its arguments and associated block.

General

Organizing Code

Here is a typical example

product_deep_space
  - bin
  - doc 
  - lib
    - deep_space
      - spaceship.rb
      - probe.rb
    - deep_space.rb
  - test
  - init.rb

To include a file use __dir__ (current path)

require '#{__dir__}/deep_space/spaceship'

require_relative 'deep_space/spaceship'

require 'json'

You can look at the load path using $LOAD_PATH

Gems

Introduction

Gems are the equivalent of nuget packages.

Sites

The main site is [[1]] or [[2]]

Usage

Here is some examples

gem install log4r
gem list

Bundler

This allows you to provide a list of dependency for your app. You put these in a Gemfile.

source 'https://rubygems.org'
gem 'pg', '0.13.2'
gem 'haml', '3.1.6'

To install you can then do

bundle install

Testing

Test Driven Development (TDD)

An example for this is mini test

Behaviour Driven Development (BDD)

An example for this is cucumber

Packaging

Type of packaging

  • Source control + bundler
  • Package as a gem

Gem Example

  • Create a gemspec file
  • Run gem build <gemspec file>
  • Run gem install <package>
  • Run gem list

Resources