Rails znaleźć lub utworzyć przez więcej niż jeden atrybut?

W active-record znajduje się przydatny atrybut dynamiczny o nazwie find_or_create_by:

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

Ale co jeśli muszę find_or_create przez więcej niż jeden atrybut?

Powiedzmy, że mam model do obsługi relacji M: M między grupą a członkiem zwanym GroupMember. Mogę mieć wiele instancji, w których member_id = 4, ale nigdy nie chcę więcej niż raz instancji, w których member_id = 4 i group_id = 7. Zastanawiam się, czy można zrobić coś takiego. to:

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

Zdaję sobie sprawę, że mogą być lepsze sposoby, aby sobie z tym poradzić, ale podoba mi się wygoda idei find_or_create.

Author: Marlin Pierce, 2010-06-15

5 answers

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

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

(użyj find_or_initialize_by Jeśli nie chcesz od razu zapisać rekordu)

Edit: powyższa metoda jest przestarzała w Rails 4. Nowy sposób na to będzie:

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

I

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

Edit 2: nie wszystkie z nich były brane pod uwagę z rails tylko te atrybuty specyficzne.

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)
 451
Author: x1a4,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-17 12:26:06

Dla każdego, kto natknie się na ten wątek, ale musi znaleźć lub utworzyć obiekt z atrybutami, które mogą się zmienić w zależności od okoliczności, dodaj następującą metodę do 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

Porada optymalizacyjna: niezależnie od tego, które rozwiązanie wybierzesz, rozważ dodanie indeksów dla atrybutów, o które pytasz najczęściej.

 30
Author: Marco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-26 21:00:42

W Rails 4 możesz zrobić:

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

I użycie where jest inne:

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

To wywoła create na GroupMember.where(member_id: 4, group_id: 7):

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

Przeciwnie, find_or_create_by(member_id: 4, group_id: 7) wywoła create na GroupMember:

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

Zobacz ten odpowiedni commit on rails / rails.

 26
Author: Juanito Fatas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-04 02:37:51

Przekazując blok do find_or_create, można przekazać dodatkowe parametry, które zostaną dodane do obiektu, jeśli zostanie utworzony nowy. Jest to przydatne, jeśli sprawdzasz obecność pola, którego nie wyszukujesz.

Zakładając:

class GroupMember < ActiveRecord::Base
    validates_presence_of :name
end

Then

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 nie znajdzie go z member_id 4 and group_id 7

 13
Author: Daniel Murphy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-19 14:01:27

Możesz 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
 3
Author: Dorian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-11 19:54:15