Ruby on Rails
HowtoUsareDatabaseMultipli (Version #4)

Rails può gestire più DBs. La sezione “Connection to multiple databases in different models” dell’ ActiveRecord API Doc spiega come impostare una connessione a livello di classe. Guardate anche questo utile consiglio per una corretta configurazione.

Il resto di questo documento esamina come determinare a quale database connettersi su una base per-richiesta.

Così vi volete connettere a più database eh? Cosa siete, pazzi? :)

Naturalmente ci sono pro e contro su questo approccio. Io vi mostrerò come è fatto prima, poi lascierò a voi decidere se è realmente quello che volete fare.

Questa guida vi spiegherà solamente come determinare a quale database connettervi per request.

Questo differisce dal default in cui ogni applicazione rails utilizza soltanto un database di produzione. In questo modo, avete soltanto bisogno di cambiare il vostro file config/database.yml e sarete pronti per partire. Con più database, questo non è più accettabile.

app/controllers/abstract_application.rb definisce una classe chiamata \AbstractApplicationController. Tutti gli altri controllers creati dall’utente ereditano da questo. Andremo a posizionare un before_filter in questa classe per poterci inserire davanti al normale funzionamento di connessione al db. Poichè le connessioni \ActiveRecord funzionano su una base primo-arrivato/primo-servito, se siamo in grado di impostare il establish_connection prima che lo faccia il default, abbiamo vinto.

Capito? Bene.


class ==AbstractApplicationController== < ==ActionController==::Base
  # qui, saltiamo di fronte alla gestione della coda di richieste
  # per eseguire un metodo chiamato hijack_db
  before_filter :hijack_db
 
  # qui, andremo a stabilire manualmente una connessione
  # al database 
  def hijack_db
    # condizione completamente ridicola
    is_odd = Time.now.hour % 2 == 1
 
    # determina il nome del database
    db_name = is_odd ? "dis_odd_db" : "dat_even_db"
 
    # connettiamoci manualmente al db appropriato
		    <a href="http://wiki.rubyonrails.org/rails/pages/ActiveRecord" class="existingWikiWord">ActiveRecord</a>::Base.establish_connection(
      :adapter  => "mysql",
      :host     => "localhost",
      :username => "mr_roboto",
      :password => "secret_secret_i_got_a_secret",
      :database => db_name
    )
  end
end

Naturalmente, vorreste avere una logica di determinazione del database migliore del fatto che l’ora sia dispari o meno. Potreste voler scambiare il database in base all’URL. Per esempio:

  • bears.footballteamsrus.com verrà mappato sul database chiamato “bears”
  • swans.footballteamsrus.com verrà mappato sul database chiamato “swans”
  • etc.

Forse state creando uno strumento per l’amministrazione di database con Rails (Wink, nudge) e avete bisogno di cambiare database in base ad una variabile query string (a.k.a. @params).

Forse, siete un web host e vi piacerebbe scegliere dinamicamente un database in base al sito del cliente che si sta caricando.

Qualsiasi sia la ragione, quel codice lo farà.

Per quale diavolo di motivo dovrei voler fare questo?

Come menzionato, ci sono pro e contro a questo approccio. Ora vi elencherò cosa posso e voglio lasciar decidere a voi.

Contro (perchè tutti elencano sempre prima i pro)

  1. Quando cambiate lo schema del database, dovete ricordarvi di cambiarlo in tutti i databases.
  2. Quando fate il backup, lo dovete fare per più database
  3. Il cache sul database è inesistente (leggete: piccolo degrado di prestazioni)
  4. Creare statistiche e dati aggregati diventa ben più che una sfida
  5. Se modificate la logica di connessione al database, i vostri test unitari e funzionali non saranno in grado di cambiare la connessione verso il database di test
  6. Anche se non modificate la logica di connessione al database, i test unitari e funzionali si aspetteranno di inserire i dati nel database di test principale, e questo potrebbe causare risultati inattesi.

Pro

  1. Isolamento Completo – potete avere schemi di database differenti e differenti funzionalità senza incidere sugli altri
  2. Il codice \ActiveRecord richiede meno relazioni e l’SQL è molto più pulito quando non dovete aggiungere ’WHERE team_id = 123` ad ogni interrogazione (granted, much of this \ActiveRecord handles more than gracefully)
  3. Meno dati ed indici più piccoli significano interrogazioni SQL più veloci (in teoria)
  4. Nessun database centrale significa che ogni database potrebbe essere localizzato su differenti macchine
  5. Fare il back di una singola squadra di calcio (football team)(nell’esempio precedente) significa scaricare soltanto un database e non scrivere sql su sql che dicono `WHERE team_id = 69`
  6. Se andate oltre e distribuite un’applicazione Rails per cliente, mettere offline qualcuno non significa mettere offline tutti gli utenti. In più, se distribuite su più macchine isolate ed una morisse, otterrete un minor numero contemporaneo di chiamate di supporto con utenti irati.
  7. Se avete avuto un successo sfacciato e volete scalare l’applicazione avendo una serie di database in sola lettura ed un solo database in scrittura, questa tecnica potrebbe fare al caso vostro. Guardate \L’articolo su LiveJournal
  8. State creando un’applicazione che integra più database pre-esistenti in un unico front end web.

Quale modalità raccomandate?

David raccomanda fortemente l’approccio a database singolo (link?). Personalmente, concordo. \ActiveRecord è stato disegnato per uno scenario con un solo database. La vita è più semplice con un solo database. Propendete per l’approccio a database singolo a meno che non sappiate di averne realmente bisogno, oppure quando vi volete sentire semplicemente diversi. :)

what-a-day con l’impagabile aiuto di #rubyonrails

Io ho una situazione in cui ho un database che controlla l’autenticazione per una serie di altre applicazioni (alcune rails… alcune no). Nella mia applicazione rails che utilizza questo schema di autenticazione ho il mio database.yml configurato per connettersi al database centrale di autenticazione (chiamato general_security). Poi, dal momento che il database specifico dell’applicazione rails vive sullo stesso server (username, password, host etc… tutti uguale cambia soltanto il nome del database) Gestisco il nome del database all’interno dei files modello.

class Mpq < ==ActiveRecord==::Base
  def self.table_name() "mpq.mpqs" end
end

Questo ovviamente non funziona se username, password o l’host sono differenti. Notate anche che questo non funziona con PostgreSQL. — AustinMoody

Cosa succede se la mia sessione contiene degli oggetti modello?

Io ho riscontrato problemi utilizzando l’esempio precedente cosi com’è. Ho inserito i metodi hijack_db e before_filter nella mio application controller. Il problema era che facevo anche l’autenticazione e mi portavo un oggetto utente nella sessione. Così sono stato obbligato ad inserire “model :user” dentro il mio application controller. Questo ha messo in croce Rails perchè non aveva una connessione impostata.

Così quello che ho dovuto fare è stato quello di appiccicare la tabella Utenti nel DB per il mio ambiente (il DB che vorrei utilizzare se lo fissassi nella configurazione dei DB). E’ brutto, ma funziona. So I always hijack the DB before anything important happens, but I do need to provide tables for model objects carried in the session in the DB setup by database.yml.

Scaricate ActiveRecordCaveats per avere un’idea su come utilizzare database multipli con ereditarietà.
Controllate qui
per degli esempi di codice con database multipli.

Accedere a Databases Multipli

Cosa fare se avete bisogno di connettervi ad un secondo (o più) database dalla vostra applicazione? Ad esempio, dovete utilizzare un database diverso da quello dell’applicazione per verificare una richiesta di autenticazione.

Aggiungete un nuovo record nel vostro file database.yml per ogni database. Per un database “security”, create questa voce per l’ambiente di sviluppo (development), di test (test), e di produzione (production).

security_development: adapter: postgresql database: security host: localhost username: uuuu password: xxxxx

Successivamente, modificate il file ./config/environment.rb, appena sotto la linea ActiveRecord::Base.establish_connection aggiungete una linea per far connettere il vostro modello al nome utilizzato qui sopra.

La variabile RAILS_ENV contiene una di questi valori “development, "test” o “produzione”, in questo modo possiamo utilizzarla per differenziare la connessione in base all’ambiente.

Diciamo di avere un modello “Autorizzazione” che necessita di accedere ad una tabella nel database di sicurezza (security), per fare ciò aggiungiamo:

Autorizzazione.establish_connection "security_#{RAILS_ENV}��?

Dubbi: Dobbiamo far questo per ogni tabella che non si trova nel database di applicazione e a cui abbiamo bisogno di accedere? Creerebbe connessioni multiple oppure creerebbe una cache delle connessioni in base al loro nome? Durante i tests il database security_test verrebbe cancellato e ricostruito?

Condividere Connessioni ActiveRecord Esterne di Dave Thomas che suggerisce questo:

class LegacyBase < ActiveRecord::Base self.abstract_class = true establish_connection “legacy_#{RAILS_ENV}” end class LegacyOrder < LegacyBase … end class LegacyLineItem < LegacyBase … end

Notate il trucco utilizzato per impedire ad ActiveRecord di cercare il LegacyBase nel DB, contrassegnandola come classe astratta.

Sovrascrivere soltanto il nome del database

ActiveRecord::Base.configurations sembra non essere documentata. Può essere utilizzata per sovrascrivere soltanto il nome del database, ma mantenendo lo stesso host e le stesse informazioni di login. Per esempio:

config = ActiveRecord::Base.configurations[RAILS_ENV] class LegacyModel < ActiveRecord::Base establish_connection ( :database => “qualcheAltroDatabase”, :adapter => config‘adapter’, :username => config‘username’, … ) end

Questo potrebbe ritornare utile se sapete che il database che state per utilizzare per memorizzare i dati si trova sullo stesso host ed utilizza le stesse informazioni. (Oppure, se avete bisogno di conoscere le informazioni riguardo la vostra configurazione corrente di accesso al database per altri scopi)

Riguardo le fixtures?

Di seguito spiego come ho fatto in modo che le fixtures utilizzino il database appropriato. Tenete a mente che ci potrebbe essere una maniera migliore per fare ciò. Il nome della mia tabella è ‘parole’ con un modello chiamato Parola e nel file Parola.rb ho
Parola.establish_connection(:genMarkov)

Ora nel nostro unit_test anzichè utilizzare:

fixtures :parole

utilizziamo:

def setup Fixtures.create_fixtures(File.join(RAILS_ROOT, ‘test’, ’fixtures’), ’parole’) { Parola.connection } end

category:Howto

Bath and Shower
Fragrance
Gift Sets
Hair Care
Makeup
Men’s Grooming
Shaving and Hair Removal
Skin Care
Tools and Accessories

Baby Apparel
Baby Bathing & Skin Care
Baby Bedding
Baby Car Seats
Baby Diapering
Baby Feeding
For Moms
Baby Furniture
Baby Gear
Baby Gifts
Baby Health & Baby Care
Nursery Décor
Potty Training
Baby Safety
Baby Strollers

Rails può gestire più DBs. La sezione “Connection to multiple databases in different models” dell’ ActiveRecord API Doc spiega come impostare una connessione a livello di classe. Guardate anche questo utile consiglio per una corretta configurazione.

Il resto di questo documento esamina come determinare a quale database connettersi su una base per-richiesta.

Così vi volete connettere a più database eh? Cosa siete, pazzi? :)

Naturalmente ci sono pro e contro su questo approccio. Io vi mostrerò come è fatto prima, poi lascierò a voi decidere se è realmente quello che volete fare.

Questa guida vi spiegherà solamente come determinare a quale database connettervi per request.

Questo differisce dal default in cui ogni applicazione rails utilizza soltanto un database di produzione. In questo modo, avete soltanto bisogno di cambiare il vostro file config/database.yml e sarete pronti per partire. Con più database, questo non è più accettabile.

app/controllers/abstract_application.rb definisce una classe chiamata \AbstractApplicationController. Tutti gli altri controllers creati dall’utente ereditano da questo. Andremo a posizionare un before_filter in questa classe per poterci inserire davanti al normale funzionamento di connessione al db. Poichè le connessioni \ActiveRecord funzionano su una base primo-arrivato/primo-servito, se siamo in grado di impostare il establish_connection prima che lo faccia il default, abbiamo vinto.

Capito? Bene.


class ==AbstractApplicationController== < ==ActionController==::Base
  # qui, saltiamo di fronte alla gestione della coda di richieste
  # per eseguire un metodo chiamato hijack_db
  before_filter :hijack_db
 
  # qui, andremo a stabilire manualmente una connessione
  # al database 
  def hijack_db
    # condizione completamente ridicola
    is_odd = Time.now.hour % 2 == 1
 
    # determina il nome del database
    db_name = is_odd ? "dis_odd_db" : "dat_even_db"
 
    # connettiamoci manualmente al db appropriato
		    <a href="http://wiki.rubyonrails.org/rails/pages/ActiveRecord" class="existingWikiWord">ActiveRecord</a>::Base.establish_connection(
      :adapter  => "mysql",
      :host     => "localhost",
      :username => "mr_roboto",
      :password => "secret_secret_i_got_a_secret",
      :database => db_name
    )
  end
end

Naturalmente, vorreste avere una logica di determinazione del database migliore del fatto che l’ora sia dispari o meno. Potreste voler scambiare il database in base all’URL. Per esempio:

  • bears.footballteamsrus.com verrà mappato sul database chiamato “bears”
  • swans.footballteamsrus.com verrà mappato sul database chiamato “swans”
  • etc.

Forse state creando uno strumento per l’amministrazione di database con Rails (Wink, nudge) e avete bisogno di cambiare database in base ad una variabile query string (a.k.a. @params).

Forse, siete un web host e vi piacerebbe scegliere dinamicamente un database in base al sito del cliente che si sta caricando.

Qualsiasi sia la ragione, quel codice lo farà.

Per quale diavolo di motivo dovrei voler fare questo?

Come menzionato, ci sono pro e contro a questo approccio. Ora vi elencherò cosa posso e voglio lasciar decidere a voi.

Contro (perchè tutti elencano sempre prima i pro)

  1. Quando cambiate lo schema del database, dovete ricordarvi di cambiarlo in tutti i databases.
  2. Quando fate il backup, lo dovete fare per più database
  3. Il cache sul database è inesistente (leggete: piccolo degrado di prestazioni)
  4. Creare statistiche e dati aggregati diventa ben più che una sfida
  5. Se modificate la logica di connessione al database, i vostri test unitari e funzionali non saranno in grado di cambiare la connessione verso il database di test
  6. Anche se non modificate la logica di connessione al database, i test unitari e funzionali si aspetteranno di inserire i dati nel database di test principale, e questo potrebbe causare risultati inattesi.

Pro

  1. Isolamento Completo – potete avere schemi di database differenti e differenti funzionalità senza incidere sugli altri
  2. Il codice \ActiveRecord richiede meno relazioni e l’SQL è molto più pulito quando non dovete aggiungere ’WHERE team_id = 123` ad ogni interrogazione (granted, much of this \ActiveRecord handles more than gracefully)
  3. Meno dati ed indici più piccoli significano interrogazioni SQL più veloci (in teoria)
  4. Nessun database centrale significa che ogni database potrebbe essere localizzato su differenti macchine
  5. Fare il back di una singola squadra di calcio (football team)(nell’esempio precedente) significa scaricare soltanto un database e non scrivere sql su sql che dicono `WHERE team_id = 69`
  6. Se andate oltre e distribuite un’applicazione Rails per cliente, mettere offline qualcuno non significa mettere offline tutti gli utenti. In più, se distribuite su più macchine isolate ed una morisse, otterrete un minor numero contemporaneo di chiamate di supporto con utenti irati.
  7. Se avete avuto un successo sfacciato e volete scalare l’applicazione avendo una serie di database in sola lettura ed un solo database in scrittura, questa tecnica potrebbe fare al caso vostro. Guardate \L’articolo su LiveJournal
  8. State creando un’applicazione che integra più database pre-esistenti in un unico front end web.

Quale modalità raccomandate?

David raccomanda fortemente l’approccio a database singolo (link?). Personalmente, concordo. \ActiveRecord è stato disegnato per uno scenario con un solo database. La vita è più semplice con un solo database. Propendete per l’approccio a database singolo a meno che non sappiate di averne realmente bisogno, oppure quando vi volete sentire semplicemente diversi. :)

what-a-day con l’impagabile aiuto di #rubyonrails

Io ho una situazione in cui ho un database che controlla l’autenticazione per una serie di altre applicazioni (alcune rails… alcune no). Nella mia applicazione rails che utilizza questo schema di autenticazione ho il mio database.yml configurato per connettersi al database centrale di autenticazione (chiamato general_security). Poi, dal momento che il database specifico dell’applicazione rails vive sullo stesso server (username, password, host etc… tutti uguale cambia soltanto il nome del database) Gestisco il nome del database all’interno dei files modello.

class Mpq < ==ActiveRecord==::Base
  def self.table_name() "mpq.mpqs" end
end

Questo ovviamente non funziona se username, password o l’host sono differenti. Notate anche che questo non funziona con PostgreSQL. — AustinMoody

Cosa succede se la mia sessione contiene degli oggetti modello?

Io ho riscontrato problemi utilizzando l’esempio precedente cosi com’è. Ho inserito i metodi hijack_db e before_filter nella mio application controller. Il problema era che facevo anche l’autenticazione e mi portavo un oggetto utente nella sessione. Così sono stato obbligato ad inserire “model :user” dentro il mio application controller. Questo ha messo in croce Rails perchè non aveva una connessione impostata.

Così quello che ho dovuto fare è stato quello di appiccicare la tabella Utenti nel DB per il mio ambiente (il DB che vorrei utilizzare se lo fissassi nella configurazione dei DB). E’ brutto, ma funziona. So I always hijack the DB before anything important happens, but I do need to provide tables for model objects carried in the session in the DB setup by database.yml.

Scaricate ActiveRecordCaveats per avere un’idea su come utilizzare database multipli con ereditarietà.
Controllate qui
per degli esempi di codice con database multipli.

Accedere a Databases Multipli

Cosa fare se avete bisogno di connettervi ad un secondo (o più) database dalla vostra applicazione? Ad esempio, dovete utilizzare un database diverso da quello dell’applicazione per verificare una richiesta di autenticazione.

Aggiungete un nuovo record nel vostro file database.yml per ogni database. Per un database “security”, create questa voce per l’ambiente di sviluppo (development), di test (test), e di produzione (production).

security_development: adapter: postgresql database: security host: localhost username: uuuu password: xxxxx

Successivamente, modificate il file ./config/environment.rb, appena sotto la linea ActiveRecord::Base.establish_connection aggiungete una linea per far connettere il vostro modello al nome utilizzato qui sopra.

La variabile RAILS_ENV contiene una di questi valori “development, "test” o “produzione”, in questo modo possiamo utilizzarla per differenziare la connessione in base all’ambiente.

Diciamo di avere un modello “Autorizzazione” che necessita di accedere ad una tabella nel database di sicurezza (security), per fare ciò aggiungiamo:

Autorizzazione.establish_connection "security_#{RAILS_ENV}��?

Dubbi: Dobbiamo far questo per ogni tabella che non si trova nel database di applicazione e a cui abbiamo bisogno di accedere? Creerebbe connessioni multiple oppure creerebbe una cache delle connessioni in base al loro nome? Durante i tests il database security_test verrebbe cancellato e ricostruito?

Condividere Connessioni ActiveRecord Esterne di Dave Thomas che suggerisce questo:

class LegacyBase < ActiveRecord::Base self.abstract_class = true establish_connection “legacy_#{RAILS_ENV}” end class LegacyOrder < LegacyBase … end class LegacyLineItem < LegacyBase … end

Notate il trucco utilizzato per impedire ad ActiveRecord di cercare il LegacyBase nel DB, contrassegnandola come classe astratta.

Sovrascrivere soltanto il nome del database

ActiveRecord::Base.configurations sembra non essere documentata. Può essere utilizzata per sovrascrivere soltanto il nome del database, ma mantenendo lo stesso host e le stesse informazioni di login. Per esempio:

config = ActiveRecord::Base.configurations[RAILS_ENV] class LegacyModel < ActiveRecord::Base establish_connection ( :database => “qualcheAltroDatabase”, :adapter => config‘adapter’, :username => config‘username’, … ) end

Questo potrebbe ritornare utile se sapete che il database che state per utilizzare per memorizzare i dati si trova sullo stesso host ed utilizza le stesse informazioni. (Oppure, se avete bisogno di conoscere le informazioni riguardo la vostra configurazione corrente di accesso al database per altri scopi)

Riguardo le fixtures?

Di seguito spiego come ho fatto in modo che le fixtures utilizzino il database appropriato. Tenete a mente che ci potrebbe essere una maniera migliore per fare ciò. Il nome della mia tabella è ‘parole’ con un modello chiamato Parola e nel file Parola.rb ho
Parola.establish_connection(:genMarkov)

Ora nel nostro unit_test anzichè utilizzare:

fixtures :parole

utilizziamo:

def setup Fixtures.create_fixtures(File.join(RAILS_ROOT, ‘test’, ’fixtures’), ’parole’) { Parola.connection } end

category:Howto

Bath and Shower
Fragrance
Gift Sets
Hair Care
Makeup
Men’s Grooming
Shaving and Hair Removal
Skin Care
Tools and Accessories

Baby Apparel
Baby Bathing & Skin Care
Baby Bedding
Baby Car Seats
Baby Diapering
Baby Feeding
For Moms
Baby Furniture
Baby Gear
Baby Gifts
Baby Health & Baby Care
Nursery Décor
Potty Training
Baby Safety
Baby Strollers