A Ruby programozási nyelv

Ruby on Rails



Bevezetés

A Rails a Ruby nyelvre épülő nyílt forráskódú, keresztplatformos, MVC (Model-View-Controller) mintára épülő webalkalmazás-keretrendszer. David Heinemeier Hansson írta 2004-ben, a Basecamp program kódjának felhasználásával.

Alapelvei a Don't repeat yourself (ne ismételd magad) és a Convention over Configuration (konvenciók a beállítások előtt): minden információ csak egy helyen szerepel (például egy adatbáziskezelő osztályban nem kell az oszlopokat definiálni, a Rails közvetlenül kiolvassa a nevüket az adatbázisból), és a konvenciókat követő elnevezésekhez automatikusan kódot generál a rendszer (például az adatbázis sales táblája automatikusan hozzárendelődik a Sale osztályhoz). AJAX-támogatása miatt a web 2.0 alkalmazások egyik népszerű keretrendszere.

Komponensek

A Rails keretrendszer az alábbi komponensekből épül fel:

Az imént felsorolt komponensek közül egy egyszerű Rails alkalmazásnak csak az Active Record, Action View és Action Controller hármasra van közvetlenül szüksége, amik rendre az MVC architektúra Model, View és Controller részei. A továbbiakban erről a három komponensről lesz szó.

Telepítés

A Ruby (Windows alatt: RubyInstaller) telepítése után a legegyszerűbben a rubygems segítségével telepíthetjük a Railst, a következő parancs kiadásával:

$ gem install rails

A rubygems feltelepít mindent, amire szükségünk van a Rails használatához, beleértve egy egyszerű webszervert (WEBrick) és egy adatbázist (SQLite). Természetesen használhatunk mást is, a Rails ismeri többek között a MySQL és PostgreSQL adatbázisokat, és használható Apache, lighttpd, nginx, stb. webszerverekkel.

A telepítés után a "rails" és a "rake" parancsokkal tudunk új rails alkalmazást létrehozni. A "rails" programot használjuk az alkalmazásunk komponenseinek generálására, a szerver elindítására, stb. A "rake" (Ruby mAKE) parancs különféle szkriptekkel segíti munkánkat, mint például az adatbázis adminisztrálása. Egy "Blog" nevű alkalmazás vázát a következő parancs kiadásával tudjuk létrehozni:

$ rails new blog

Ez az aktuális könyvtárunkon belül egy "blog" könyvtárat hoz létre, majd azon belül számos alkönyvtárat és fájlt. Ezekről részletesebben a Szerkezet pontban lesz szó. A blog könyvtárba belépve és onnan a szervert elindítva ellenőrizni tudjuk, hogy sikeres volt-e a rails telepítése és az alkalmazásunk generálása:

$ rails server

A böngészőnkben a localhost:3000 címre navigálva meg kell jelennie a Rails üdvözlő oldalának.

Scaffolding

A scaffolding nevű művelettel tudunk automatikusan adatbázis sémákat reprezentáló osztályokat generálni.

Példa:

$ rails generate scaffold Post name:string title:string content:text $ rake db:migrate

Ez létrehozza az adatbázisban a posts nevű táblát, ami tartalmazza a fenti oszlopokat, a Post nevű osztályt, ami ennek elérésére szolgál, továbbá a viewhez tartozó html fájlokat a tábla elérésére és módosítására. A második sor azért kell, mert a scaffold parancs valójában nem módosítja az adatbázisfájlunkat, hanem e helyett létrehoz egy "migrate" fájlt. A migrate fájlok segítségével bármikor újra tudjuk építeni az adatbázist.

Végül a routes fájlba bekerül a "resources :posts" sor, ami mindezek elérését teszi lehetővé. A localhost:3000/posts címen megjelenik az összes Post listája, a /posts/<id> címen az id azonosítójú Postot találjuk (például a 2. sorszámút a /posts/2 címen), illetve a /posts/<id>/edit címen szerkeszthetjük azt.

Szerkezet

Ebben a pontban az előbbiek során generált fájlok szerepéről lesz szó. Az átláthatóság érdekében a kevésbé lényeges elemeket nem említjük meg.

Rails konzol

A Rails konzolban kézzel tudunk a létrehozott modellekkel dolgozni, például új elemeket létrehozni, vagy lekérdezéseket futtatni. A Rails konzolt kétféleképpen indíthatjuk el:

$ rails console $ rails console --sandbox

Sandbox módban indítva a Rails minden módosításhoz létrehoz egy rollback bejegyzést, és a konzolból kilépve visszaállítja az eredeti állapotot, egyébként minden változtatás végleges lesz. A Rails konzol a Ruby IRB(Interactive Ruby) shelljét használja, és tetszőleges Ruby vagy Rails utasításokat futtathatunk benne, többek között a következő pontban ismertetett CRUD műveleteket.

Hasznos lehet még a következő parancs, ami a használt adatbázisunk konzolját indítja el (feltéve, hogy telepítve van):

$ rails dbconsole

CRUD

A CRUD a Create, Read, Update, Delete rövidítése, ami az adatbázis-műveletek összefoglaló neve. Ezeket a műveleteket támogatják a scaffoldinggal generált modellek.

Create

Új elemet a new vagy create műveletekkel tudunk létrehozni. A new művelet visszaad egy objektumot, aminek a save függvényét meg kell hívni, hogy az belekerüljön az adatbázisba. Ezzel szemben a create művelet azonnal létrehozza az elemet, így minden kötelezően kitöltendő mezőt meg kell adnunk az argumentumában. Az elem azonosítóját ("id" mező) nem kell megadnunk: az Active Record gondoskodik arról, hogy egyedi azonosítóval kerüljön bele az adatbázisba.
Példák elem létrehozására:

p = Post.new p.name = "Béla" p.title = "Cím" p.content = "Szöveg" p.save p = Post.new(:name => "Béla", :title => "Cím", :content => "Szöveg") p.save p = Post.create(:name => "Béla", :title => "Cím", :content => "Szöveg")
Read

Az elemeket a népszerűbb adatbázisokból már ismerős kulcsszavakkal tudjuk olvasni, mint "where", "order", "count", "limit", stb. Az első sorban lévő kód egyetlen elemet ad vissza, a második és harmadik sorban lévők pedig egy tömböt (feltéve, hogy van ilyen azonosítójú elem).
Példák az adatbázis olvasására:

Post.find(2) Post.find(2, 3, 4) Post.all Post.first Post.last Post.count

A find művelet az elemek azonosítója alapján keres. Van lehetőség bonyolultabb feltételeket is megadni vagy más mezők alapján keresni a where használatával:

Post.where(:title => "Cim") Post.where("title = 'Cim'")

A metódusokat tetszőlegesen láncolhatjuk egymás után:

Post.where(:title => "Cim").order(:name).limit(5)

Ha egy where lekérdezésbe változót szeretnénk behelyettesíteni, akkor azt a ? karakterrel tehetjük meg:

x = "Cím" Post.where("title = ?", x)

Megjegyzés: habár a Rubyból ismert "változó helyettesítése stringbe" módszer is működik, célszerű minden esetben az előző szintaxist használni, ugyanis közvetlen szöveges helyettesítéssel sebezhetővé tesszük a programunkat az SQL injection támadásokkal szemben:

x = "') UNION SELECT id,login AS name,password AS content,1,1,1 FROM users --" Post.where("title = '#{x}'") #ezt ne használjuk

Az első példában az escape karaktereket automatikusan kiszűri a where metódus változóhelyettesítése.

Update

Az attributes/save és update_attributes közötti különbség ugyanaz, mint feljebb a new és a create között.
Példák elemek szerkesztésére:

p = Post.find(2) p.title = "Új cím" p.save p = Post.find(2) p.attributes = {:name => "Béla", :title => "Új Cím", :content => "Szöveg"} p.save p = Post.find(2) p.update_attributes(:name => "Béla", :title => "Új Cím", :content => "Szöveg")
Delete

Példák elemek törlésére:

p = Post.find(2) p.destroy Post.find(2).destroy Post.destroy_all

Az utóbbi az összes elemet törli a táblából.

Modell

A modellünk az adatbázishoz hozzáférést biztosít az alkalmazásunk számára, és felügyeli, hogy ne sérüljenek meg az adatbázis integritását védő invariánsok.
A scaffolding pontban generált Post modell kódja a következő:

class Post < ActiveRecord::Base attr_accessible :content, :name, :title end

A modellt különféle makrókkal és függvényekkel bővíthetjük ki. Egy ilyen makró a fent látható "attr_accessible", ami után felsorolt mezők írhatók lesznek formok és URL paraméterek segítségével. Ha egy lapunk olyan mezőt próbál írni, ami itt nincs felsorolva, akkor hibaüzenetet kapunk.
Hasonló makrók a "before" és "after" makrók. Segítségükkel megadhatunk függvényeket, amelyek mindig lefutnak az előtt vagy az után, hogy egy elemet lekérdezünk, létrehozunk, módosítunk vagy törlünk. A példa kedvéért tegyük fel, hogy a táblánkat kibővítettük egy "date" mezővel, ahol a Post létrehozásának napját tároljuk, de ehez a mezőhöz nem akarunk hozzáférést adni a kliensnek, továbbá azt automatikusan akarjuk beállítani. Ezt a következő változtatással tehetjük meg:

class Post < ActiveRecord::Base attr_accessible :content, :name, :title before_create :fill_date def fill_date self.date = Date.today end end

A fill_date függvény le fog futni minden alkalommal, amikor létrehozunk egy elemet, és beállítja a date mezőt.

Validációk

Hasonló makrókkal tudjuk validálni a mezők értékeit. Ha scaffold segítségével generáltuk a modellhez tartozó view-t, akkor a validáció sikertelensége esetén a lapon automatikusan megjelenik a megfelelő szöveges hibaüzenet, és felszólítja a felhasználót, hogy javítsa ki a megfelelő mezőt.

Példa: name és title mező ne legyen üres, és a title legalább 5 karakter hosszú legyen:

class Post < ActiveRecord::Base validates_presence_of :name, :title validates_length_of :title, :minimum => 5 end

Példa: Egyszerű validáló regex e-mail formátumra:

class Post < ActiveRecord::Base validates_format_of :email, :with => /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/ end

A validációkban leírt feltételek teljesülését kétféleképpen is vizsgálhatjuk. Az eddig látott perzisztáló műveletek (pl save) visszatérési értéke jelzi, hogy az adott művelet sikeres volt-e. A másik lehetőség e műveletek ! jeles változata, amely kivételt dob. Ezek után az objektum errors mezőjében találhatjuk meg a valódációs hibák részleteit.

Példa: Post name mezőjét üresen hagyjuk

p=Post.new p.title=”valami” b=p.save error=p.errors.messages #error értéke: {:name =>[{:can’t be blank]}, b értéke: false
Kapcsolatok

Modellek közötti kapcsolatokra azért van szükségünk, hogy a különböző műveleteket egyszerűbben tudjuk végrehajtani. Képzeljünk el egy példát, amiben két modellünk van: vásárló és megrendelés.

Kapcsolat nélkül az alábbi módon fog kinézni:

class Customer < ActiveRecord::Base end class Order < ActiveRecord::Base end

Ahhoz, hogy egy vásárolóhoz új megrendelést vegyünk fel, ezt kell csinálnunk:

@order = Order.create(order_date: Time.now, customer_id: @customer.id)

Amennyiben egy vásárló törlésénél a megrendeléseit is szeretnénk kitörölni, az alábbi módon tehetjük meg:

@orders = Order.where(customer_id: @customer.id) @orders.each do |order| order.destroy end @customer.destroy

Az ActiveRecordnak köszönhetően ezeket lényegesen egyszerűbben is megvalósíthatjuk, azzal, hogy megmondjuk a rails-nek, hogy a két modell kapcsolatban áll egymással:

class Customer < ActiveRecord::Base has_many :orders, dependent: :destroy end class Order < ActiveRecord::Base belongs_to :customer end

Ezekkel a változtatásokkal egy új megrendelés létrehozása lényegesen rövidebb:

@order = @customer.orders.create(order_date: Time.now)

Még ennél rövidebbé vált a vásárló és a hozzá tartozó megrendelések törlése:

@customer.destroy

Railsben az asszociációk kapcsolatok két ActiveRecord modell között. Az asszociációkat makró-szerű hívásokkal határozhatjuk meg. Például, ha egy modellnél meghatározzunk, hogy belongs_to másik modell, azzal a Railsnek azt mondjuk, hogy egy primary key-foreign key információt tároljon a két modell példányaiban.

Hat különböző kapcsolatot használhatunk Rails-ben:

belongs_to

A belongs_to kapcsolat egy egy-az-egyhez kapcsolatot létesít két modell között, úgy hogy a modellünk egy példánya a másik modell egy másik példányához tartozik. Például, ha van egy vásárló és megrendelés modellünk, és egy megrendelés csak egy vásárlóhoz tartozhat, akkor ezt így definiálhatjuk a megrendelés modellben:

class Order < ActiveRecord::Base belongs_to :customer end
A belongs_to kapcsolatnak mindig egyes számú modell nevet kell használnia, mert a Rails automatikusan kikövetkezteti a modellt a :customer szóból. Ha oda többes számú szót írtunk volna (:customers), akkor az alábbi hibát kaptuk volna: "uninitialized constant Order::Customers".
has_one

A has_one kapcsolat szintén egy egy-az-egyhez kapcsolatot létesít, akárcsak a belongs_to, de más a jelentése. A modellünk minden példánya tartalmaz egy példányt a másik modellből. Például, ha minden vásárlónknak egy számlája lehet csak, akkor az alábbi módon definiálhatjuk ezt:

class Customer < ActiveRecord::Base has_one :account end
has_many

A has_many kapcsolat egy egy-a-többhöz kapcsolatot reprezentál két modell között. Gyakran a másik oldala a belongs_to kapcsolatnak. Az adott modell minden példánya nulla vagy több példányát tartalmazza a másik modellnek. A vásárló és megrendelés példánál maradva, egy vásároló több megrendelését az alábbi módon ábrázolhatjuk a modellek között:

class Customer < ActiveRecord::Base has_many :orders end
A másik modell neve többes számban van a has_many kapcsolatnál!
has_many :through

A has_many :through kapcsolatot több-a-többhöz kapcsolatnál használjuk egy másik modellen keresztül. A kapcsolat azt jelenti, hogy az adott modell példányai összekapcsolhatók nulla vagy több példányával egy másik modellnek, egy harmadik modellen keresztül. Példának nézzünk egy egészségügyi alkalmazást, ahol a betegek időpontot foglalhatnak az orvosokhoz.

class Doctor < ActiveRecord::Base has_many :appointments has_many :patients, through :appointments end class Appointment < ActiveRecord::Base belongs_to :doctor belongs_to :patient end class Patient < ActiveRecord::Base has_many :appointments has_many :doctors, through :appointments end

A has_many :through kapcsolat hasznos, ha egymásba ágyazott has_many kapcsolatokat szeretnénk rövidíteni. Például egy dokumentumnak több fejezete van, a fejezeteknek pedig több bekezdésük.

class Document < ActiveRecord::Base has_many :sections has_many :paragraphs, through: :sections end class Sectiont < ActiveRecord::Base belongs_to :document has_many :paragraphs end class Paragraph < ActiveRecord::Base belongs_to :section end

Azzal, hogy through: :sections-t írtunk, a Rails tudni fogja az alábbit hívást értelmezni

@document.paragraphs
has_one :through

A has_one :through kapcsolat egy egy-az-egyhez kapcsolatot létesít egy másik modellel. Ezzel a kapcsolattal azt mondhatjuk meg, hogy az adott modell példányai összekapcsolhatók egy másik modell példányával egy harmadik modellen keresztül. Például, ha egy vásárlónak egy számlája van és minden számlának egy számla története van.

class Customer < ActiveRecord::Base has_one :account has_one :account_history, through: :account end class Account < ActiveRecord::Base belongs_to :customer has_one :account_history end class AccountHistory < ActiveRecord::Base belongs_to :account end
has_and_belongs_to_many

A has_and_belongs_to_many kapcsolat egy direkt több-a-többhöz kapcsolatot jelent egy másik modellel, közbeeső modell nélkül. Például ha van együttes és helyszín modellünk és egy együttes több helyen is felléphet, de egy helyen több együttes is felléphet, akkor ezt az esetet így jelölhetjük:

class Band < ActiveRecord::Base has_and_belongs_to_many :locations end class Location < ActiveRecord::Base has_and_belongs_to_many :bands end
Hova kerül a belongs_to és hova a has_one?

Ahhoz, hogy egy-az-egyhez kapcsolatot fejezzünk ki két modell között, az egyikhez has_one-t kell adnunk, a másikhoz pedig a belongs_to-t. A különbség, hogy melyiket melyikhez írjuk az, hogy amelyik modellhez kerül a belongs_to, annál lesz a foreign key. Ezen kívül vegyük figyelembe a modellek jelentését is. A has_one kapcsolat azt jelenti, hogy valami hozzád tartozik. Például több értelme van annak, hogy egy vásárlónak van számlája, mint annak, hogy egy számlának van egy vásárlója. Ebben az esetben tehát:

class Customer < ActiveRecord::Base has_one :account end class Account < ActiveRecord::Base belongs_to :customer end
has_many :through vagy has_and_belongs_to_many?

Railsben két különböző lehetőségünk van több-a-többhöz kapcsolatot létesíteni modellek között. Az egyszerűbb megoldás a has_and_belongs_to_many, amivel közvetlenül két modell között teremthetjük meg a kapcsolatot.

class Band < ActiveRecord::Base has_and_belongs_to_many :locations end class Location < ActiveRecord::Base has_and_belongs_to_many :bands end

A másik lehetőségünk a has_many :through. Ebben az esetben egy harmadik modellen keresztül teremtjük meg a két modell közti több-a-többhöz kapcsolatot.

class Band < ActiveRecord::Base has_many :events has_many :locations, through: :events end class Event < ActiveRecord::Base belongs_to :band belongs_to :location end class Location < ActiveRecord::Base has_many :events has_many :bands, through: :events end

A szabály arra, hogy mikor használunk has_many :through-t, hogy ha szeretnénk a harmadik modellel - ami a kapcsolatot teremti a két modell között - is önállóan foglalkozni, akkor ezt használjuk. Ha nincs szükségünk a kapcsoló modellre, mint önálló entitásra, akkor a has_and_belongs_to_many kapcsolatot használjuk.

A has_many :through használatára vagyunk korlátozva, ha szeretnénk validációt vagy extra attribútumokat a kapcsoló modellben.
dependent

Megadhatjuk azt is hogy törlés esetén mi történjen a kapcsolt objektumokkal. Ezt a dependent segítségével tehetjük meg. A következő opciók lehetségesek:

Az alapértelmezett viselkedés a nullify.

Mivel a dependent callbackek segítségével van implementálva, ezért ha olyan művelettel törlünk ami figyelmen kívül hagyja a callbackeket (pl delete) akkor a dependent által megadott akció se fog lefutni.
Mohó betöltés

Általános eset hogy egy lekérdezés eredményeként megkapott objektumok kapcsolt objektumait szeretnék elérni. Ebben az esetben minden eléréskor újabb lekérdezést futtatunk le ami nagy mennyiségű adat esetén jelentősen ronthat a teljesítményen. Ilyenkor érdemes a kapcsolatokat mohón előre betölteni. Erre két lehetőségünk van ami az adatok kapcsolásának módjában különböznek: