Szyny find_or_create_by więcej niż jeden atrybut?

202

W aktywnym rekordzie znajduje się przydatny atrybut dynamiczny o nazwie find_or_create_by:

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

Ale co jeśli będę musiał znaleźć_lub_tworzyć za pomocą więcej niż jednego atrybutu?

Powiedzmy, że mam model do obsługi relacji M: M między grupą a członkiem o nazwie GroupMember. Mógłbym mieć wiele instancji, w których member_id = 4, ale nigdy nie chcę więcej niż jednej instancji, gdzie member_id = 4 i group_id = 7. Próbuję dowiedzieć się, czy można zrobić coś takiego:

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

Zdaję sobie sprawę, że mogą istnieć lepsze sposoby na poradzenie sobie z tym, ale podoba mi się wygoda pomysłu find_or_create.

tybro0103
źródło

Odpowiedzi:

464

Wiele atrybutów można połączyć za pomocą and:

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

(użyj, find_or_initialize_byjeśli nie chcesz od razu zapisać rekordu)

Edycja: Powyższa metoda jest nieaktualna w Rails 4. Nowy sposób na zrobienie tego to:

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

i

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

Edycja 2: Nie wszystkie z nich zostały uwzględnione w szynach, tylko te specyficzne dla atrybutów.

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

Przykład

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

stał się

GroupMember.find_or_create_by(member_id: 4, group_id: 7)
x1a4
źródło
Może ci się również spodobać github.com/seamusabshere/upsert - pierwszy argument to skrót atrybutów, który służy do znalezienia lub utworzenia rekordu
Seamus Abshere
1
Warto zauważyć, że inicjalizacja wywołuje funkcje create () zamiast new (), które mogą być wywoływane w przypadku first_or_create
Sumit Bisht
Czy ktoś może podać ogólny przykład tego użycia? Nie znam jej umiejscowienia i dzwonienia.
6 stóp Dan
Cofnąłem usuwanie kodu Rails 3, ponieważ wiele osób jeszcze nie używa Rails 4.
Kyle Heironimus
Wystarczy dodać: funkcje find_or_create_by _ * () zostały przestarzałe w Rails 4, jednak NIE jest find_or_create_by (). Można użyć find_or_create_by (). Sprawdź zatwierdzenie tam, gdzie zostało dodane github.com/rails/rails/commit/... oraz oficjalną dokumentację Rails 4.2 dotyczącą użycia: guide.rubyonrails.org/v4.2.0/...
CodeExpress
33

Dla każdego, kto natknie się na ten wątek, ale musi znaleźć lub utworzyć obiekt z atrybutami, które mogą ulec zmianie w zależności od okoliczności, dodaj następującą metodę do swojego modelu:

# 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

Wskazówka dotycząca optymalizacji: bez względu na to, które rozwiązanie wybierzesz, rozważ dodanie indeksów najczęściej zadawanych atrybutów.

Marco
źródło
8
Należy zauważyć, że nie będzie to obsługiwać atrybutów, których nie można przypisać masowo, podczas gdy find_or_create_bybędzie.
x1a4
1
Marco, spróbuj Model.where(attributes).instance_eval{|q| q.first || q.create}.
hiroshi,
3
find_or_createma również tę zaletę, że wykorzystuje transakcję, w której where….first || createwprowadza warunek wyścigu
mrm
Powiedziałbym def self.find_or_create(attributes) self.where(attributes).first || self.create(attributes) end, żebyście nie musieli powtarzaćModel
Augustin Riedinger
@AugustinRiedinger Nie potrzebujesz też selfw treści metody (ponieważ chcesz usunąć słowa!).
David Tuite
31

W Rails 4 możesz:

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

I użycie wherejest inne:

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

Wywoła to createna GroupMember.where(member_id: 4, group_id: 7):

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

Na Przeciwnie, find_or_create_by(member_id: 4, group_id: 7)będzie zadzwonić createna GroupMember:

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

Proszę zobaczyć to odpowiednie zatwierdzenie na szynach / szynach.

Juanito Fatas
źródło
16

Przekazywanie bloku do find_or_create , możesz przekazać dodatkowe parametry, które zostaną dodane do obiektu, jeśli zostanie on utworzony jako nowy. Jest to przydatne, jeśli sprawdzasz obecność pola, którego nie szukasz.

Zarozumiały:

class GroupMember < ActiveRecord::Base
    validates_presence_of :name
end

następnie

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

utworzy nowego członka grupy o nazwie „John Doe”, jeśli go nie znajdzie member_id 4 and group_id 7

Daniel Murphy
źródło
4

Możesz to zrobić:

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

Lub po prostu zainicjować:

User.find_or_initialize_by(first_name: 'Penélope', last_name: 'Lopez')
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_initialize
dorycki
źródło