Ruby on Rails/Grunnleggende Ruby

Objekter

rediger

Så godt som alt i Ruby er et objekt. Mange har forsøkt å forklare hva et objekt er, men de aller fleste ender opp med å bli innlagt på et mentalsykehus. Kort sagt: poenget med objekter er å modellere virkeligheten. Dersom et objekt har en lengde, ligger informasjonen om lengden i selve objektet. Man trenger ikke en ekstern funksjon for å vite hvor langt det er. (Dette trenger sårt en bedre forklaring.)

Alle objekter har en klasse, og klassen definerer hva slags objekt det er.

5.class
=> Integer #(En Integer er et tall uten desimaler)

"Dette er en tekst".class
=> String #(en String er en samling tekst, symboler tall etc.

["Fiolin", "Gitar", "Tuba"].class
=> Array #(et Array er en samling av objekter)

#Og alt etter '#' blir ansett som en kommentar, og vil ikke bli
#lest som Ruby-kode.

Alle «tingene» (som 5, Dette er en tekst etc) har en klasse. Eksempler på klasser er Integer (tall uten desimaler) og String (tekstsbiter med tall, symboler etc). Klassen til objektet definerer hva slags type objekt det er, og hva du kan gjøre med det.

Med ett Integer-objekt kan man gjøre typiske numeriske kalkulasjoner.

1 + 2
=> 3

6 - 1 
=> 5

8 / 2
=> 4

Ett String-objekt oppfører seg annerledes, siden det er av en annen klasse.

"Hei på deg".upcase
=> "HEI PÅ DEG"

"Snakke baklengs".reverse
=>"sgnelkab ekkanS"

"Legg meg" + " til deg."
=> "Legg meg til deg."

"5" + "2"
=> "52"

Metoder

rediger

Metoder er beskjeder man sender til objekter.

Det finnes to ulike typer metoder. Klassemetoder og instansmetoder. Klassemetoder kaller man direkte på en klasse, som String, mens instansemetoder kalles på en instans av noe. new er en klassemetode av String, mens upcase er en instansmetode av String.

Logikken er at klassemetoder gir funksjonalitet som går direkte på klassen.

Lag klasser

rediger

Klassene String og Integer er innebygd i Ruby. I Ruby kan man også lage nye klasser, og gi dem egenskaper.

class Vehicle
  def move_forward
    "moving forward"
  end

  def stop
    "stopped"
  end
end

def er et nøkkelord (end og class er også nøkkelord) for å definere en metode. Det som står etter def er navnet på metoden, i dette tilfellet move_forward og stop.

Slik virker den nye klassen:

#Lag en instans av Vehicle
vehicle = Vehicle.new

vehicle.class
=> Vehicle

vehicle.move_forward
=> "moving forward"

Arving av egenskaper

rediger

En klasse kan være en underklasse av en annen klasse. En underklasse vil arve alle egenskapene til klassen den er underklasse av.

class Bicycle < Vehicle
  def ring_bell
    "pling plong"
  end

  def pedal
    "pedaling"
  end
end

Det kan brukes på denne måten.

bicycle = Bicycle.new

bicycle.ring_bell
=> "pling plong"

# arver metoder fra over-klassen
bicycle.stop
=> "stopped"

bicycle.tullball
=> NoMethodError: "undefined method ‘tullball’ for Vehicle"

Metoden stop ble ikke definert i klassen Bicycle. Den metoden arvet Bicycle fra Vehicle.

Ruby har et helt sett med innebygde klasser. Kernel ligger på topp, og definerer nøkkelordene som def, return, class. Kernel sin underklasse Object definerer metoder som ettellerannet.class, og andre liknende «globale» metoder, som ettellerannent.object_id, ettellerannet.send, ettelerannet.inspect osv.

I stedet for å skrive ettellerannet.metode for å referere til en instansmetode, er det vanlig å bruke Klasse#metode. Så, dersom den forestilte klassen Queue har instansemetoden move_forward og klassemetoden find_all, kan man referere til dette som Queue#move_forward og Queue.find_all. Dette kan ikke brukes på denne måten i selve språket, da # definerer en kommentar i koden. Det man ville skrevet som Queue#move_forward vil i virkeligheten bli skrevet slik:

queue_item = Queue.new
queue_item.move_forward

Lagring av data

rediger

Et objekt kan settes opp slik at det inneholder data.

class Vehicle
  def initialize(brand, horse_power)
    @brand = brand
    @horse_power = horse_power
  end

  def brand
    @brand
  end
  
  def horse_power
    @horse_power
  end
end

initialize-metoden har Vehicle arvet fra Object-klassen. Den brukes til å definere hva som skal skje når man lager en ny instans av klassen man definerer den i.

Variabler som @ser_ut_som_dette er instansvariabler. De er tilgjengelige i den instansen av klassen de er satt i.

not_working = Vehicle.new
=> ArgumentError: "wrong number of arguments (0 for 2)"

my_car = Vehicle.new("Volvo", 192)

my_car.horse_power
=> 192

Logisk nok deles ikke verdiene på tvers av instanser.

v1 = Vehicle.new("Subaru", 110)
v2 = Vehicle.new("Fiat", 97)

v1.horse_power
=> 110

v2.horse_power
=> 97

Det finnes også funksjonalitet for å redigere data etter instansen er laget.

class Vehicle
  def initialize(brand, horse_power)
    @brand = brand
    @horse_power = horse_power
  end

  def brand
    @brand
  end
  
  def horse_power
    @horse_power
  end

  def set_brand(brand)
    @brand = brand
  end

  def set_horse_power(horse_power)
    @horse_power = horse_power
  end
end

Dette kan brukes på følgende måte:

my_bmw = Vehicle.new("BMW", 182)

my_bmw.horse_power
=> 182

my_bmw.set_horse_power(212)
my_bmw.horse_power
=> 212

I de aller fleste tilfeller ønsker man å gjøre my_bmw.horse_power = 138. For å sette opp dette, definer metonede på følgende måte:

class Vehicle
  def horse_power=(horse_power)
    @horse_power = horse_power
  end
end

my_saab = Vehicle.new("saab", 177)
my_saab.horse_power = 172
my_saab.horse_power
=> 172

Siden det er ganske tungvint å skrive alle disse metodene - særlig hvis man har mange attributter - finnes det snarveier for å sette opp en klasse på denne måten med attr_reader, attr_writer og attr_accessor.

class Vehicle
  # definerer Vehicle#brand og Vehicle#horse_power
  attr_reader :brand, :horse_power
  # definerer Vehicle#max_speed=
  attr_writer :max_speed
  # defivener Vehicle#weight og Vehicle#weight=
  attr_accessor :weight

  def initialize(brand, horse_power)
    @brand = brand
    @horse_power = horse_power
  end

  # Siden @max_speed bare er satt med attr_writer, defineres
  # denne metoden for å vise verdien av @max_speed
  def max_speed
    @max_speed
  end
end

a_truck = Vehicle.new("Scania", 496)
a_truck.horse_power
=> 496

a_truck.max_speed
=> nil

a_truck.max_speed = 120
a_truck.max_speed
=> 120

a_truck.weight = 8000
a_truck.weight
=> 8000