3
votes

Ruby / Rails - Chaîne un nombre inconnu d'appels de méthode

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?


0 commentaires

3 Réponses :


1
votes

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

  1. pour arr vide, il renverra la classe que nous avons passée comme premier argument dans la méthode.

  2. 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


0 commentaires

2
votes

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'}
}


0 commentaires

0
votes
arr.inject(Articles){|articles, args| articles.send(*args)}

0 commentaires