Rails find_or_create_by plus d'un attribut?

203

Il existe un attribut dynamique pratique dans l'enregistrement actif appelé find_or_create_by:

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

Mais que faire si j'ai besoin de trouver_ou_créer par plus d'un attribut?

Disons que j'ai un modèle pour gérer une relation M: M entre le groupe et le membre appelé GroupMember. Je pourrais avoir de nombreuses instances où member_id = 4, mais je ne veux jamais plus d'une fois où member_id = 4 et group_id = 7. J'essaie de comprendre s'il est possible de faire quelque chose comme ceci:

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

Je me rends compte qu'il peut y avoir de meilleures façons de gérer cela, mais j'aime la commodité de l'idée de find_or_create.

tybro0103
la source

Réponses:

465

Plusieurs attributs peuvent être connectés avec and:

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

(à utiliser find_or_initialize_bysi vous ne souhaitez pas enregistrer immédiatement l'enregistrement)

Edit: La méthode ci-dessus est déconseillée dans Rails 4. La nouvelle façon de le faire sera:

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

et

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

Édition 2: tous ces éléments n'ont pas été pris en compte dans les rails uniquement les attributs spécifiques.

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

Exemple

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

est devenu

GroupMember.find_or_create_by(member_id: 4, group_id: 7)
x1a4
la source
Vous aimerez aussi github.com/seamusabshere/upsert - le premier argument est un hachage d'attributs qui est utilisé pour rechercher ou créer un enregistrement
Seamus Abshere
1
Il est utile de noter que l'initialisation appelle le create () au lieu du nouveau () qui pourrait être appelé en cas de first_or_create
Sumit Bisht
Quelqu'un peut-il partager un exemple essentiel de cette utilisation? Je ne connais pas son emplacement et son appel.
6 pieds Dan
J'ai annulé la suppression du code Rails 3, car beaucoup de gens n'utilisent pas encore Rails 4.
Kyle Heironimus
Juste pour ajouter: les fonctions find_or_create_by _ * () ont été dépréciées dans Rails 4, mais find_or_create_by () ne l'est PAS. Il est correct d'utiliser find_or_create_by (). Vérifiez le commit où cela a été ajouté github.com/rails/rails/commit/… et la documentation officielle de Rails 4.2 pour l'utilisation: guides.rubyonrails.org/v4.2.0/…
CodeExpress
33

Pour toute autre personne qui tombe sur ce fil mais doit trouver ou créer un objet avec des attributs qui peuvent changer selon les circonstances, ajoutez la méthode suivante à votre modèle:

# 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

Conseil d'optimisation: quelle que soit la solution que vous choisissez, pensez à ajouter des index pour les attributs que vous interrogez le plus fréquemment.

Marco
la source
8
Une chose à noter est que cela ne gérera pas les attributs qui ne peuvent pas être affectés en masse, alors qu'il le find_or_create_bysera.
x1a4
1
Marco, essayez Model.where(attributes).instance_eval{|q| q.first || q.create}.
hiroshi
3
find_or_createa également l'avantage d'utiliser une transaction, où where….first || createintroduit une condition de
concurrence
Je dirais def self.find_or_create(attributes) self.where(attributes).first || self.create(attributes) endpour que vous n'ayez pas besoin de répéterModel
Augustin Riedinger
@AugustinRiedinger Vous n'avez pas non plus besoin de l' selfintérieur du corps de la méthode (puisque vous cherchez à supprimer des mots!).
David Tuite
31

Dans Rails 4, vous pouvez faire:

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

Et l'utilisation whereest différente:

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

Cela fera appel createà GroupMember.where(member_id: 4, group_id: 7):

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

Au contraire, la find_or_create_by(member_id: 4, group_id: 7)volonté fera appel createà GroupMember:

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

Veuillez consulter ce commit pertinent sur rails / rails.

Juanito Fatas
la source
16

En passant un bloc à find_or_create, vous pouvez passer des paramètres supplémentaires qui seront ajoutés à l'objet s'il est créé nouveau. Ceci est utile si vous validez la présence d'un champ que vous ne recherchez pas.

En supposant:

class GroupMember < ActiveRecord::Base
    validates_presence_of :name
end

puis

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

va créer un nouveau GroupMember avec le nom "John Doe" s'il n'en trouve pas un avec member_id 4 and group_id 7

Daniel Murphy
la source
4

Tu peux faire:

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

Ou pour simplement initialiser:

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
la source