Ketterä web-kehitys
ja Ruby on Rails

Matti Paksula / Syksy 2009

Tietojenkäsittelytieteen laitos
Helsingin Yliopisto

Kotikatsomot

+ 1 op

Kurssista on mahdollista saada 4op + 1op tekemällä "27h" enemmän töitä.

Muista katsella kurssisivua

Ensiviikon harjoitukset

Kurssikirja

Apidock.com - kirjan tueksi

Mitä kurssi sisältää?

Oppimistavoite

Kurssin jälkeen on mahdollista työllistyä Rails-kehittäjänä

Rails?

"Rails isn't playing catch-up with the new de facto web standards; it's helping define them."

Ruby?

Versionhallinta-kurssin robotti & tarkistus

Agile?

"Agility is part of the fabric of Rails"

MVC?

Convention over configuration

DRY: Don't Repeat Yourself

Tiimi

Kehitysympäristö

Käyttöjärjestelmä

Ruby 1.8.7

RubyGems

Tietokanta

Tietokanta-adapteri

Editori vai IDE

Varsinaisesta IDE:stä ei välttämättä ole hyötyä, hyvä tekstieditori toimii paremmin.

Kehitysympäristö laitoksella

Laitokselle asennettu valmiiksi "hyvät oletukset"

rails.cs.helsinki.fi

Aloitellaan

Rails-applikaation runko

.
|-- README		Geneerinen readme	
|-- Rakefile		"rake" -komennon oletus "Makefile"	
|-- app			Koko applikaation koodi tulee pääosin tänne
|   |-- controllers	Kaikki controllerit, elikkäs ohjaimet
|   |   `-- application_controller.rb	Yläluokka kaikille controllereille, oletusarvoisesti kaikki periytyvät tästä	
|   |-- helpers 	Viewien apumetodit
|   |   `-- application_helper.rb	Yläluokka helppereille
|   |-- models		Modelit, eli tietokantatauluja edustavat luokat
|   `-- views		Näkymät
|       `-- layouts	Näkymäwrapperit
|-- config		Konfiguraatiotiedostot
|   |-- boot.rb		Railssin alustus
|   |-- database.yml	Tietokantakonfiguraatiot YAML -muodossa
|   |-- environment.rb	Koko applikaatiota koskevat ympäristöasetukset.
|   |-- environments	Eri ympäristöjen asetukset
|   |   |-- development.rb	Kehitysympäristö
|   |   |-- production.rb	Tuotantoympäristö
|   |   `-- test.rb	Testausympäristö
|   |-- initializers	Lisää asetuksia, ajetaan kerran käynnistyksessä
|   |   |-- backtrace_silencers.rb	kts. sisältö
|   |   |-- inflections.rb	Sanojen taivutuksen määrittely, tarvitaan tietokantataulujen nimeämiseen.
|   |   |-- mime_types.rb	Tuetut mime-tyypit
|   |   |-- new_rails_defaults.rb	Rails 2.0 -> 3.0 -asioita
|   |   `-- session_store.rb	Määrittelee missä sessiot säilytetään, oletusarvoisesti cookiessa.
|   |-- locales	Lokalisointi
|   |   `-- en.yml	Kielen lokalisointitiedosto
|   `-- routes.rb	Määrittelee miten URL:t yhdistyvät applikaation controllereihin.
|-- db	Tietokantaskeemat, SQLite -kannan oletussijainti
|-- doc	Dokumentaatio tänne..
|   `-- README_FOR_APP	..ehkä
|-- lib	Applikaation käyttämät kirjastot, katso myös "vendor"
|   `-- tasks	Applikaation Rake -taskit
|-- log	Eri ympäristöjen logitiedostot
|   |-- development.log	
|   |-- production.log	
|   |-- server.log	
|   `-- test.log	
|-- public	Applikaation staattiset tiedostot
|   |-- 404.html	Oletusarvoinen 404, annetaan myös silloin kun haettua tietoa ei löydy tietokannasta
|   |-- 422.html	Access denied
|   |-- 500.html	Näytetään, kun applikaatio suorittaa poikkeuksen
|   |-- favicon.ico	
|   |-- images	Kuvat tänne
|   |   `-- rails.png	..
|   |-- index.html	Tämä pitää poistaa, jotta "/" -URL toimii
|   |-- javascripts	JavaScriptit
|   |   |-- application.js	Applikaation omat JavaScriptit tänne
|   |   |-- controls.js	script.aculo.us -kirjaston muinainen versio
|   |   |-- dragdrop.js	..drag'n'drop -kirjasto
|   |   |-- effects.js	..ja effect.  Nämä voi halutessaan poistaa tai päivittää uudempiin.
|   |   `-- prototype.js	Prototype JavaScript -kirjasto
|   |-- robots.txt	robots.txt
|   `-- stylesheets	Tyylitiedostot tänne
|-- script	Railssin omat skriptit
|   |-- about	Kertoo tietoja ajoympäristöstä
|   |-- console	irb konsoli Rails-tuella
|   |-- dbconsole	Valitun tietokannan konsoli
|   |-- destroy	Poistaa generoituja tiedostoja
|   |-- generate	Generoi tiedostoja
|   |-- performance	Suorituskykytestausta
|   |   |-- benchmarker	
|   |   `-- profiler	
|   |-- plugin	Asentaa plugineja vendor/plugins -kansioon
|   |-- runner	Suorittaa mitä tahansa koodia
|   `-- server	Käynnistää paikallisen web-serverin (Webrick tai Mongrel)
|-- test	Test::Unit -yksikkötestit
|   |-- fixtures	Valmis tietosisältö testeihin (tai kehitykseen)
|   |-- functional	
|   |-- integration	
|   |-- performance	
|   |   `-- browsing_test.rb	
|   |-- test_helper.rb	
|   `-- unit	Yksikkötestit modeleille
|-- tmp	Väliaikaistiedostot
|   |-- cache	
|   |-- pids	
|   |-- sessions	
|   `-- sockets	
`-- vendor	
    `-- plugins	Pluginit

MVC ja Rails

Model

script/console

Controller

Action?

Testataan

View

Yhdistetään kaikki kerrokset

Eri ympäristöt

Luento 2

Perjantai 10-12

Ohjelmaa

Ruby: Hash + "Stringin" ja :symbolin ero

# Leikkikää irb:ssä!

hajautustaulu = {}
hajautustaulu = Hash.new

hajautustaulu[:avain] = "symboli -avaimen arvo"
hajautustaulu["avain"] = "string -avaimen arvo"
hajautustaulu[23] = "integer -avaimen arvo"

# >> hajautustaulu
# => { :avain=>"symboli -avaimen arvo",
#      23=>"integer -avaimen arvo",
#      "avain"=>"string -avaimen arvo"}

Tietokannan perusoperaatiot ActiveRecordilla

Hakeminen


# id:n perusteella
Course.find(1)

# nimen perusteella
Course.find_by_name("rio")

# useamman kentän perusteella
Course.find_by_name_and_description("rio",
                                    "rinnakkaisohjelmointi")

Lisäys

c = Course.new :name => "LaMa"
c = Course.new({:name => "LaMa"})

c.save

Päivitys

c = Course.find(:first)
c = Course.first

c.name = c.name.upcase
c.save

Poisto

Course.destroy(1)

c = Course.last
c.destroy

Course.destroy_all :name => "Rails"

Count

Course.count

Person.count(:conditions => "age > 26 \
                             AND salary > 60000")

Ensi viikon tehtäviin liittyvää

Luento 4

Käsiteltävää..

Luento 5

Luento 6

Luento 7

My horror examples


# Original controller code

def authenticate
  if params[:login][:username] and params[:login][:password]
    user = User.authenticate(params[:login][:username], params[:login][:password])
    if user
      # password correct
      session[:user] = user
      redirect_to_stored
    else
      # password wrong
      redirect_to :action=>"index"
    end
  end
end

# Better implementation





def authenticate
  # No need to check if params were given to controller
  # if params[:login][:username] and params[:login][:password]

  user = User.authenticate(params[:login][:username], params[:login][:password])

  if user
    # 1) Don't store entire user object in session
    # 2) Wrap a method around login, so that we can change authentication behaviour if needed
    # session[:user] = user

    login_user(user)
    redirect_to_stored		# This probably was to redirect to "attempted page" (before filter) so this might be okay!
  else
    # If we change our page to use for login, let's use a named_route for that
    # redirect_to :action=>"index"
    redirect_to login_page
  end

end




# refactored

def authenticate
  user = User.authenticate(params[:login][:username], params[:login][:password])

  if user
    login_user(user)
    redirect_to_stored		# This probably was to redirect to "attempted page" (before filter) so this is okay.
  else
    redirect_to login_page
  end	
end





# Original controller code

def logout
  Offer.unlock_all_by_user_id(session[:user].id) if session[:user]
  session[:user] = nil
  redirect_to :controller=>"offer", :action=>"index"
end

# Better implementation

def logout
  # Wrap this to logout(user) or user.logout! -method
  # Note also that I checked if session[:user] exists even if we are in logout, where it exists.
  # Offer.unlock_all_by_user_id(session[:user].id) if session[:user]

  Offer.unlock_by(user)  # Unlock all by user
  user.logout!  		 # Logout user

  # As above, use named routes to ensure readability
  #redirect_to :controller=>"offer", :action=>"index"

  redirect_to root_url
end


# refactored
def logout
  Offer.unlock_by(user)
  user.logout!

  redirect_to root_url
end






# original controller code

if not @offer.locked.nil? and @offer.locked != session[:user].id
  redirect_to :action=>"edit_lock", :id=>@offer.id
else
  @offer.lock = session[:user].id
end





# step 1
# 1) use implement locking check at instance
# 2) use current_user instead of session
if @offer.locked? and @offer.locked != current_user.id
  redirect_to :action=>"edit_lock", :id=>@offer.id
else
  @offer.lock = current_user.id
end




# step 2
# 1) combine lock checking with user at instance
if @offer.locked_by?(current_user)
  # use resources instead of direct classes and actions
  redirect_to edit_offer_lock(@offer)
else
  # Implement locking at lower level, this implementation can be changed without breaking API to controllers
  @offer.lock_for(current_user)
end




# refactored:
if @offer.locked_by?(current_user)
  redirect_to edit_offer_lock(@offer)
else
  @offer.lock_for(current_user)
end

Asioita..

Luento 10

Yhteenveto

Tentti

Lisää mahtavahkoja juttuja ja muuta