proto prototype
Geplaatst door Remco van 't Veer do, 12 okt 2006 00:12:00 GMT
Laatst sprak ik iemand die rails gebruikt om prototypes te bouwen voor J2EE web applicaties omdat het zo lekker snel werkt. Het uiteindelijke project wordt uitgewerkt in J2EE. Ten opzichte van J2EE is er met rails erg snel iets in elkaar te zetten maar wat doe je als je een prototype nodig hebt voor een rails site?
Je kan natuurlijk gewoon beginnen met rails fancy_shit
en los gaan. Vroeg in de ontwerp fase van een project vind ik dat een beetje overkill, als je alleen maar een beetje wilt goochelen met een paar objectjes om gevoel te krijgen voor het probleem wat je probeert op te lossen.
Gelukkig is er iets veel beters dan rails voor dit soort dingen!
Camping is klein web framework gemaakt door een van de grootste helden uit de Ruby gemeenschap, Why The Lucky Stiff. Het past MVC, kent ActiveRecord, doet Markaby en is reloadable (ofwel edit je app en druk op reload in je browser), maar belangrijker nog geeft me de mogelijkheid om in een enkele soepele beweging een web appje uit m’n mouw te schudden!
Wat hebben we allemaal mee gekregen? Laten we beginnen bij de C van Controller, wat heb je immers aan een web app als je geen browser requests kan verwerken. Daar gaan we:
gem install camping
En ons eerste appje:
require 'camping' Camping.goes :Proto module Proto module Controllers class Index < R '/' def get 'wij gaan kamperen!' end end end end
Dat is alles! Een complete applicatie welke “wij gaan kamperen!” roept. Opslaan in een file met naam proto.rb
, opstarten met camping proto.rb
, dan je browser naar http://localhost:3301/
en tevreden toekijken.
Ons appje heeft een controller Index
welke gekoppeld is aan '/'
. Voor GET
requests op deze locatie wordt de tekst “wij gaan kamperen!” terug geleverd. Maar dat was duidelijk toch?
Laten we '/'
uitbreiden zodat we gegevens aan de controller kunnen doorgeven:
module Controllers class Index < R '/(.*)' def get(arg) arg = 'kamperen' if arg.empty? "wij gaan #{arg}!" end end end
Nu kunnen we naast kamperen ook gaan fietsen!
Het argument aan R
wordt geïnterpeteerd als reguliere expressie waarvan eventuele groepen (in reguliere expressie aangegeven met haakjes) doorgegeven worden als argumenten voor de controller methode. Een echte expressie doorgeven mag ook maar '/(.*)'
is beter leesbaar dan /\/(.*)/
of %r{/(.*)}
volgens mij.
Het resultaat van het laatste statement (return value dus), wordt gerenderd. Maar we kunnen het ook een beetje op leuken met markaby:
module Controllers class Index < R '/(.*)' def get(arg) arg = 'kamperen' if arg.empty? @mission = "wij gaan #{arg}!" html do head { title @mission } body { h1 @mission } end end end end
en dat kunnen we verhuizen naar de V van View:
module Controllers class Index < R '/(.*)' def get(arg) arg = 'kamperen' if arg.empty? @mission = "wij gaan #{arg}!" render :index end end end module Views def index html do head { title @mission } body do h1 @mission text 'hallo uit de view!' end end end end
en dat kan zelfs beter met de magische layout
method:
module Views def layout html do head { title 'hallo uit de layout!' } body { self << yield } end end def index h1 @mission text 'hallo uit de view!' end end
En meerdere pagina’s? Hoe knoop je die dan aan elkaar? Hebben we helper methoden zoals url_for
? Natuurlijk! De constructie:
class Index < R '/'
heeft z’n tegenpool voor de view:
a 'index', :href => R(Index)
dit levert een URL naar de gewenste controller. In actie:
module Proto module Controllers class Index < R '/' def get h1 'index' a 'detail', :href => R(Detail) end end class Detail < R '/detail' def get h1 'detail' a 'index', :href => R(Index) end end end end
De index pagina linkt naar detail en anders om.
Nu met objectjes goochelen, een simpele site met artikelen erop. Nee, een volledig CMS!
module Proto ARTICLES = [] module Controllers class Index < R '/' def get; render :index; end end class Add < R '/add' def get; render :add; end def post ARTICLES << { :title => input.title, :body => input.body } redirect Index end end end module Views def layout html do head { title 'a proto-prototype' } body { self << yield } end end def index a 'nieuw artikel', :href => R(Add) ARTICLES.each do |article| h1 article[:title] text article[:body] end end def add form :method => 'POST' do input :type => 'text', :name => 'title' br textarea :name => 'body', :rows => 10 input :type => 'submit' end end end end
Interessant in deze versie van ons appje is de post
methode in de Add
controller. Ten eerste de naam van de methode: post
, yep hier worden alle POST
requests afgewerkt. Geen gemier met request.post?
of speciale routing, GET
en POST
hebben gewoon hun eigen methoden. Verder wordt de input
methode gebruikt om request parameters op te pakken en wordt de redirect
methode gebruikte om weer terug naar de begin pagina te springen.
Alle artikelen worden bewaard in een array genaamd ARTICLES
en artikelen zijn niet meer dan een hash met twee velden; title
en body
. Dat is genoeg voor een klein experimentje maar zodra je wat aan je applicatie verandert ben je je data kwijt, niet zo handig. Gelukkig hebben we de M nog van Model.
Zoals ik al eerder aangaf kan je op de camping met ActiveRecord spelen. Standaard gebeurt dat in een SQLite database die je niet zelf aan hoeft te maken. Lees eerst de waarschuwing van _why over de installatie van de sqlite3-ruby
gem als je SQLite nog niet in combinatie met Ruby gebruikt hebt.
Laten we ons CMS persistent maken! Een PCMS! Eerst introduceren we models met migratie script (ja echt!) in onze Proto
module:
module Proto module Models class Article < Base; end class BasicVersion < V 1.0 def self.up create_table :proto_articles do |t| t.column :id, :integer t.column :title, :string t.column :body, :text end end def self.down drop_table :proto_articles end end end def self.create Models.create_schema end ..
De self.create
methode verzekert dat de tabellen ook echt aangemaakt worden, deze methode wordt altijd bij het laden van je appje aangeroepen. De naam BasicVersion
heb ik zo uit m’n duim gezogen, het is gewoon de naam van die migratie. De namen van de tabellen worden geprefixed met de naam van je app, platgeslagen.
Nu kunnen we de ARTICLES
array weg halen, in de post
methode van de Add
controller kunnen we nu Article.create!(input)
schrijven en in de index view kan ARTICLES.each
vervangen worden door Models::Article.find(:all).each
. Reload in de browser et voilá een PCMS!
Reload doet migraties?? Ja zeker! Voeg maar eens een migratie toe:
.. class TimeStamped < V 1.1 def self.up add_column :proto_articles, :created_at, :datetime end def self.down remove_column :proto_articles, :created_at end end ..
en een maak created_at
zichtbaar in de index view:
.. def index a 'nieuw artikel', :href => R(Add) Article.find(:all).each do |article| h1 article.title p article.created_at p article.body end end ..
Nieuw artikeltje maken, kijken in het overzicht en tevreden toekijken. Misschien is die wel een leuk idee voor een rails plugin, reloadable-migrations-in-development?
Wat hebben we nog meer in onze knapzak zitten? Hmm, M van Model hebben we gehad, V van View ook behandelt en C van Controller volgens mij ook.. hoewel hoe zit het eigenlijk met sessies? Het kan toch niet zo zijn deze ik zelf cookie headers moet gaan zetten?
Tuurlijk niet! Als je sessies echt nodig hebt kan je ze gewoon meenemen net als een opblaasboot.
require 'camping/session' ..
Een uitbreiding voor je create
method:
.. def self.create Camping::Models::Session.create_schema Models.create_schema end ..
En het in mixen van sessies:
.. module Proto include Camping::Session ..
Nu heb je in je controllers een @state
variable voor elke bezoeker, te gebruiken als hash. Eindelijk kunnen onze PCMS beschermen tegen mensen die helemaal niets te melden hebben!
.. class Login < R '/login' def get form :method => 'post' do label 'wachtwoord' input type => 'password', :name => 'password' input type => 'submit' end end def post if input.password == 'douwe dabbert' @state[:login] = true redirect Add else h1 'no passeran' end end end ..
En de add controller beschermen we met:
.. class Add < R '/add' def get if @state[:login] render :add else redirect Login end end def post Article.create!(input) if @state[:login] redirect Index end end ..
Nou, ik zou zeggen “enterprise ready”! Een SPCMS klaar voor ISO certificering! Download de code en doe er je voordeel mee!
Camping is wat mij betreft een vonkel in de robijn en dat in minder dan 4k! In veel opzichten vind ik het veel prettiger werken dan rails; migraties tijdens reload, automatische creatie van je database, alles in een enkele file, alles kort en bondig.. Ik zal het nog een keer zeggen _why is mijn grote held!
Sqlite werkte bij mij pas nadat ik in de index method niet Article.find(:all).each maar Models::Article.find(:all).each schreef. Wat deed ik fout?
Ik ben er nu achter dat je eigenlijk @articles moet initialiseren in de controler. Deze kun je dan gebruiken in de view.
Oeps, sorry.. Ik dacht dat ik echt alle code getest had. Blijkbaar wordt
Modules
wel in automatischControllers
geinclude maar niet in jeViews
.