Come impostare i valori predefiniti in Rails?


108

Sto cercando di trovare il modo migliore per impostare i valori predefiniti per gli oggetti in Rails.

La cosa migliore a cui riesco a pensare è impostare il valore predefinito nel newmetodo nel controller.

Qualcuno ha qualche input se questo è accettabile o se c'è un modo migliore per farlo?


1
Quali sono questi oggetti; come vengono consumati / utilizzati? Vengono utilizzati durante il rendering delle viste o per la logica del controller?
Gishu,

3
Se stai parlando di un oggetto ActiveRecord, devo dirti che non esiste una soluzione sensata al problema dei "valori predefiniti". Solo hack folli, e gli autori di rails non sembrano pensare che la funzionalità valga la pena (incredibile come solo la community di rails è ..)
Mauricio

Poiché le risposte accettate e la maggior parte si concentrano su ActiveRecords, presumiamo che la domanda originale riguardasse AcitveRecords. Quindi possibile duplicato di stackoverflow.com/questions/328525/...
Ciro Santilli郝海东冠状病六四事件法轮功

Risposte:


98

"Corretto" è una parola pericolosa in Ruby. Di solito c'è più di un modo per fare qualsiasi cosa. Se sai che vorrai sempre quel valore predefinito per quella colonna su quella tabella, impostarli in un file di migrazione DB è il modo più semplice:

class SetDefault < ActiveRecord::Migration
  def self.up
    change_column :people, :last_name, :type, :default => "Doe"
  end

  def self.down
    # You can't currently remove default values in Rails
    raise ActiveRecord::IrreversibleMigration, "Can't remove the default"
  end
end

Poiché ActiveRecord rileva automaticamente le proprietà della tabella e della colonna, questo farà sì che lo stesso valore predefinito venga impostato in qualsiasi modello che lo utilizzi in qualsiasi app Rails standard.

Tuttavia, se desideri impostare valori predefiniti solo in casi specifici, ad esempio è un modello ereditato che condivide una tabella con altri, un altro modo elegante è farlo direttamente nel codice Rails quando viene creato l'oggetto del modello:

class GenericPerson < Person
  def initialize(attributes=nil)
    attr_with_defaults = {:last_name => "Doe"}.merge(attributes)
    super(attr_with_defaults)
  end
end

Quindi, quando si esegue un GenericPerson.new(), verrà sempre trasmesso l'attributo "Doe" fino a, a Person.new()meno che non lo si sostituisca con qualcos'altro.


2
Provalo. Funzionerà su nuovi oggetti modello chiamati con il .newmetodo class. La discussione di quel post sul blog sulla chiamata diretta di ActiveRecord .allocateriguardava gli oggetti del modello caricati con i dati esistenti dal database. ( Ed è una pessima idea che ActiveRecord funzioni in questo modo, IMO. Ma non è questo il punto.)
SFEley

2
Se fai un passo indietro per leggere di nuovo la domanda del poster originale, Nikita, e poi i miei commenti in ordine, potrebbe avere più senso per te. Altrimenti ... Beh, la domanda ha avuto risposta. Buona giornata.
SFEley

8
Meglio per la migrazione verso il basso: change_column_default :people, :last_name, nil stackoverflow.com/a/1746246/483520
Nolan Amy

9
Si noti che per le versioni più recenti dei binari, è necessario un parametro di tipo aggiuntivo prima del parametro predefinito. Cerca: digita in questa pagina.
Ben Wheeler

3
@JoelBrewer Mi sono imbattuto in questo oggi - per Rails 4, devi anche specificare il tipo di colonna:change_column :people, :last_name, :string, default: "Doe"
GoBusto

56

Sulla base della risposta di SFEley, eccone uno aggiornato / corretto per le versioni più recenti di Rails:

class SetDefault < ActiveRecord::Migration
  def change
    change_column :table_name, :column_name, :type, default: "Your value"
  end
end

2
Attenzione questo NON funziona su Rails 4.2.x (non testato nelle versioni successive). come change_columnpuò essere abbastanza "strutturante", l'operazione inversa non può essere dedotta. Se non sei sicuro, provalo eseguendo db:migratee db:rollbacksubito dopo. Risposta accettata come lo stesso risultato ma almeno si presume!
gfd

1
Questa è la risposta corretta per me .. Funziona con rails 5.1 ma dovrai fare upe downcome descritto sopra da @GoBusto
Fabrizio Bertoglio

22

Prima di tutto non puoi sovraccaricare initialize(*args) perché non viene chiamato in tutti i casi.

La tua migliore opzione è inserire le tue impostazioni predefinite nella migrazione:

add_column :accounts, :max_users, :integer, :default => 10

La seconda opzione migliore è inserire i valori predefiniti nel modello, ma funzionerà solo con attributi inizialmente nulli. Potresti avere problemi come ho fatto con le booleancolonne:

def after_initialize
  if new_record?
    max_users ||= 10
  end
end

Hai bisogno di new_record? modo che i valori predefiniti non sovrascrivano i valori caricati dal database.

Devi ||=impedire a Rails di sovrascrivere i parametri passati al metodo initialize.


12
nota minore - vuoi fare due cose: .... 1) non chiamare il tuo metodo after_initialize. Vuoi after_initiation :your_method_name.... 2 usiself.max_users ||= 10
Jesse Wolgamott

5
Per i booleani, fai semplicemente questo: prop = true if prop.nil?
Francis Potter

2
o after_initialize doinvece didef after_initialize
fotanus

self.max_users per essere al sicuro.
Ken Ratanachai S.

15

Puoi anche provare change_column_defaultnelle tue migrazioni (testato in Rails 3.2.8):

class SetDefault < ActiveRecord::Migration
  def up
    # Set default value
    change_column_default :people, :last_name, "Smith"
  end

  def down
    # Remove default
    change_column_default :people, :last_name, nil
  end
end

change_column_default Rails API docs


1
La risposta accettata è più completa ma mi piace questa pulita e documentata ... Grazie!
gfd

7

Se ti riferisci a oggetti ActiveRecord, hai (più di) due modi per farlo:

1. Utilizzare un: parametro predefinito nel DB

PER ESEMPIO

class AddSsl < ActiveRecord::Migration
  def self.up
    add_column :accounts, :ssl_enabled, :boolean, :default => true
  end

  def self.down
    remove_column :accounts, :ssl_enabled
  end
end

Maggiori informazioni qui: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

2. Utilizzare una richiamata

PER ESEMPIO before_validation_on_create

Maggiori informazioni qui: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M002147


2
Non è il problema con l'utilizzo dei valori predefiniti nel database che non vengono impostati fino a quando l'oggetto non viene salvato? Se voglio costruire un nuovo oggetto con i valori predefiniti popolati, devo impostarli nell'inizializzatore.
Rafe

I booleani non possono essere impostati come predefiniti su 1 o 0: devono essere impostati su vero o falso (vedere la risposta di Silasj).
Jamon Holmgren

@JamonHolmgren Grazie per l'osservazione, ho corretto la risposta :)
Vlad Zloteanu

Nessun problema @VladZloteanu
Jamon Holmgren

7

In Ruby on Rails v3.2.8, utilizzando il after_initializecallback ActiveRecord, puoi chiamare un metodo nel tuo modello che assegnerà i valori predefiniti per un nuovo oggetto.

Il callback after_initialize viene attivato per ogni oggetto che viene trovato e istanziato da un finder, con after_initialize che viene attivato anche dopo che sono stati istanziati nuovi oggetti ( vedere Callback ActiveRecord ).

Quindi, IMO dovrebbe assomigliare a qualcosa di simile:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    # required to check an attribute for existence to weed out existing records
    self.bar = default_value unless self.attribute_whose_presence_has_been_validated
  end
end

Foo.bar = default_valueper questa istanza a meno che l'istanza non contenga un attribute_whose_presence_has_been_validatedprecedente salvataggio / aggiornamento. Il default_valueverrà quindi utilizzato in congiunzione con il fine di rendere il modulo che utilizza la default_valueper l' barattributo.

Nella migliore delle ipotesi questo è hacky ...

MODIFICA - usa "new_record?" per verificare se si sta creando un'istanza da una nuova chiamata

Invece di controllare il valore di un attributo, usa il new_record?metodo integrato con rails. Quindi, l'esempio sopra dovrebbe essere simile a:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo, if: 'new_record?'
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    self.bar = default_value
  end
end

Questo è molto più pulito. Ah, la magia di Rails - è più intelligente di me.


Questo non funziona come mi aspettavo: avrebbe dovuto impostare il valore predefinito solo su new, ma imposta anche il valore predefinito sull'attributo per ogni oggetto trovato e istanziato (cioè i record caricati dal db). Puoi controllare l'attributo di un oggetto alla ricerca di valori prima di assegnare il valore predefinito, ma questa non è una soluzione molto elegante o robusta.
errore

1
Perché consigli di after_initializerichiamare qui? La documentazione di Rails sui callback ha un esempio di impostazione del valore predefinito before_createsenza controlli delle condizioni aggiuntivi
Dfr

@Dfr - Devo essermelo perso, puoi lanciarmi un link per rivedere e aggiornerò la risposta ...
errore

Sì, qui api.rubyonrails.org/classes/ActiveRecord/Callbacks.html primo esempio, classeSubscription
Dfr

5
@Dfr - Il motivo per cui stiamo usando after_initialize, invece del before_createcallback, è che vogliamo impostare un valore predefinito per l' utente (da utilizzare in una vista) quando crea nuovi oggetti. Il before_createcallback viene chiamata dopo l' utente ha servito un nuovo oggetto, purché il loro ingresso, e sottoposto l'oggetto per la creazione al controllore. Il controller verifica quindi eventuali before_createcallback. Sembra controintuitivo, ma è una nomenclatura: si before_createriferisce createall'azione. L'istanza di un nuovo oggetto non fa createl'oggetto.
errore

5

Almeno per i campi booleani in Rails 3.2.6, questo funzionerà nella migrazione.

def change
  add_column :users, :eula_accepted, :boolean, default: false
end

Mettere un 1o 0per un valore predefinito non funzionerà qui, poiché è un campo booleano. Deve essere un valore trueo false.



2

Genera una migrazione e usa change_column_default, è succinto e reversibile:

class SetDefaultAgeInPeople < ActiveRecord::Migration[5.2]
  def change
    change_column_default :people, :age, { from: nil, to: 0 }
  end
end

1

Se stai solo impostando i valori predefiniti per determinati attributi di un modello supportato da database, prenderei in considerazione l'utilizzo di valori di colonna predefiniti sql: puoi chiarire quali tipi di valori predefiniti stai utilizzando?

Esistono diversi approcci per gestirlo, questo plugin sembra un'opzione interessante.


1
Penso che non dovresti dipendere dal database per gestire impostazioni predefinite e vincoli, tutte queste cose dovrebbero essere risolte nel livello del modello. Quel plugin funziona solo per i modelli ActiveRecord, non è un modo generico per impostare i valori predefiniti per gli oggetti.
Lukas Stejskal

Direi che dipende dai tipi di impostazioni predefinite che stai cercando di utilizzare, non è qualcosa che ho bisogno di fare molto spesso, ma direi che i vincoli sono in realtà molto meglio impostati sia nel database che il modello - per evitare che i tuoi dati diventino non validi nel database.
paulthenerd

1

Il suggerimento di sovrascrivere new / initialize è probabilmente incompleto. Rails chiamerà (frequentemente) allocate per oggetti ActiveRecord e le chiamate per allocare non risulteranno in chiamate da inizializzare.

Se stai parlando di oggetti ActiveRecord, dai un'occhiata a override after_initialize.

Questi post del blog (non il mio) sono utili:

Valori predefiniti Costruttori predefiniti non chiamati

[Modifica: SFEley sottolinea che Rails in realtà guarda il valore predefinito nel database quando istanzia un nuovo oggetto in memoria - non me ne ero reso conto.]


1

Avevo bisogno di impostare un valore predefinito proprio come se fosse stato specificato come valore di colonna predefinito in DB. Quindi si comporta in questo modo

a = Item.new
a.published_at # => my default value

a = Item.new(:published_at => nil)
a.published_at # => nil

Poiché il callback after_initialize viene chiamato dopo aver impostato gli attributi dagli argomenti, non c'era modo di sapere se l'attributo è nil perché non è mai stato impostato o perché è stato impostato intenzionalmente come nil. Quindi ho dovuto dare un'occhiata un po 'e sono arrivato con questa semplice soluzione.

class Item < ActiveRecord::Base
  def self.column_defaults
    super.merge('published_at' => Time.now)
  end
end

Funziona alla grande per me. (Rotaie 3.2.x)



0

ho risposto a una domanda simile qui .. un modo pulito per farlo è usare Rails attr_accessor_with_default

class SOF
  attr_accessor_with_default :is_awesome,true
end

sof = SOF.new
sof.is_awesome

=> true

AGGIORNARE

attr_accessor_with_default è stato deprecato in Rails 3.2 .. potresti farlo invece con Ruby puro

class SOF
  attr_writer :is_awesome

  def is_awesome
    @is_awesome ||= true
  end
end

sof = SOF.new
sof.is_awesome

#=> true

attr_accessor_with_defaultè deprecato a partire da rails> 3.1.0
flynfish

Nel tuo esempio, is_awesome sarà sempre vero anche quando @is_awesome == false.
Ritchie



-3

È possibile sostituire il costruttore per il modello ActiveRecord.

Come questo:

def initialize(*args)
  super(*args)
  self.attribute_that_needs_default_value ||= default_value
  self.attribute_that_needs_another_default_value ||= another_default_value
  #ad nauseum
end

2
Questo è un posto terribile per cambiare la funzionalità dei binari principali. Ci sono altri modi molto stabili che hanno meno probabilità di rompere altre cose, modi per farlo menzionati in altre risposte. Il patching delle scimmie dovrebbe essere solo l'ultima risorsa per cose che non possono essere fatte in altro modo.
Will il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.