Schienen find_or_create_by mehr als ein Attribut?

202

In Active-Record gibt es ein praktisches dynamisches Attribut namens find_or_create_by:

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

Aber was ist, wenn ich nach mehr als einem Attribut suchen oder erstellen muss?

Angenommen, ich habe ein Modell für die Behandlung einer M: M-Beziehung zwischen Gruppe und Mitglied namens GroupMember. Ich könnte viele Instanzen haben, in denen member_id = 4 ist, aber ich möchte nie mehr als eine Instanz, in der member_id = 4 und group_id = 7 sind. Ich versuche herauszufinden, ob es möglich ist, so etwas zu tun:

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

Mir ist klar, dass es vielleicht bessere Möglichkeiten gibt, damit umzugehen, aber ich mag die Bequemlichkeit der Idee von find_or_create.

tybro0103
quelle

Antworten:

464

Mehrere Attribute können verbunden werden mit and:

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

(Verwenden find_or_initialize_bySie diese Option, wenn Sie den Datensatz nicht sofort speichern möchten.)

Bearbeiten: Die obige Methode ist in Rails 4 veraltet. Die neue Vorgehensweise lautet:

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

und

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

Bearbeiten 2: Nicht alle davon wurden aus Schienen herausgerechnet, nur die attributspezifischen.

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

Beispiel

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

wurden

GroupMember.find_or_create_by(member_id: 4, group_id: 7)
x1a4
quelle
Vielleicht gefällt Ihnen auch github.com/seamusabshere/upsert - das erste Argument ist ein Hash von Attributen, die zum Suchen oder Erstellen eines Datensatzes verwendet werden
Seamus Abshere
1
Es ist hilfreich zu beachten, dass initialize das create () anstelle von new () aufruft, das im Fall von first_or_create
Sumit Bisht
Kann jemand ein wesentliches Beispiel für diese Verwendung nennen? Ich bin nicht mit der Platzierung und dem Anruf vertraut.
6ft Dan
Ich habe das Entfernen von Rails 3-Code rückgängig gemacht, da viele Leute Rails 4 noch nicht verwenden.
Kyle Heironimus
Nur zum Hinzufügen: find_or_create_by _ * () -Funktionen sind in Rails 4 veraltet, find_or_create_by () jedoch NICHT. Es ist in Ordnung, find_or_create_by () zu verwenden. Überprüfen Sie das Commit, wo dies hinzugefügt wurde. Github.com/rails/rails/commit/… und die offizielle Rails 4.2-Dokumentation für die Verwendung: guide.rubyonrails.org/v4.2.0/…
CodeExpress
33

Fügen Sie Ihrem Modell die folgende Methode hinzu, wenn Sie über diesen Thread stolpern, aber ein Objekt mit Attributen suchen oder erstellen müssen, die sich je nach den Umständen ändern können:

# 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

Optimierungstipp: Unabhängig davon, für welche Lösung Sie sich entscheiden, sollten Sie Indizes für die Attribute hinzufügen, die Sie am häufigsten abfragen.

Marco
quelle
8
Eine zu beachtende Sache ist, dass dies keine Attribute behandelt, die nicht massenweise zugewiesen werden können, während dies der Fall find_or_create_byist.
x1a4
1
Marco, versuch es Model.where(attributes).instance_eval{|q| q.first || q.create}.
Hiroshi
3
find_or_createhat auch den Vorteil, eine Transaktion zu verwenden, bei where….first || createder eine Rennbedingung eingeführt wird
mrm
Ich würde sagen, def self.find_or_create(attributes) self.where(attributes).first || self.create(attributes) enddamit Sie nicht wiederholen müssenModel
Augustin Riedinger
@AugustinRiedinger Sie brauchen auch nicht selfinnerhalb des Methodenkörpers (da Sie Wörter entfernen möchten !).
David Tuite
31

In Rails 4 können Sie Folgendes tun:

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

Und die Verwendung whereist anders:

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

Dies ruft createauf GroupMember.where(member_id: 4, group_id: 7):

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

Im Gegenteil, die find_or_create_by(member_id: 4, group_id: 7)rufen createauf GroupMember:

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

Bitte beachten Sie dieses relevante Commit auf Schienen.

Juanito Fatas
quelle
16

Durch Übergeben eines Blocks an find_or_createkönnen Sie zusätzliche Parameter übergeben, die dem Objekt hinzugefügt werden, wenn es neu erstellt wird. Dies ist nützlich, wenn Sie das Vorhandensein eines Felds überprüfen, nach dem Sie nicht suchen.

Angenommen:

class GroupMember < ActiveRecord::Base
    validates_presence_of :name
end

dann

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

erstellt ein neues GroupMember mit dem Namen "John Doe", wenn es keines mit findet member_id 4 and group_id 7

Daniel Murphy
quelle
4

Du kannst tun:

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

Oder einfach zu initialisieren:

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