Rails find_or_create_by più di un attributo?


203

C'è un utile attributo dinamico in active-record chiamato find_or_create_by:

Model.find_or_create_by_<attribute>(:<attribute> => "")

E se avessi bisogno di find_or_create con più di un attributo?

Supponiamo di avere un modello per gestire una relazione M: M tra Gruppo e Membro chiamato GroupMember. Potrei avere molti casi in cui member_id = 4, ma non voglio mai più di un'istanza in cui member_id = 4 e group_id = 7. Sto cercando di capire se è possibile fare qualcosa del genere:

GroupMember.find_or_create(:member_id => 4, :group_id => 7)

Mi rendo conto che potrebbero esserci modi migliori per gestirlo, ma mi piace la comodità dell'idea di find_or_create.

Risposte:


465

È possibile collegare più attributi con un and:

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

(utilizzare find_or_initialize_byse non si desidera salvare subito il record)

Modifica: il metodo sopra è obsoleto in Rails 4. Il nuovo modo di farlo sarà:

GroupMember.where(:member_id => 4, :group_id => 7).first_or_create

e

GroupMember.where(:member_id => 4, :group_id => 7).first_or_initialize

Modifica 2: Non tutti questi sono stati presi in considerazione dai binari solo quelli specifici dell'attributo.

https://github.com/rails/rails/blob/4-2-stable/guides/source/active_record_querying.md

Esempio

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

divenne

GroupMember.find_or_create_by(member_id: 4, group_id: 7)

Potrebbe piacerti anche github.com/seamusabshere/upsert - il primo argomento è un hash di attributi che viene utilizzato per trovare o creare un record
Seamus Abshere

1
È utile notare che l'inizializzazione chiama create () anziché new () che potrebbe essere chiamato in caso di first_or_create
Sumit Bisht

Qualcuno può condividere un esempio chiaro di questo utilizzo? Non ho familiarità con il posizionamento e la chiamata.
6ft Dan

Ho ripristinato la rimozione del codice Rails 3, poiché molte persone non utilizzano ancora Rails 4.
Kyle Heironimus,

Solo per aggiungere: le funzioni find_or_create_by _ * () sono state deprecate in Rails 4, tuttavia find_or_create_by () NON lo è. Va bene usare find_or_create_by (). Controlla il commit in cui è stato aggiunto github.com/rails/rails/commit/… e la documentazione ufficiale di Rails 4.2 per l'uso: guide.rubyonrails.org/v4.2.0/…
CodeExpress

33

Per tutti coloro che si imbattono in questo thread ma devono trovare o creare un oggetto con attributi che potrebbero cambiare in base alle circostanze, aggiungere il seguente metodo al modello:

# Return the first object which matches the attributes hash
# - or -
# Create new object with the given attributes
#
def self.find_or_create(attributes)
  Model.where(attributes).first || Model.create(attributes)
end

Suggerimento per l'ottimizzazione: indipendentemente dalla soluzione scelta, prendere in considerazione l'aggiunta di indici per gli attributi a cui si esegue la query più frequentemente.


8
Una cosa da notare è che questo non gestirà attributi che non possono essere assegnati in serie, mentre lo find_or_create_byfarà.
x1a4,

1
Marco, prova Model.where(attributes).instance_eval{|q| q.first || q.create}.
Hiroshi,

3
find_or_createha anche il vantaggio di utilizzare una transazione, dove where….first || createintroduce una condizione di gara
sig.

Direi def self.find_or_create(attributes) self.where(attributes).first || self.create(attributes) endche non è necessario ripeterloModel
Augustin Riedinger,

@AugustinRiedinger Non hai nemmeno bisogno selfall'interno del corpo del metodo (poiché stai cercando di rimuovere le parole!).
David Tuite,

31

In Rails 4 puoi fare:

GroupMember.find_or_create_by(member_id: 4, group_id: 7)

E l'uso whereè diverso:

GroupMember.where(member_id: 4, group_id: 7).first_or_create

Ciò richiederà createon GroupMember.where(member_id: 4, group_id: 7):

GroupMember.where(member_id: 4, group_id: 7).create

Al contrario, find_or_create_by(member_id: 4, group_id: 7)farà appello createa GroupMember:

GroupMember.create(member_id: 4, group_id: 7)

Vedere questo commit pertinente su binari / binari.


16

Passando un blocco a find_or_create , è possibile passare parametri aggiuntivi che verranno aggiunti all'oggetto se viene creato nuovo. Questo è utile se stai convalidando la presenza di un campo per il quale non stai cercando.

assumendo:

class GroupMember < ActiveRecord::Base
    validates_presence_of :name
end

poi

GroupMember.where(:member_id => 4, :group_id => 7).first_or_create { |gm| gm.name = "John Doe" }

creerà un nuovo GroupMember con il nome "John Doe" se non lo trova member_id 4 and group_id 7


4

Tu puoi fare:

User.find_or_create_by(first_name: 'Penélope', last_name: 'Lopez')
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_create

O solo per inizializzare:

User.find_or_initialize_by(first_name: 'Penélope', last_name: 'Lopez')
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_initialize

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.