2010-11-08 16 views
26

Ho il seguente modello Rails:Rails: Ignorando gli attributi inesistenti passati per creare()

class CreateFoo < ActiveRecord::Migration 
    def self.up 
    create_table :foo do |t| 
     t.string :a 
     t.string :b 
     t.string :c 
     t.timestamps 
    end 
    end 

    def self.down 
    drop_table :foo 
    end 
end 

Se provo e creare un nuovo record con un attributo aggiuntivo inesistente, questo produce un errore:

Foo.create(a: 'some', b: 'string', c: 'foo', d: 'bar') 
ActiveRecord::UnknownAttributeError: unknown attribute: d 

C'è un modo per ottenere create() per ignorare attributi che non esistono nel modello? In alternativa, qual è il modo migliore per rimuovere gli attributi inesistenti prima di creare il nuovo record?

Molte grazie

+2

perché si desidera passare attributi non esistenti? – cbrulak

+0

sì, cosa stai cercando di fare. Non vedo ragioni per questo. – s84

+3

Sto interrogando un servizio web di terze parti che restituisce XML che sto analizzando con un hash. Ci sarà un campo nel modello per ogni campo restituito dal servizio, ma devo essere sicuro che se decidono di restituire campi aggiuntivi, questo non impedisce la creazione del record. Ci sono oltre 100 campi, quindi mappare ogni campo individualmente non è un'opzione. – gjb

risposta

35

Cercando pensare a un modo potenzialmente più efficiente, ma per ora:

hash = { :a => 'some', :b => 'string', :c => 'foo', :d => 'bar' } 
@something = Something.new 
@something.attributes = hash.reject{|k,v| [email protected]?(k.to_s) } 
@something.save 
+0

Penso che tu abbia un errore, hash.select restituisce un array, non un hash. –

+0

Sì, hai ragione, dovresti semplicemente usare l'opzione di rifiuto. L'ho avuto in origine, stavo scrivendo prima di pensare: P – jenjenut233

+0

Grazie per la risposta. – gjb

6

Re: C'è un modo per ottenere create() per ignorare gli attributi che non esistono nel modello? - No, e questo è di progettazione.

È possibile creare un attr_setter che verrà utilizzato dal create -

attr_setter :a # will silently absorb additional parameter 'a' from the form. 

Re: In alternativa, qual è il modo migliore per rimuovere gli attributi inesistenti prima di creare il nuovo record?

È possibile rimuovere esplicitamente:

params[:Foo].delete(:a) # delete the extra param :a 

Ma il migliore è di non metterli lì, in primo luogo. Modifica il tuo modulo per ometterli.

Aggiunto:

Dato l'informazioni aggiornate (dati in entrata), penso che mi piacerebbe creare un nuovo hash:

incoming_data_array.each{|rec| 
    Foo.create {:a => rec['a'], :b => rec['b'], :c => rec['c']} # create new 
                   # rec from specific 
                   # fields 
} 

aggiunto più

# Another way: 
keepers = ['a', 'b', 'c'] # fields used by the Foo class. 

incoming_data_array.each{|rec| 
    Foo.create rec.delete_if{|key, value| !keepers.include?(key)} # create new rec 
}                # from kept 
                   # fields 
+0

Grazie per la risposta. Ho aggiunto ulteriori dettagli sopra mentre stavi rispondendo. È possibile fare il contrario di questo ed eliminare "tutti gli attributi tranne ..." da un hash? – gjb

+1

se hai downvoted questo, per favore aggiungi un commento perchè. Questa soluzione sembra abbastanza buona. – cbrulak

+1

Perché nei commenti dell'OP accenna "Sto interrogando un servizio web di terze parti che restituisce XML che sto analizzando con un hash". ... quindi non ci sono array di dati in entrata, egli menziona anche che ha oltre 100 campi in modo che la rimappatura manuale degli attributi non sia un'opzione. (Downvoted prima del tuo delete_if edit) Indipendentemente, con oltre 100 campi, dovrebbe semplicemente estrarre le chiavi di attributo valide dal record, non creare un array di "keepers" (dal momento che Rails già fa questa logica per noi). – jenjenut233

1

penso che utilizzando il metodo di attr_accessiblenella classe modello per Foo potrebbe ottenere quello che vuoi, ad esempio ,:

class Foo < ActiveRecord::Base 

    attr_accessible :a, :b, :c 

    ... 
end 

Ciò consentirebbe la creazione/aggiornamento dei soli attributi elencati con attr_accessible.

7

Ho appena avuto questo esatto aggiornamento problema a Rails 3.2, quando ho impostato:

config.active_record.mass_assignment_sanitizer = :strict 

ha causato alcuni dei miei creare! chiama per fallire, dal momento che i campi precedentemente ignorati causano errori di assegnazione di massa.Ho lavorato intorno ad esso fingendo i campi nel modello come segue:

attr_accessor :field_to_exclude 
attr_accessible :field_to_exclude 
10

Io lo uso spesso (semplificato):

params.select!{|x| Model.attribute_names.index(x)} 
Model.update_attributes(params) 
+0

Si potrebbe voler usare' x.to_s' perché 'attribute_names' è una matrice di stringhe. In ogni caso, +1 =) – Abdo

3

mi si avvicinò con una soluzione che assomiglia a questo, si potrebbe trovare utile:

def self.create_from_hash(hash) 
    hash.select! {|k, v| self.column_names.include? k } 
    self.create(hash) 
end 

Questa è stata una soluzione ideale per me, perché nel mio caso hash era venuta da una fonte di dati ideale che rispecchiava il mio schema (tranne che c'erano campi aggiuntivi).

+0

Mi piace questa soluzione. A volte, potrebbe essere utile cancellare un elemento dall'hash in arrivo (ad esempio l'id) come tale: 'FooModel.create_from_hash (hash.except ('id'))' – Abdo

0

Ho trovato una soluzione che funziona abbastanza bene, ed è in definitiva una combinazione delle opzioni di cui sopra. Permette di passare (e ignorare) parametri non validi, mentre quelli validi sono mappati correttamente all'oggetto.

def self.initialize(params={}) 
    User.new(params.reject { |k| !User.attribute_method?(k) }) 
end 

Ora invece di chiamare User.new(), chiamano User.initialize(). Questo "filtrerà" i parametri corretti in modo abbastanza elegante.

+0

Elegante, sì. Attenzione però, non funziona quando i valori param sono booleani. – bjorn

0

È possibile utilizzare il metodo Hash#slice e column_names anche come metodo di classe.

hash = {a: 'some', b: 'string', c: 'foo', d: 'bar'} 
Foo.create(hash.slice(*Foo.column_names.map(&:to_sym))) 
Problemi correlati