Je voudrais créer dynamiquement des requêtes Active Record (potentiellement complexes) à partir d'un tableau 2D passé dans une méthode en tant qu'argument. En d'autres termes, j'aimerais prendre ceci:
case arr.length
when 1
Articles.send(*arr[0])
when 2
Articles.send(*arr[0]).send(*arr[1])
when 3
Articles.send(*arr[0]).send(*arr[1]).send(*arr[2])
# etc.
end
Et créer l'équivalent de ceci:
Articles.send(*arr[0]).send(*arr[1])
Une façon de faire ceci est:
Articles.join(:comments).where(:author => 'Bob')
Mais que se passe-t-il si arr contient 3 tableaux imbriqués, ou 4, ou 5? Une manière très peu raffinée serait de faire ceci:
arr = [
['join', :comments],
['where', :author => 'Bob']
]
Mais y a-t-il un moyen plus propre et plus succinct (sans avoir à frapper la base de données plusieurs fois)? Peut-être un moyen de construire une chaîne d'appels de méthodes avant de les exécuter?
3 Réponses :
J'ai créé la requête suivante qui fonctionnera sur n'importe quel modèle et le tableau de requêtes chaîné associé.
# renamed to make concise Article.chain_queries(arr) User.chain_queries(arr)
J'ai testé en local pour le test suivant,
Article Load (0.9ms) SELECT `article`.* FROM `article` WHERE `article`.`id` IN (1, 2) AND `article`.`first_name` = 'Shobiz' ORDER BY created_at desc
La requête déclenchée est comme ci-dessous pour renvoyer une sortie correcte,
arr = [['where', {id: [1,2]}], ['where', {first_name: 'Shobiz'}]]
chain_queries_on(Article, arr)
Note-1: quelques cas notables
pour arr vide, il renverra la classe que nous avons passée comme premier argument dans la méthode.
Il renverra nil en cas d'erreur. Une erreur peut se produire si nous utilisons pluck qui retournera un tableau (sortie qui ne peut pas être chaînée) ou si nous ne passons pas la classe comme premier paramètre etc.
Davantage de modifications peuvent être apportées pour améliorer les cas ci-dessus et éviter les bords.
Note-2: améliorations
Vous pouvez définir cette méthode comme méthode de classe pour la classe Object également avec un argument (c.-à-d. tableau) et appel directement sur la classe comme,
def chain_queries_on(klass, arr)
arr.inject(klass) do |relation, query|
begin
relation.send(query[0], *query[1..-1])
rescue
break;
end
end
end
À l'intérieur de la méthode, utilisez self au lieu de klass
Un moyen pratique serait d'utiliser un hachage au lieu d'un tableau 2D.
Quelque chose comme ça
query = {
join: [:comments],
where: {:author => 'Bob', comments: {author: 'Joe'}}
}
#=> "SELECT `articles`.* FROM `articles` INNER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` WHERE `articles`.`author` = 'Bob' AND `comments`.`author` = 'Joe'"
Cette approche n'est pas très complexe et vous ne le faites pas il faut s'inquiéter si la clé n'est pas fournie ou est vide
query = {
join: []
}
Article.joins(query[:join]).where(query[:where])
#=> "SELECT `articles`.* FROM `articles`"
Si les clés sont vides ou pas du tout présentes
Article.joins(query[:join]).where(query[:where]) #=> "SELECT `articles`.* FROM `articles` INNER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` WHERE `articles`.`author` = 'Bob'"
Ou imbriqué
query = {
join: [:comments],
where: {:author => 'Bob'}
}
arr.inject(Articles){|articles, args| articles.send(*args)}