Rails - polimorf világ

Azért vannak dolgok, amikkel lehet szépeket szívni.

A feladat valami olyasmi volt, hogy kell egy újrafelhasználható “cím” osztály, ami külön táblára is képeződne le, hogy ne a megfelelő táblákat terheljük feleslegesen, plusz a címek amúgy se mindig kellenek pl. a felhasználókezeléshez. Azért kell újrafelhasználhatónak lennie, mert két különböző dolog is használ címeket a rendszerben.

Nem akarom nagyon húzni az időt, az alábbi kód született rá:

Polimophic user profile
1
2
3
4
5
6
7
8
9
class Address < ActiveRecord::Base
  belongs_to :addressable, :polymorphic => true
end

class User < ActiveRecord::Base
  has_many  :addresses, :as => :addressable
  has_one   :billing_address , :as => :addressable, :class_name => 'Address', :conditions => { :billable => true, :shippable => false }
  has_one   :shipping_address, :as => :addressable, :class_name => 'Address', :conditions => { :shippable => true, :billable => false }
end

Erre kedves barátom azt mondaná, hogy szép baleset. És akkor még nagyon finoman fogalmazna. Nézzük, mik azok a buktatók, amin át lehet esni egy ilyet nézve:

  • A has_many asszociáció ugye azért kell, hogy legyen valami, ami polimorf. Elvben bele lehet suvasztani a has_one mellé is, de nekem egyébként is kell tudnom a címeket egyben lekérni, akkor meg miért ne emelném ki?
  • Az előbbi asszociáció alapján definiálunk két hozzárendelést. Ugye itt a hangsúly a :conditions hashen van, ez mondja meg, hogy a polimorf tömbből mely adatok kellenek (pontosabban, hogy az asszociáció alapján generált query-ben a WHERE feltétel hogyan bővüljön)
  • Az egyik trükk az, hogy mindenképp meg kell adni az osztályt is, ugyanis a has_one asszociáció nem kéri le ezt az infót a hivatkozott polimorf asszociációtól, hanem saját maga áll neki találgatni - természetesen rosszul.
  • A másik trükk pedig, hogy mivel ez egy kétparaméteres, egymást kölcsönösen kizáró feltétel, így mindkettőt meg kell adni, különben létrehozáskor előjöhet olyan csúfság, hogy valaki meghinteli a dupla false-t - vagyis úgy mentünk el pl. egy billing címet, hogy az elvész az :addresses tömb nevű fekete lyukban (van értelme amúgy a dupla false-nak is, csak abból nem billing meg shipping címek lesznek, hanem csak címek). Azaz, mentéskor a billing_address-en keresztül mentünk, de a mentés után a billing_address pont ugyanolyan nil lesz, mint előtte volt vala.

Azért ezzel is sikerült egy jó órát szívni…

RedisHash - this hash is a db!

I started to understand how [Redis] is working and how can I use it in my projects. In my current project I collect some informations with background jobs, and store these informations in Redis. I choosen this way because these data should not be really persistent (that’s why I not decided to use MySQL as storage) because it can be collected quickly again if vanishes (it collected periodically) but I would like to access it quickly.

Redis has a beautiful interface to handle data. However, I usually like if the data storing logic is separated from my business logic. After some thinking I decided to store object in a easiest way I can: I store them in a “smart” hash. Basically, all hashes are key-value store, and Redis is a key-value store too. So, I extended ruby’s Hash class, and overriden some methods…

RubyMine és TeamCity

Már régóta keresek egy olyan IDE-t, ami képes nem csak projektszinten kezelni és szintaxis-kiemelést adni a Ruby/Rails projektjeimnek, de magát a nyelvet is “beszéli”, vagyis van például kódkiegészítés, refaktorálás, figyelmeztetések, etc, etc. Az általam eddig ismert legkomolyabb IDE, a NetBeans is csak elég alapszinten beszélte a Ruby nyelvet - aztán ki is vették belőle a támogatást, jelenleg a plugin folyamatosan haláltusáját vívja, 7.2-höz már meg sem jelent semmiféle verzió.

Egy StackOverflow böngészés során akadtam rá a JetBrains RubyMine nevű IDE-jére. Saját bevallásuk szerint az eszköz képes nyelvi szinten is kezelni a Ruby-t, illetve támogatja az összes nagyobb teszkörnyezetet. Mivel amúgy is épp indítottam egy projektet, úgy gondoltam, adok neki egy esélyt, hát legfeljebb letörlöm, és folytatom az eddigi módon, TextMate és Vim párossal. Szerencsére csalódtam. Pozitívan. Hatalmasat.

A RubyMine ugyanis nem csak egyszerűen jó, hanem - amennyire tudom - a maga nemében jelenleg páratlan a piacon azzal, hogy tényleg hozza azt, amit ígér, vagyis teljes mértékben támogatja a Ruby nyelvet és a Rails keretrendszert, sőt, még ennél is tovább megy, beépített támogatása van a lokalizációk kezeléséhez, grafikusan mutatja, hogy mely mdoeljeink vannak adatbázisban, képes a Rails projekt szerkezetét Rails-aspektusú nézetben mutatni (ahol nem mappák és fájlok vannak, hanem kategóriák, pl. Controllers, Models, Helpers), a kontrollerek akcióihoz hozzá tudja rendelni a nézeteket, partialokat, a refaktor során pedig olyan mélységekbe is lemegy, amiről álmodni sem mertem soha - egy modell átnevezésekor képes a nézetekben is utánanézni az útvonal metódusoknak, és megfelelően átnevezni azokat!

Ezen felül csak úgy mellékesen, képes kezelni az RVM-et, támogatja a Bundlert, a Rails generátorokat, a Rake taszkokat, SCSS-t meg CoffeeScript-et, … meg ilyen kis semmiségeket is.

Az egyetlen probléma vele a kissé hiperaktív kódkiegészítése, ha leírok egy nyilat, rögtön ugrana rá, pedig én magamtól szoktam szóközt is ütni utána, de azzal meg elfogadom az első lehetőséget is egyben - ami általában rossz ötlet. Biztos le lehet valahogy beszélni róla, majd utánanézek (de ha valaki tudja a tuti megoldást, a komment doboz mindig nyitott az ilyesmikre :-) ), egyelőre még elviselem.

Jelenleg folyamatosan használom az épp aktív projektemhez, teljesen jól szolgál, bár még képességeinek csak szerény részét használom. Most egyelőre a 30 napos próbaidőszakban vagyunk (dicséretes, hogy ez alatt sem ugrál föl valami kis ablak indításkor, hogy még 22.45 nap van hátra, és mostazonnal vedd meg - ez rém undorító, ha tetszik, úgyis megveszem - főleg, mert lejár -, ha meg nem tetszik, akkor ez a kapacitálás csak ront a dolgon), ám azt gondolom, hogy a 66 euro a személyes licencért abszolút megéri.

Amit nem használok belőle (sem), az a Git integráció, nekem a git még mindig parancssoros dolgot jelent, egyszerűbb kiadni a git co feature-awesome-stuff parancsot, mint GUI-ban kikattintgatni. Legalábbis szerintem.

A JetBrains honlapját tallózgatva (ismét) ráakadtam a TeamCity nevű CI szerverre is, ezzel egyszer volt már egy futó kapcsolatom, de valamiért nem sikerült telepíteni, és a végén Jenkins lett belőle. Most ismét kapott tőlem egy lehetőséget, hogy elinduljon, és meggyőzzön. Ezúttal mindkettő sikerült is neki.

A TeamCity-ről azt érdemes tudni, hogy egy olyan Continous Integration szerver (továbbiakban CI szerver), melyet nagyon egyszerű kezelni, és nagyon jól áttekinthető felületet ad, még egy felületes szemlélő számára is (ezt muszáj volt elsütni :-) ).

A RubyMine fejlesztők blogján pedig találtam egy írást, mely borzasztóan felcsigázta a kíváncsiságomat. Azt merték ugyanis állítani, hogy TeamCity beépítetten képes kezelni a RSpec és a Cucumber tesztek kimenetét, arrol riportot készít, hibáról értesít. Plusz, a TeamCity beépítetten támogatja az XMPP alapú notifikációt, amit pl. a Jenkins csak pluginnel tud.

Sajnos a TeamCity egyik hiányossága, hogy ő viszont már csak korlátozott mértékben képes kezelni a Bundlert: képes felismerni a létét, és azon keresztül futtatni a rake taszkokat, de már pl. egy bundle install -t képtelen elengedni, azt kézzel kell egy külön Build Step-ben, viszonylag kacifántosan megoldani. Végül sikerült, de azért nem voltam felhőtlenül boldog. Viszont az első teszteredmények feledtették velem az ezen a téren történt csalódást, mert pontosan azt kaptam, amit elvártam: mindenféle külön konfiguráció nélkül jelenítete meg a Cucumber es RSpec tesztek eredményét, ráadásul egy egészen barátságos felületen.

Tényleg nem JetBrains reklámnak szánom ezt a postot, de azért annyira érződik mindkét terméken, hogy ezeket olyanok fejlesztik, akik láttak már közelről Rails projektet, meg RVM-et, meg ilyesmit… például a Jenkins Ruby pluginjénél nem volt ilyen érzésem.

Logging with Logstash

In these days I started looking up for an opensource, easy-to-use central logging system. I read a lot of articles on the internet, and I found two good solution, Graylog2 and LogStash. Both system can operate as syslog server and store our logs in database. I tried both system, and I experienced both have small problems - but some of these problems are different.

Graylog2

Graylog has a very good-looking interface and a relative easy configuration file. It has a lot of parameters, but the default configuraiton file is well-documented and easy to read.

Graylog uses a MongoDB for store some small stuffs and ElasticSearch for store and search logs. The main advantage of this solution is the separated web interface. It can be installed anywhere, it just need a some small configurations to connect to same ES/Mongo instances as Graylog server uses.

The web ui makes easy to search and filter logs, backlist some entries, and create a “channels” what is a named filter setting, where you can follow what happening in a simple context.

But as I started use web interface and the server itself in everyday tasks, I found two problems:

  • Web interface is does not make easy to write filters, because nor the interface, nor the documentation not describe how filters will be matched against messages. Because these filters are simple regular expressions, it is needed to know how to search some specific things. I sucked with it a lot of time until I found a method to find something. It is need a lot of improvement.
  • Graylog seems does not take care about its input. After a week I found logging is stopped working. After some investigation, I discovered the ElasticSearch eaten a lot of resources and generating a huge log. After reading log I found the ElasticSearch is broken on some invalid characters in index files. I tried to restart ES server and leave to recover itself for a hour but no luck, it stuck in recovering state. Because I am not know well ES and graylog I started ask Google about these problems, but I did not find a good solution for me. So I decided to move on from Graylog.

LogStash

LogStash is a more robust logging solution. It is similar as syslog-ng but it does not restrict itself to working as Syslog server (with other words: get infos from input like syslog server) but it can chew anything what is a text stuff. It can use a lot of things as a log source, e.g.:

  • Syslog server (it can act as syslog server, but see below)
  • Plain file
  • Raw TCP/UDP socket
  • Output of any executable
  • IRC/Jabber channels
  • Twitter
  • Stdin
  • Windows Eventlog
  • and so on…

And it has a numerous output format too. What is important for me is it can use ElasticSearch and some No-SQL databases as log-storing backend.

It has a built-in web interface for searching in log records, but Kibana is a little better Web interface to search against ElasticSearch, it makes searching/filtering easier.

My current configuration is very easy:

  • There is a central syslog server, provided by LogStash
  • All my server is logging here via RSyslog (default syslog server of Debian Linux)
  • LogStash is configured to store log messages in ElasticSearch after some parsing

Basically, I followed the official recipe for this setup except one thing: somehow I couldn’t get working the date parser in the way what recipe says. So I tricked it out.

First, I created an Rsyslog config part, what I uploaded to /etc/rsyslog.d on every server:

/etc/rsyslog.d/to-logstash.conf
1
2
3
$template LOGSTASH,"<%PRI%>%timegenerated:::date-rfc3164-buggyday% %HOSTNAME% %APP-NAME%: %msg:::drop-last-lf%\n"
$ActionForwardDefaultTemplate LOGSTASH
*.*   @1.2.3.4:514

Note the %timegenerated:::date-rfc3164-buggyday% directive. The documentation of Rsyslog says it is basically pads a day number with zero if it is smaller than 10. It is introduced to emulate a syslog-ng ‘bug’.

After it I created a following configuration for logstash:

LogStash configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
input {
  tcp {
    port => 514
    type => syslog
  }

  udp {
    port => 514
    type => syslog
  }
}

filter {
  grok {
      type => "syslog"
      pattern => [ "<%{POSINT:syslog_pri}>%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{PROG:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" ]
      add_field => [ "received_at", "[email protected]}" ]
      add_field => [ "received_from", "[email protected]_host}" ]
  }
  syslog_pri {
      type => "syslog"
  }

  date {
      type => "syslog"
  #    syslog_timestamp => [ "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
      syslog_timestamp => [ "MMM dd HH:mm:ss" ]
  }

  mutate {
      type => "syslog"
      exclude_tags => "_grokparsefailure"
      replace => [ "@source_host", "%{syslog_hostname}" ]
      replace => [ "@message", "%{syslog_message}" ]
  }
  mutate {
      type => "syslog"
      remove => [ "syslog_hostname", "syslog_message", "syslog_timestamp" ]
  }
}


output {
  elasticsearch {
    index => "syslog-%{+YYYY.MM.dd}"
    embedded => false
  }

  #stdout { debug => true debug_format => json }
}

# vim: ts=2 sw=2 et

Note the commented out date mapping. This is what original recipe says but I always got an exception related to parser and DateFormat what complaining about invalid data in the date. With buggyday hack on Rsyslog side I made dates 2-digit long, so it will always be parsed, I hope. It will reveals at least Nov 1.

The last output stuff is just for debugging, it displays the parsed object in JSON format, this is a way how can you check your all filter working correctly.

I sucked with parsing the message because my old rsyslog template has a typo and I could not imagine, what failing. After some digging, I found jls-grok RubyGem is contains a same parser what LogStash uses, so you can check your filters via IRB. Just make sure you using 1.9.3 ruby, because Ruby 1.8 throws a SyntaxError when you try require 'grok-pure' (what is recommended, because the simple ‘grok’ is searching a C lib).

Simple example for testing Grok filter
1
2
3
4
5
6
7
8
9
10
11
require 'rubygems'
require 'grok-pure'

grok = Grok.new

# I extracted patterns dir from jar package
Dir.glob('patterns/*').each { |f| grok.add_patterns_from_file f }
s="Oct 27 19:01:09"
pattern = '%{SYSLOGTIMESTAMP:syslog_timestamp}'
grok.compile(pattern)
p grok.match(s).match.to_a # => ["Oct 27 19:01:09", "Oct 27 19:01:09", "Oct", "27", "19:01:09", "19", "01", "09"]

You can use exactly same pattern as you use in LogStash configuration file.

Note: If you see a _grokparsefailure tag in your tag list, then you do something wrong. This means the filter what you using in first filter does not match correctly on your syslog message from Rsyslog. The good news is LogStash stop evaluating filters on the first failing in debug mode, and leave @message untouched (if you see the second mutate from end, you can see we replace @message with the content of syslog_message, and remove unneccessary fields in the last mutate filter). If somehow it falls through the grok filter and removes the original (raw) message, then simply comment out the last two filter by prefixing lines with a hashmark.

As you see in the config use an external ES server, not the embedded one, because I currently have a working and configured ES instance, and I would like to use that. No other reason to not use embedded ES server: if you currently not have an ES instance, then simply use what shipped with LogStash.

The embedded web interface of LogStash is a bit tricky thing, because I could not start as documentation says via the java command, I had to extract the jar file and run standalone to check its user interface. Don’t expect something cool: It is just a textfield with a button and contains a link with a basic query string what filters messages generated today.

Conclusion

I think Graylog is a great logging solution, but it needs some love for prevent crashing ES server with invalid characters in the stored data. But the web interface and easy configuration makes it very, really very lovely.

LogStash is a good solution if you want to handle multiple log sources or you want to validate/manipulate your log messages or you want to distribute logs to multiple destinations. I think LogStash is a little overkill if you just want a central syslog server, however - this is working as expected. So I took my two cent on LogStash and not on Graylog - even if I would like to prefer Graylog.

Guard - projektjeink testőre

Nem olyan régen ismerkedtem meg a guard nevű gemmel, ami egy nagyon hasznos kis szerszám annak, aki Rails fejlesztésre adja a fejét, de plug-injei révén például PHP fejlesztők is profitálhatnak a tudásából.

GitHub powa

Egy régóta dédelgetett álom vált ma valóra: végre megtaláltam a megfelelő környezetet arra, hogy felköltözzem a GitHub-ra. Már régóta tervezgettem a költözést, egészen pontosan azóta, amióta olvastam a GitHub Pages szolgáltatásról - már nem tudom, hol. Mert igazából ez az, ami nekem kell: egy szövegfájl, amibe beleírom a gondolataimat, néhány egyszerű trükk, amivel megformázom, és végül pár gombnyomás, amivel beküldöm.

Valamit másképpen

A HUP-on bedobott szavazás gondolkodtatott el azon, hogy csinálnék-e valamit alapvetöen másképpen, ha újrakezdhetném az életemet. Nagyon csábító a lehetöség, mert hibáztam, nem is egyszer, sokszor, néha kicsit, néha nagyot.

Akkor és ott úgy éreztem, hogy nagyon gyorsan meg kellene nyomni a backspace gombot, és nekifutni megint. De utólag visszanézve mindig rájövök, hogy hülyeség lett volna. Mert akkor egy csomó jó dolog sem történt volna meg velem az életben.

Talán egyetlen esemény van, amit megakadályoznék, bár jelenleg nem tudom felmérni, mekkora hatása lenne: a tavalyelött nyári eseményeket. Az az egy hónapnyi kínlódás lehet, hogy megspórolható lett volna. Nem biztos, de lehet.

De a többin nem változtatnék. Söt, még csak azt sem szeretném, hogy egészségesen szülessek. Nem volt egyszerü a betegségemmel, ez tény. És azt is tudom, hogy a környezetemnek sem volt egyszerü. De ha nem annak születek, akinek, ma nem lehetek az, aki, és nem lehetnek azok a barátaim, akik. A két legjobb barátomat úgy ismertem meg, hogy interneten kértek tölem segítséget. Egészségesként talán nem ülnék ennyit a gép elött, hiszen nagyon impulzív és aktív emberke vagyok egyébként, de ez azzal jár, hogy lemaradnék mindenröl, ami most fontos. Emiatt gondolom azt, hogy - bár kellemetlen, de - nem baj az, ami van velem. Én egy picit más vagyok, nálam ez az “egészséges”.

Választanék-e másik családot, környezetet? Aki közelebbröl ismer, tudja, hogy nálam még ez sem egy egyszerü eldöntendö kérdés. De úgy gondolom, nem. Nem volt sima életem, voltak göröngyök, bukkanók, hegyek és völgyek. Volt, amikor nagy volt a sötét, volt amikor sok volt a fény. De hiszem, hogy ezek is segítettek olyanná formálni, amilyen most vagyok. 

Csak néhány dolog van, amiken változtatnék. Pár életvitelbeli apróság. Pár apró tárgy, amire jobban vigyáznék. És néhány ember, akikkel egy kicsit több idöt töltenék, kicsit jobban szeretném öket, mert úgy érzem, adós maradtam. De ez minden.

Continous deployment, vagy afféle

Az ember a netet olvasgatva érdekes dolgokra tud bukkanni, illetve sokszor nagyon jó ötleteket lehet kapni pusztán a mások írásaiból.

Info@ írt arról, hogy náluk hogy került bevezetésre az, hogy a tesztelök gombnyomásra deployolnak a teszt rendszerre verziókat. Bár nálam max 1 tesztelö lenne, és öt sem szeretném a Jenkins közelébe engedni, azért elgondolkodtatott a dolog: mi volna, ha a Jenkins egyrészt monitorozná a deploy ágat (ez nálam a master ág, amint azt már említettem), ezen végrehajtana egy deployment tesztet is a staging környezetre (ezzel lenne egy folyamatosan friss teszt környezet), illetve adott esetben meg lehetne kérni öt arra, hogy, legyen már kedves az éles szerverre is kidobni az aktuál kódot.

Ez amennyire bonyolultan hangzik, annyira egyszerü volt megoldani. Egyrészt a staging és az éles szerverre fel kellett venni a Jenkins saját SSH kulcsát (már régebbröl volt ilyenem, mert a Jenkins slave-k kezeléséhez kell), másrészt kellett két olyan jobot csinálni, amelyek elvégzik a fent említett két müveletet.

Utána fogtam, lemásoltam azt a jobot, ami a normál (nem deployolós) teszteket csinálja, hozzácsaptam még egy lépést, miszerint deployoljon is a staging környezetbe. Illetve átírtam, hogy a master ágat figyelje. Annyit még egyszerüsítettem a dolgon, hogy egyik tesztnél sem csinálok coverage mérést, mert ez csak lassítja a tesztek futtatását, de érdemi eredményt nem ad hozzá a teszthez, hiszen itt a tesztek lefutása is csak azért fontos, hogy ha valaki véletlenül rácomittolt volna az éles ágra, mielött lefutna a deployment teszt, az még kibukjon. De általában senki nem szokott.

Az éles deploy job annyiban volt különbözö ettöl, hogy itt még kikapcsoltam a pollingozást is, mert ezt kézzel (na jó, XMPP-n keresztül) fogom triggerelni, és csak akkor, ha a staging környezetbe rendben kikerült a kód, illetve mindenki jóváhagyta a müködést is. Egyébként az elözö job másolata volt.

Így gyakorlatilag az van, hogy amikor release van, akkor szokás szerint mergelek egyet a master ágra, ezt felnyomom a git repóba, majd nekiállok türelmetlenül lesni az IM kliensem ablakát. Némi idö elteltével a Jenkins beszól, hogy (jó esetben) az aktuális job átment minden lépésen, és készen áll az élesítésre. Ekkor megkérem a jenkins-t, hogy deployoljon (jenkins, build project-deploy now), ez átfuttatja még egyszer a teszteket, majd kiteszi az éles szerverre a kódot, újraindítja a thin webszervert hozzá, és kész is. Mivel mind a teszteknek mind a deploymentnek része az adatbázis frissítése is (rake db:migrate), így nem fordulhat elö, hogy a db séma miatt halunk meg.

Azért itt még nincs vége a történetnek, a következö lépés az, hogy ellenörizni kell, hogy az app fel is bootolt-e tisztességesen. Sajnos a thin webszervernek megvan az a kellemetlen tulajdonsága, hogy csak azt várja meg, amíg ö maga meg a környezete felbootol, mielött forkol, magát a rails appot nem várja be. Vagyis lehet, hogy úgy kapsz HTTP 500-at, hogy amúgy a thin azt jelezte vissza, hogy sikeresen elindult (a deployment utolsó mozzanata az appszerver újraindítása). Egyelöre most a kézi meló is jó, a jövöben meg sztem lesz egy negatív teszt, egy fix oldalra fogok egy curl-t hívni, ha HTTP 200 jön vissza, már jók vagyunk.

Qt fordítás kezdőknek

Naszóval, ez a cikk itten arról fog szólni, hogy hogyan fordítsunk magunknak forrásból Qt-t úgy, hogy az ne piszkítsa össze a rendszerünket.

Hogy ez miért is jó?

  • A rendszerünk régi Qt-t szállít, mi pedig egy újhoz szeretnénk fejleszteni
  • A Qt nyelvi fájlokba akarunk beletúrni