Je li f()==5 i 5==f() isto?

Vi koji ste radili sa C-olikim jezicima setićete se jedne od konvencija pri pisanju if-uslova, da se vrednost sa kojom se promenljiva poredi piše sa leve strane znaka jednakosti, a promenljiva s desne (if 5 == i naspram uobičajenog if i == 5), zbog česte greške da se umesto dvostrukog znaka jednakosti stavi jednostruki, i odradi dodela vrednosti promenljivoj s leve strane, za šta se neki kompajleri ne bi bunili.

Samo naoko sličan problem javlja se ponekad i u Rubiju, ali ne zbog moguće greške u kucanju, već zbog prirode znaka jednakosti.

Analizirajući performanse jedne skripte profajlerom, primetio sam da se u listi pozivanih metoda neočekivano javlja metoda Time#<=> (koja opet, iz ko zna kog razloga, zove BasicObject#method_missing), iako sam bio prilično siguran da u kodu nemam poređenje objekata klase Time. Detaljnijim pregledom shvatio sam da se poziv te metode javlja pri proveri rezultata jedne metode, tj. u otprilike ovakvom koodu:

return if m() == false

(Pre nego što počnete prozivku zbog onog == false, reći ću da mi je bila potrebna eksplicitna provera za false, tj. nisam hteo da mi se uslov ispuni ako je rezultat metode nil, već samo false.)

Ispostavilo se da metoda m u nekim slučajevima vraća objekat klase Time. (Ok, još malo pravdanja: metoda m je zapravo metoda send na jednom objektu, i može zapravo pozivati različite metode, otud nije baš uvek izvesno šta će se naći kao vraćena klasa.) S obzirom da je u Rubiju manje-više sve ili objekat ili poziv metode nekog objekta, ono == je takođe poziv metode. U slučaju klase Time, ovaj operator ne vrši jednostavno poređenje referenci, pa se usled toga == kome je poziv metode sa leve strane različito ponaša od onog gde je sa leve strane npr. false. Ok, da ne grešim dušu, kood se ponaša isto, ali se izvršava različitom brzinom, što opet nekad može da bude bitno.

Da bi se izbegla nepotrebna logika vezana za konkretnu implementaciju metode == levog objekta, dovoljno je promeniti redosled:

return if false == m()

false je u Rubiju instanca klase FalseClass, i njeno == je isto što i obično Object#==, odnosno jednostavna provera jesu li u pitanju isti objekti.

Uvod u memcached i cache_fu

Šta je memcached?

Memcached je besplatan distributivni sistem za keširanje objekata u memoriji, otvorenog kôda i visokih performansi. Po sistemu ključ-vrednost, u njemu se mogu keširati rezultati poziva baze podataka, API-ja, renderovanja stranice….

Šta je cache_fu?

cache_fu je Rails plugin za keširanje ActiveRecord objekata koji koristi memcached. Cache_fu je pre nekoliko godina napravio Chris Wanstrath (koautor GitHuba)

Čemu sve ovo?

Kada je reč o performansama, poznato je da je za većinu web aplikacija najveće usko grlo baza podataka. Zato je potrebno minimalizovati komunikaciju aplikacije i baze što je više moguće. Cache_fu je jedan od načina da se to realizuje.

Šta treba da znam o memcachedu da bih počeo da koristim cache_fu?

  • Posle instalacije, opcije za startovanje su opisane pozivom: memcached -h. Ovo je uglavnom dovoljno: memcached -d -m 512
  • Kada se “ubije” memcached, sav keš nestaje
  • Postoji ograničenje od 1MB za vrednost, po ključu (ovo uglavnom ne treba da vas brine)

Kako se koristi?

SAVET: podesite logovanje u konzoli kako biste mogli mogli da posmatrate AR/cache_fu interakciju sa memcachedom i bazom kada budete isprobavali kôd iz primera u Rails konzoli. Neki od primera koji slede imaju prikazan upravo takav izlaz. Kôd prikazan u primerima možete pronaći na githubu.

cache_fu API (sastavljen od svega nekoliko metoda) se metodom acts_as_cached ubacuje u željenu klasu.

class Article < ActiveRecord::Base
  acts_as_cached

Počnimo od keširanja ActiveRecord#count metode. Prebrojavanje redova u bazi je skupa operacija čak i sa srednje velikim tabelama (50K+ redova). Setite se da se prebrojavanje uvek izvršava prilikom paginacije. Railsov plugin will_paginate podržava :total_entries opciju preko koje je moguće proslediti (keširanu) vrednost broja redova u tabeli.

>> Article.cached(:count)   
==> Got Article:count from cache. (0.00048)
  SQL (0.5ms)   SELECT count(*) AS count_all FROM "articles" 
==> Set Article:count to cache. (0.00056)  
=> 4

Svaki sledeći poziv će “zaobići” bazu

>> Article.cached(:count)
==> Got Article:count from cache. (0.00038)
=> 4

Keš se invalidira expire_cache metodom

>> Article.expire_cache(:count)
==> Deleted Article:count from cache. (0.00041)
=> true

after_create/destroy se može iskoristi za expire_cache(:count) poziv. Takođe moguće je iskoristiti :ttl (time-to-live) opciju prilikom keširanja. To će automatski invalidirati taj keš svakih 15 minuta.

>> Article.cached(:count, :ttl => 15.minutes)

Metodom cached je moguće keširati rezultat svake metode klase/instance ActiveRecord objekta. Parametri metoda se prosleđuju koristeći :with.

>>  Article.cached(:random, :with => 1, :ttl => 5.minutes)
==> Got Article:random:1 from cache. (0.10194)
  Article Load (0.9ms)   SELECT * FROM "articles" ORDER BY random() LIMIT 1
==> Set Article:random:1 to cache. (0.00397)
=> [#<Article id: 6, title: "Haml tips", body: "...",...>]

Jedan izuzetak je metoda find kojoj se prosleđuje ID. I ona se može keširati na isti način ali postoji prečica koja pruža i automatsko invalidiranje keša.

>> Article.get_cache(1)
==> Got Article:1 from cache. (0.00034)
  Article Load (0.4ms)   SELECT * FROM "articles" WHERE ("articles"."id" = 1) 
==> Set Article:1 to cache. (0.00065)
=> #<Article id: 1, title: "Rails console tips", body: "...", ....>

Da bi ovakav keš automatski bio invalidiran:

class Article < ActiveRecord::Base
  acts_as_cached
  after_save :expire_cache # postoji default implementacija

Relacije se keširaju takođe sa cached metodom

>> Article.find(1).cached(:comments)
  Article Load (0.5ms)   SELECT * FROM "articles" WHERE ("articles"."id" = 1) 
==> Got Article:1:comments from cache. (0.00033)
==> Set Article:1:comments to cache. (0.00451)
=> [#<Comment id: 1, article_id: 1, ...>, #<Comment id: 2, article_id: 1, ...>, #<Comment id: 3, article_id: 1, ....>]

Naravno, kod relacija je potrebno malo dodatne invalidacije:

class Comment < ActiveRecord::Base
  acts_as_cached
  belongs_to :article
  after_save    :expire_cache
  after_destroy :expire_cache

  def expire_cache
    super
    article.expire_cache(:comments)
  end

# ... (probajte da kreirate neki komentar 
# za neki članak i posmatrajte log) ...

Za kraj, evo primera kada je potrebno keširati nešto što ima neki vremenski okvir (npr. “Današnji članci”).

class Article
  acts_as_cached

  after_save do |record|
    expire_cache_by_date(record.created_at.to_date)
  end

  after_destroy do |record|
    expire_cache_by_date(record.created_at.to_date)
  end

  def self.expire_cache_by_date(date)
    expire_cache("on_date:#{date}")
  end

  def self.on_date(date)
    find(:all, :conditions => ['date(created_at) = ?', date.to_date])
  end

  def self.cached_todays
    # ubaciće datum u ključ keša 
    # (npr. "Article:on_date:2010-01-30").
    # na taj način izbegnuto je eksplicitno  
    # invalidiranje keša nakon ponoći. 
    cached(:on_date, :with => Date.today)
  end

Linkovi

Etikete ↓ Dejan Simić # Komentari

23. januar 2010. Beograd

U nekada popularnom Beogradskom kafiću na prvom spratu Dom Omladine okupila se grupa kreativih mladih ljudi koji vole Ruby u svim pojavnim oblicima.

Pored toga što smo bili najbrojnija ekipa prisutna u okolnim građevinskim radovima dobro sakrivenom kafiću to nije bilo dovoljno da se prisutno osoblje namoli da pojačaju grejanje. 

I pored nepovoljnih meteo uslova nešto malo više od 3 sata je prošlo kao tren u inicijalnom upoznavanju i razgovru o nama omiljenoj temi - Ruby. Dejan Simić je imao najavljenu javnu prezentaciju ali iz opravdanih razoga istu nije spremio što nam je omogućilo da i dalje razmenjujemo ideje i informacije.

Hladnoća i glad su oglasili završetak ovog skupa. Jednom rečju bilo je SUPER i jedva čekam da se ponovo okupimo.

7. decembar 2009., Novi Sad

Stara četvorka (Slobodan, Darko, Marko i Milan) su se ovog puta našli u kancelariji Rendered Texta. Druženje je propratilo kraće Markovo izlaganje o njegovom skorašnjem radu sa Memcacheom. Rails pruža odličnu integraciju (vidi npr Rails.cache.fetch metodu) sa ovim rešenjem za keširanje rezultata upita na bazu, celog ili parcijalnog sadržaja stranica itd. Glavni programerski zadatak u takvim situacijama jeste doći do ispravne i efikasne logike koja će što više koristiti keš, a da pri tome prikazani sadržaj uvek odgovara najnovijem skupu podataka. Sve se na kraju srećno završilo jednom zanimljivom partijom stonog fudbala.

23. novembar 2009., Novi Sad

Sinoć je naše skromno udruženje dobilo dva nova člana - Dejana Simića i Mladena Jablanovića, koji su zapucali čak iz Beograda. Time je broj članova postao veći za 50% u odnosu na lane.

Dok oni nisu stigli, Slobodan je okupljenima pokazivao Clearance, Rails engine za autentifikaciju. Radi se o rešenju koje je zanimljivo zato što se ne radi sa kodom koji je generisan, već potpuno modularno. Kod u korisničkoj aplikaciji može parcijalno da preklapa ponašanje plugina. Tako je dovoljno, na primer, napisati svoj UsersController sa custom signup akcijom - ona ima pravo prvenstva u odnosu na endžinov UsersController#signup, dok sve ostalo ostaje po endžinu.

Potom je usledilo međusobno upoznavanje, kao i diskusije o projektima kojima se prisutni bave, iskustvima u firmama i radnim praksama. Zaista puno tema. Nisu više svi prisutni Rails ljudi - Jablan koristi Ruby na poslu kao alat za obradu podataka i pravljenje izveštaja.

Novosađani planiraju da se ponovo okupe za dve nedelje, a dogovoreno je da za oko mesec dana održimo novi međugradski skup u Beogradu (detalji će još biti objavljeni na ovom sajtu). Opšti je zaključak da nam stvar ide u dobrom smeru i nadamo se da će nas tom prilikom biti još više.

Prvi sastanak - 16. novembar 2009.

Danas, u 19h u Beza kafeu pored novosadskog Štranda, je bio prvi zvanični Ruby.rs sastanak na kome su prisustvovali Darko Fabijan, Marko Anastasov, Milan Dobrota i Slobodan Kovačević. Svi su bili vrlo raspoloženi i posle nešto ćaskanja o mogućim zajedničkim akcijama u budućnosti, složili se da za početak ustanovimo redovna okupljanja. Želimo da, pored slobodne diskusije na Ruby i Rails teme, tim prilikama neko od prisutnih pokaže neku zanimljivu tehniku, biblioteku, manje poznatu mogućnost jezika ili prosto ukaže na neke nove i zanimljive alate.

Darko je ubrzo otvorio editor i browser i pokazao kako je za potrebe trenutnog klijentskog posla počeo da piše Cucumber integracione testove sa Selenium back endom.

Cucumber je već poznat alat za pisanje funkcionalnih i integration testova. Bazira se na RSpecu i odlikuje ga izuzetno čitljiv DSL koji se sastoji od gotovo prirodnih rečenica na engleskom jeziku. Ti iskazi (ili koraci, steps) se u pozadini zasnivaju na regularnim izrazima, i grupišu se u scenarija koji se mapiraju na Webrat komande.

Velika pogodnost korišćenja Selenium back enda je to što je moguće testirati kompleksne scenarije koji uključuju Ajax pozive, pošto se sve odigrava u programiranom browseru. Sa druge strane, mana ovog pristupa je sporo izvršavanje.

Slobodan je potom pokazao nešto unit test koda koji koristi Shoulda. Shoulda je proširenje za Test/Unit iz standardne biblioteke i pruža mogućnost definisanja konteksta za izvršavanje unit testova, kao i neke dodatne makroe. Ukupno sa ovim gemom je moguće pisati ekspresivnije i kraće unit testove bez prelaska na novi DSL kao što je RSpec. Usledila je i kraća diskusija o mocking i stubbing alatima koje niko od prisutnih još nije koristio u praksi, te smo se svi složili da ih svakako treba probati.

Naredno okupljanje je zakazano već za sledeći ponedeljak, a plan je da se potom održava svake druge nedelje.