2009-10-11 12 views
171

Voglio creare un valore predefinito per un attributo definendolo in ActiveRecord. Per impostazione predefinita ogni volta che viene creato il record, voglio avere un valore predefinito per l'attributo :status. Ho provato a fare questo:Come si crea un valore predefinito per gli attributi nel modello di Rails activerecord?

class Task < ActiveRecord::Base 
    def status=(status) 
    status = 'P' 
    write_attribute(:status, status) 
    end 
end 

Ma al momento della creazione ho ancora recuperare questo errore dal database:

ActiveRecord::StatementInvalid: Mysql::Error: Column 'status' cannot be null 

Quindi presumo il valore non è stato applicato l'attributo.

Quale sarebbe il modo elegante per farlo in Rails?

Molte grazie.

+1

Una più completa e aggiornata risposta è disponibile a http://stackoverflow.com/questions/328525/how-can-i -set-default-values-in-activerecord –

risposta

258

È possibile impostare l'opzione di default per la colonna nella migrazione

.... 
add_column :status, :string, :default => "P" 
.... 

O

È possibile utilizzare un callback, before_save

class Task < ActiveRecord::Base 
    before_save :default_values 
    def default_values 
    self.status ||= 'P' # note self.status = 'P' if self.status.nil? might be safer (per @frontendbeauty) 
    end 
end 
+2

Dovrebbe essere il 'self.status' –

+0

Radar, hai ragione. Lo aggiusterò. – Jim

+15

Normalmente scriviamo self.status || = 'P'. Inoltre, se il campo viene convalidato, prendere in considerazione l'utilizzo della callback before_validation. – tokland

75

È possibile farlo senza scrivere alcun codice :) È sufficiente impostare il valore predefinito per la colonna nel database. Puoi farlo nelle tue migrazioni. Ad esempio:

create_table :projects do |t| 
    t.string :status, :null => false, :default => 'P' 
    ... 
    t.timestamps 
end 

Spero che questo aiuti.

+5

Questa soluzione richiede un dump del database per conservare le informazioni al suo interno. – EmFi

+6

Nota, MySQL non consente valori predefiniti sulle colonne TEXT/BLOB.Altrimenti questa è la soluzione ideale –

+0

Wow non una volta è ': default' menzionato nella guida! http://guides.rubyonrails.org/migrations.html Purtroppo, ho già eseguito la mia migrazione in modo da cercare un modo per ottenere un valore predefinito nel modello. – Chloe

22

La soluzione dipende da alcune cose.

Il valore predefinito dipende da altre informazioni disponibili al momento della creazione? È possibile cancellare il database con conseguenze minime?

Se avete risposto alla prima domanda sì, allora si desidera utilizzare la soluzione di Jim

Se avete risposto alla seconda domanda sì, allora si desidera utilizzare la soluzione di Daniel

Se avete risposto di no a entrambe le domande, probabilmente stai meglio aggiungendo ed eseguendo una nuova migrazione.

class AddDefaultMigration < ActiveRecord::Migration 
    def self.up 
    change_column :tasks, :status, :string, :default => default_value, :null => false 
    end 
end 

: la stringa può essere sostituita con qualsiasi tipo riconosciuto da ActiveRecord :: Migration.

La CPU è economica, quindi la ridefinizione di Task nella soluzione di Jim non causerà molti problemi. Soprattutto in un ambiente di produzione. Questa migrazione è un modo corretto di farlo mentre viene caricata e chiamata molto meno spesso.

+0

Ho appena usato la tecnica di migrazione e sono rimasto sorpreso nel constatare che il valore predefinito era stato applicato di nuovo a tutti i miei dati esistenti. Questo Rails v3.0.1 sqlite in modalità di sviluppo. – SooDesuNe

+0

La maggior parte dei motori DB non funziona in questo modo. È possibile avere un valore predefinito e avere ancora valori nulli. Non sono sicuro che si tratti di Rails o sqlite che presuppone che tutte le righe nulle debbano avere il valore predefinito quando viene applicato il vincolo non nullo. Ma so che altri motori db si soffocheranno se applicherete il vincolo non nullo su una colonna contenente valori nulli. – EmFi

0

Ho trovato un modo migliore per farlo ora:

def status=(value) 
    self[:status] = 'P' 
end 

In Ruby una chiamata di metodo è consentito di avere non parentesi, quindi dovrei chiamare la variabile locale in qualcosa d'altro, altrimenti Rubino lo riconoscerà come una chiamata al metodo.

+1

La tua domanda dovrebbe essere cambiata per soddisfare questa risposta accettata. Nella tua domanda hai voluto impostare un valore iniziale per un attributo. Nella risposta che hai scritto, stai elaborando un valore inserito per quell'attributo. – Jim

+3

Questo non imposta un valore predefinito, imposta un valore su "P" se in qualsiasi momento il valore "stato" è impostato. Inoltre, dovresti usare veramente "write_attribute: status, 'P'" invece – radiospiel

182

Perché ho riscontrato questo problema solo un po 'di tempo fa, e le opzioni per Rails 3.0 sono leggermente diverse, fornirò un'altra risposta a questa domanda.

in Rails 3.0 si vuole fare qualcosa di simile:

class MyModel < ActiveRecord::Base 
    after_initialize :default_values 

    private 
    def default_values 
     self.name ||= "default value" 
    end 
end 
+103

Una parola di cautela; 'after_initialize' significa dopo l'inizializzazione di Ruby. Quindi viene eseguito ogni volta che un record viene caricato dal database e utilizzato per creare un nuovo oggetto modello in memoria, quindi non utilizzare questa richiamata se ciò che si desidera è solo per impostare i valori predefiniti la prima volta che si aggiunge un nuovo record. Se vuoi farlo, usa before_create e non before_save; before_create viene eseguito prima di creare il nuovo record db e dopo la prima inizializzazione. before_save viene chiamato ogni volta che c'è un qualunque tipo di aggiornamento ad un record db. –

+8

Il problema con l'uso di before_create invece di before_save è che before_save viene eseguito per primo. Quindi, quando si vuole fare qualcosa di diverso da impostare i valori predefiniti, ad esempio, calcolare un valore da altri attributi sia in fase di creazione che di aggiornamento causerà problemi poiché i valori predefiniti potrebbero non essere impostati.È preferibile utilizzare l'operatore || = e utilizzare before_save – Altonymous

+2

come controllare se è "persistuto?" E solo impostandolo in caso contrario? – dleavitt

12

vorrei prendere in considerazione utilizzando i attr_defaults trovato here. I tuoi sogni più sfrenati si avvereranno.

+1

Anche se è notevole che questo è essenzialmente un wrapper attorno alla risposta di @ BeepDog: https://github.com/bsm/attribute-defaults/blob/master/lib/attribute_defaults.rb – user456584

73

Quando ho bisogno di valori predefiniti, di solito per i nuovi record, prima che la vista della nuova azione venga visualizzata. Il seguente metodo imposterà i valori predefiniti solo per i nuovi record in modo che siano disponibili durante il rendering dei moduli. before_save e before_createsono troppo tardi e non funzioneranno se si desidera visualizzare i valori predefiniti nei campi di input.

after_initialize do 
    if self.new_record? 
    # values will be available for new record forms. 
    self.status = 'P' 
    self.featured = true 
    end 
end 
+1

Grazie. Questo è quello che stavo cercando, dove i miei campi di input saranno riempiti con i valori predefiniti. – wndxlori

+6

Buon lavoro, questo è quello che generalmente faccio anche io. Tranne che si dovrebbero impostare i valori solo se sono nulli. Altrimenti sovrascriverai i valori quando saranno passati al metodo di creazione, come in 'a = Model.create (stato: 'A', in primo piano: falso)' – asgeo1

0

Nel tuo esempio qui, stai assegnando 'P' a una variabile locale 'stato'. Prova self.status = 'P' per questo re delle cose. Anche se sarebbe meglio usare after_initialize come menzionato in altre risposte.

3

Per i tipi di colonna I supporti di Rails sono pronti, come la stringa in questa domanda, l'approccio migliore è impostare la colonna predefinita nel database stesso come indica Daniel Kristensen. Rails sarà introspettivo sul DB e inizializzerà l'oggetto di conseguenza. Inoltre, questo rende il tuo database sicuro da qualcuno che aggiunge una riga al di fuori della tua app Rails e dimentica di inizializzare quella colonna.

Per i tipi di colonna Rails non supporta fuori dalla scatola - ad es. Colonne ENUM: le rotaie non saranno in grado di analizzare la colonna come predefinita. Per questi casi fai non vuoi usare after_initialize (viene chiamato ogni volta che un oggetto viene caricato dal DB così come ogni volta che un oggetto viene creato usando .new), before_create (perché si verifica dopo la convalida) o before_save (perché si verifica anche durante l'aggiornamento, che di solito non è quello che vuoi).

Piuttosto, si desidera impostare l'attributo in un before_validation su: creare, in questo modo:

before_validation :set_status_because_rails_cannot, on: :create 

def set_status_because_rails_cannot 
    self.status ||= 'P' 
end 
4

come la vedo io, ci sono due problemi che devono affrontare quando ha bisogno di un valore predefinito.

  1. È necessario il valore presente quando viene inizializzato un nuovo oggetto. Usare after_initialize non è adatto perché, come detto, verrà chiamato durante le chiamate a #find che porteranno a un successo nelle prestazioni.
  2. È necessario persistere il valore predefinito quando salvato

Ecco la mia soluzione:

# the reader providers a default if nil 
# but this wont work when saved 
def status 
    read_attribute(:status) || "P" 
end 

# so, define a before_validation callback 
before_validation :set_defaults 
protected 
def set_defaults 
    # if a non-default status has been assigned, it will remain 
    # if no value has been assigned, the reader will return the default and assign it 
    # this keeps the default logic DRY 
    status = status 
end 

Mi piacerebbe sapere perché la gente pensa di questo approccio.

+0

Non sarà eseguito se impostato per saltare la convalida ? –

9

Solo rafforzando Jim's answer

Utilizzando presence si può fare

class Task < ActiveRecord::Base 
    before_save :default_values 
    def default_values 
    self.status = status.presence || 'P' 
    end 
end 
Problemi correlati