Dynamisk opprette typer basert på XML

Én av fordelene med å programmere i et dynamisk språk er at man kan opprette typer og objekter basert på data som er ukjent i designtime. Som for eksempel å opprette sterkt typede objekter basert på XML. For hvorfor skal jeg jobbe med XML når jeg kan jobbe med objekter?!

Men da jeg begynte å skikke på XML-biblotekene i Ruby ble jeg faktisk litt skuffet.., det fungerte nemlig ikke sånn out-of-the-box. Jeg fant en rekke open source prosjekter som tenkte i samme bane som meg, men kvaliteten var svært varierende. Jeg fikk derfor lyst til å forsøke selv, mest får å trene på meta-programmering.

Jeg har ikke kommet så langt som jeg ønsket enda – denne formen for programmering er krevende, ettersom man har to lag med kode man må holde rede på (den man skriver, og den som oppstår når programmet kjører). Jeg har derimot noe som fungerer godt nok for et lite eksempel, så da er det på tide med en blogpost.

Sett at jeg har XML’en som følger nedenfor. Jeg ønsker Ã¥ kunne parse denne med en generisk modul som gir meg en typet kolleksjon tilbake.

1 <people>
2   <person category=“Programmer”>
3     <firstname>Bob</firstname>
4     <lastname>Martin</lastname>
5   </person>
6   <person category=“Programmer”>
7     <firstname>Kent</firstname>
8     <lastname>Beck</lastname>
9   </person>
10 </people>

For akkurat dette eksempelet vil jeg ha ut en objektstruktur som den nedefor (den kunne vært bedre, men det er dette koden min produserer for øyeblikket). XmlObject opprettes ved Ã¥ sende inn en xml. NÃ¥r parsingen er ferdig har objektet fÃ¥tt en people-property. People er en kolleksjon av Person – med en count-propery og en iterator. Person-klassen som er opprettet har fÃ¥tt properties tilsvarende det vi finner i xml’en: category, firstname og lastname.

XmlObject_spec (2)

Her er et eksempel på bruk av XmlObject, tatt fra enhetstestene jeg brukte for å utvikle løsningen.

Utdrag fra xml_object_spec.rb:

28   def setup
29     @xml_obj = XmlObject.new PEOPLE_XML
30   end
—- snip —-
66   def test__usage_sample
67     list_of_names = []
68     @xml_obj.people.each do |person|
69       list_of_names << #{person.lastname}, #{person.firstname} (#{person.category})
70     end
71     list_of_names.first.should == Martin, Bob (Programmer)
72     list_of_names.last.should == Beck, Kent (Programmer)
73   end

PEOPLE_XML er dataene gjengitt i starten av denne artikkelen. Som du ser har @xml_obj etter setup fått en people-property jeg kan bruke til å iterere og hente ut data om personene. I linje 71 og 72 verifiserer jeg at objektene inneholdt det jeg forventet.

En fullstendig kodelisting av løsningen vil forvirre mer enn jeg ønsker.., jeg vil heller trekke frem et par ting som illustrerer hva som er mulig i forhold til in-memory kodegenerering i runtime. Nedenfor ser du for eksempel en metode jeg bruker for å opprette en ny klasse basert på et XML element.

15     def define_class_and_create_instanse name
16       name = name.capitalize
17       unless XmlObject.const_defined? name.to_sym
18         eval %(
19           class #{name} < Array
20             alias :count :length
21           end
22         )
23       end
24       klass = eval XmlObject::#{name}
25       [klass, klass.new]
26     end

Metoden tar inn navnet pÃ¥ elementet, og sørger for at det begynner med en stor bokstav (linje 16). Hvis klassen ikke er definert fra før (linje 17) oppretter jeg en ny klasse som arver fra Array ved Ã¥ evaluere strengen i linje 19 til 21. Alias (linje 20) brukes for Ã¥ døpe length-propertien til Array om til “count”, som er det jeg ønsker objektet skal ha.

I linje 24 bruker jeg eval igjen for å få tak i en referanse til den nye klassen. Til slutt returnerer jeg klassen samt en ny instans (i et array á to elementer – linje 25).

Neste eksempel er metoden jeg bruker for å opprette attributter (properties) og sette verdien for et gitt objekt.

50     def add_attr name, klass, instanse, value
51       attr_name = name.to_sym
52       klass.send(:attr_accessor, attr_name) unless klass.respond_to? attr_name
53       instanse.send #{attr_name}=.to_sym, value
54     end

I linje 52 sender jeg meldingen “attr_accessor” til klassen – se pÃ¥ det som Ã¥ kalle en statisk metode som heter attr_accessor. Denne metoden brukes til Ã¥ opprette en get/set-property (reader/writer accessor i Ruby) – attr_name bestemmer navnet pÃ¥ propertien.

Legg ogsÃ¥ merke til at kallet til “send” er etterfulgt av en unless-statement. Hvis klassen allerede har denne propertien, ved at den responderer pÃ¥ property-navnet, trenger jeg ikke opprette propertien pÃ¥ nytt.

I linje 53 setter jeg verdien på den nye propertien ved å bruke send-metoden til en instans av klassen. Det jeg egentlig gjør er å kaller setter-metoden for propertien – navnet på den er like navnet på propertien pluss et erlik-tegn (=). Andre parameter til send er verdien som skal settes.

Dette er bare et lite utdrag av de mange metodene og teknikkene man har tilgjengelig for introspection og dynamisk metaprogrammering i Ruby. Jeg håper dette gir et lite innblikk i de enorme mulighetene man har i dynamiske språk som Ruby i forhold til mere statiske språk.

Les også: Du MÅ beherske et dynamsik språk | Hvilket dynamisk programmeirngsspråk du skal lære deg

Kategorier: Ruby.
RSS feed for kommentarene. Tilbaketråkk.

Skriv en kommentar

Torbjørn Marø

Torbjørn er systemutvikler og et aktivt medlem av .NET-miljøet i Bergen. Dette er hans blog.

Mulig relaterte linker

Siste kommentarer


Torbjørn: Takk for kommentar og skryt.., det setter jeg alltid pris pÃ...

Morten: Heisann Fulgt bloggen din lenge, bra saker! Ad Boo og...

Vidar Lund: Eg må sei eg synes YAGNI har mye for seg... ;) http://en.w...

Torbjørn: Ehh.. Nei! ;) Regnet ikke med at ideen min var orginal, men...

Odd Rune: Ehh.. Nagios?...

Arneth: «de definerer en Monad som noe som er en Monad» … det er...

Lasse V. Karlsen: Jeg har lenge forsøkt å forstå Monads og relatert kunnska...

 Hold deg oppdatert

Søk i bloggen

  • Follow me on Spotify

    Kategorier

  • .net ninja (26)
  • Bøker (10)
  • Diverse prosjekter (26)
  • Erlang (7)
  • F# (2)
  • Hardware (1)
  • Jobb (64)
  • kjempekjekt.com (16)
  • LISP/Clojure (11)
  • NNUG / community (39)
  • O/RM & databaser (9)
  • Off topic (112)
  • OO-design/clean code (19)
  • Podcasts (10)
  • Polyglot (27)
  • Ruby (19)
  • Silverlight / RIA (3)
  • Software/verktøy (16)
  • Softwareutvikling (14)
  • Testing / TDD (25)
  • the contiki strip (13)
  • User experience (3)
  • WCF (3)
  • Webutvikling (20)
  • WPF (9)
  • WTF (4)
  • Abonner via epost

    Om du vil kan du få alle nye blogposter tilsendt til din epost. Abonner nå, det er kjempeenkelt!

    Mine bokmerker

    Meta