Ajax, Observers e autorefresh

Ciao a tutti, mi servirebbe una dritta su come implementare
l’autorefresh di una lista di posts.

L’idea e’, che se un utente sta guardando una pagina che lista i suoi
post e quelli del suo gruppo, dovrebbe vedere arrivare i nuovi post
eseguiti dagli altri in tempo reale, cioe dinamicamente senza fare il
reload della pagina.

Non ho molta dimestichezza con Ajax, ma dovrebbe avere a che fare con i
metodi di ActionView::Helpers::PrototypeHelper:

* build_callbacks
* build_observer
* button_to_remote
* evaluate_remote_response
* form_remote_for
* form_remote_tag
* link_to_remote
* method_option_to_s
* observe_field
* observe_form
* options_for_ajax
* periodically_call_remote
* remote_form_for
* remote_function
* submit_to_remote
* update_page
* update_page_tag

la pagina che devo refreshare e’ l’index dell’home controller:


root@webby2066:/var/rails/flitter# cat ./app/views/home/index.html.erb

<%= render :partial => “flits_list”, :locals => { :flits => @flits }%>

che sostanzialmente chiama un parziale che implementa ogni singolo post:


root@webby2066:/var/rails/flitter# cat
./app/views/home/_flits_list.html.erb

    <% flits.each do |flit| %> class="first"<% end %>> <%= image_tag flit.user.gravatar_url %>
    <%= link_to flit.user.username, user_flits_path(flit.user.username) %> <%= h flit.message %>
    <%= distance_of_time_in_words_to_now(flit.created_at) %> ago
    <% end %>
--------------------------------------------------------------

in un controller diverso da home, ho un “evento trigger” che e’
flit.save! e dovrebbe scatenare il refresh di home/index.html.erb

Non mi e’ del tutto chiaro pero’ se c’e’ un metodo tra quelli elencati
prima, idoneo a rilevare l’evento trigger flit.save! che in sostanza
salva un nuovo post nel DB.

Ringrazio in anticipo per ogni suggerimento
Ciao Luca

Il 10 marzo 2010 10.51, Luca G. Soave [email protected] ha scritto:

metodi di ActionView::Helpers::PrototypeHelper:

  • observe_form
  • options_for_ajax
  • periodically_call_remote
  • remote_form_for
  • remote_function
  • submit_to_remote
  • update_page
  • update_page_tag

Ciao,

premetto che, dopo un periodo iniziale irto di difficoltà, ho
abbandonato del tutto rjs e i PrototypeHelper (e veramente anche
Prototype, perché scoprire jQuery è stato un po’ come quando cerchi di
lavorare a tentoni nel buio e a un certo punto accendono la luce).

Quello che ti serve è periodically_call_remote; la cosa più pulita è
avere un metodo di un qualche controller che ti restituisca un json
contenente una variabile boolean che ti dica se è necessario il
refresh oppure no, in modo che la risposta sia sempre rapidissima.

Poi, quando ti serve fare il refresh, potresti fare una nuova chiamata
all’index di home (ad esempio con remote_function), da cui farti
restituire solo il partial da rimpiazzare (la funzione potrebbe anche
usare rjs, volendo…).

La questione è se vuoi fare tutto con gli helper di rails o se vuoi
fare tutto con javascript.

pietro

puoi anche farti spedire direttamente i nuovi post in html e aggiungerli
alla lista oppure ricaricare semplicemente la lista:

new Ajax.PeriodicalUpdater(‘items’, ‘/items’, {
method: ‘get’, frequency: 3, decay: 2
});

Poi, quando ti serve fare il refresh, potresti fare una nuova chiamata
all’index di home (ad esempio con remote_function), da cui farti
restituire solo il partial da rimpiazzare (la funzione potrebbe anche
usare rjs, volendo…).

Il 10 marzo 2010 13.01, Alessandro S. [email protected] ha
scritto:

puoi anche farti spedire direttamente i nuovi post in html e aggiungerli
alla lista oppure ricaricare semplicemente la lista:

new Ajax.PeriodicalUpdater(‘items’, ‘/items’, {
method: ‘get’, frequency: 3, decay: 2
});

Sì, è anche questa una soluzione ma, avendola provata in passato, non
fa un bell’effetto, specie se la lista non è leggerissima.

Magari prova, in effetti così è molto semplice, ma se vedi che lo
sfarfallìo è percettibile, riconsidera l’idea di modificare il dom
solo quando serve.

pietro

Grazie a Pietro e Alessandro x le info.

Mi pare di capire che sia periodically_call_remote che
Ajax.PeriodicalUpdater siano metodi “attivi” di polling, per cosi’ dire.

Non esiste, che voi sappiate, un Observer di ActiveRecord in grado di
mandare una callback per una data commit sul DB (.save)? Oppure una cosa
tipo after_create() di ActiveRecord::Callbacks che scatena un evento
ajax?

Sto solo immaginando, perche’ non ho esperienza in questi metodi.

Il problema e’ che dentro il partial,
./app/views/home/_flits_list.html.erb c’e diversa roba, tra qui una
query al db <%= h flit.message %> che verrebbe eseguita nel mio caso
ogni 2 secondi x tutto il periodo in cui il browser utente rimane aperto
sulla pagina, moltiplicato x tutti gli utenti collegati nell’unita’ di
tempo … poco scalabile credo.

Sto solo speculando, ma mi piacerebbe innescare un certo numero di
soluzioni percorribili e di confronti.

Grazie a tutti
Luca G.S.

Luca G. Soave wrote:

Grazie a Pietro e Alessandro x le info.

Mi pare di capire che sia periodically_call_remote che
Ajax.PeriodicalUpdater siano metodi “attivi” di polling, per cosi’ dire.

Non esiste, che voi sappiate, un Observer di ActiveRecord in grado di
mandare una callback per una data commit sul DB (.save)? Oppure una cosa
tipo after_create() di ActiveRecord::Callbacks che scatena un evento
ajax?

Direi che si tratta di polling, un aggiornamento sul server come può
avere relazioni con i clients che interrogano il database? Impostando un
intervallo di check moderato, non credo si appesantisca troppo il server

On 10/03/2010 17:04, Luca G. Soave wrote:

in teoria, non ha molto senso mettere mettere una logica che implica
AJAX dentro un modello, altrimenti non sarebbe un pattern MVC :stuck_out_tongue:

in caso puoi farlo nel controller: riprendendo l’esempio di after_create
di ActiveRecord, vorrà dire che nella action ‘create’ farai qualcosa
dopo aver salvato il modello.

per quanto riguarda il partial, puoi usare una chiamata AJAX
periodicamente, come ti hanno già consigliato. per quanto riguarda la
scalabilità, di sicuro puoi usare il “fragment caching”. Dovrai
impostare un observer sul modello per aggiornare il contenuto del
partial cachato.

se poi la scalabilità è davvero un problema urgente, potresti valutare
qualcosa tipo Redis (un db chiave/valore che fa anche molte altre cose:
Google Code Archive - Long-term storage for Google Code Project Hosting.) + redis_store (plugin Rails per fare il
caching usando Redis: GitHub - redis-store/redis-store: Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks). Tra le
altre cose, entrambe i progetti sono scritti da programmatori italiani
:slight_smile:

ciao,
A.

Il 10 marzo 2010 17.04, Luca G. Soave [email protected] ha scritto:

Grazie a Pietro e Alessandro x le info.

Mi pare di capire che sia periodically_call_remote che
Ajax.PeriodicalUpdater siano metodi “attivi” di polling, per cosi’ dire.

Non esiste, che voi sappiate, un Observer di ActiveRecord in grado di
mandare una callback per una data commit sul DB (.save)? Oppure una cosa
tipo after_create() di ActiveRecord::Callbacks che scatena un evento
ajax?

Come ti hanno già detto, no, non si può. Alcune applicazioni web
implementano eventi push tenendo aperto un socket (java o flash) ma, a
parte il fatto di avere una dipendenza in più, non credere che, per il
server, mantenere (potenzialmente) migliaia di connessioni aperte sia
un carico trascurabile.
Altre applicazioni simulano un evento push “trattenendo” le risposte a
eventi poll, ma è una cosa ancora più complicata e pesante per il
server.

La cosa più semplice e leggera è appunto quella suggeritati (vedi sotto).

Sto solo immaginando, perche’ non ho esperienza in questi metodi.

Il problema e’ che dentro il partial,
./app/views/home/_flits_list.html.erb c’e diversa roba, tra qui una
query al db <%= h flit.message %> che verrebbe eseguita nel mio caso
ogni 2 secondi x tutto il periodo in cui il browser utente rimane aperto
sulla pagina, moltiplicato x tutti gli utenti collegati nell’unita’ di
tempo … poco scalabile credo.

Ecco, qui c’è un problema grossissimo, indipendentemente da ajax e
quant’altro: le view non devono fare query. Le view devono solo
mostrare, nient’altro.

Poi, c’è un altro problema, e cioè che il partial è complesso. E anche
stavolta, questo è un problema indipendentemente da ajax.
Semplicemente, scomponi il partial in pezzi più piccoli, e uno di
questi pezzi si limiterà a mostrare la lista.

una query che verrebbe eseguita nel mio caso ogni 2 secondi

Due secondi mi sembra un’esagerazione, in genere cinque (o anche
dieci) secondi vanno più che bene; sei proprio sicuro che sia
indispensabile? Molte applicazioni mostrano una buona reattività pur
tenendosi appunto intorno ai dieci secondi e oltre.

Se ripetere tante volte la stessa query è davvero un problema, ci sono
tecnologie ad hoc per la persistenza dello stato (che in questo caso è
nuovi post sì / nuovi post no), come ti ha suggerito Andrea, che però
ha senso indagare solo se è vera almeno una delle due ipotesi:

a) hai già centinaia di utenti prenotati che terranno aperto il
browser sulla tua applicazione (ma questo probabilmente significa che
hai bisogno di una certa infrastruttura, ad esempio una banda “seria”
e un server come si deve);

b) hai voglia di imparare a usare redis :slight_smile:

Tra l’altro, come dicevo nell’email precedente, usare una chiamata
ajax che ridisegni migliaia di volte la stessa identica lista è uno
spreco enorme, ed è un carico sia per il server (che finché non
finisce di trasmettere una risposta resta impegnato, ed ha così minore
capacità di risposta per gli altri) che per il client, e l’utente si
ritroverebbe un computer inchiodato da un browser che ridisegna lo
stesso pezzo di pagina; anche l’effetto visivo non è il massimo,
perché lo sfarfallìo si vede.

Ma questo non è necessario: quando mostri la pagina passi al
javascript l’ora corrente (lato server), così il javascript può
interrogare il server chiedendo: è cambiato niente dall’ora X? Se la
risposta è no, non fai niente; se è sì, si ridisegna la pagina.

pietro

Ok Pietro, grazie per i numerosi consigli e le opzioni che mi hai
prospettato. Mi sembra di avere un quadro un po piu’ completo sullo
“stato dell’arte”.

Provo a distillare il mio compromesso sugli elementi forniti.

Ciao
Luca