Rails + Postgres + fixations

image

Bonjour mes amis. Ce n'est un secret pour personne que, travaillant sur de grands projets avec une logique complexe, Active Record ne devient pas un assistant, mais un fardeau. Imaginez que vous ayez besoin de faire une requête très complexe pour PostgreSQL de manière native (en SQL pur), où un certain nombre de variables devraient être présentes. Mais il y a une bagatelle désagréable dans Rails, la fonctionnalité d'exécuter des requêtes natives ne permet pas l'utilisation de liaisons nommées. Mais il existe une solution :) Testé et implémenté avec succès sur un projet avec Rails API 5.2 + Ruby 2.6.0 + Postgres 11.

Donc, un peu plus sur le problème. La méthode principale qui vous permet d'exécuter vos propres requêtes SQL est exec_query:

sql = 'SELECT id, name, desc FROM schema.news WHERE id=$1' bindings = [[nil, 100]] new = ActiveRecord::Base.connection.exec_query(sql, 'SQL', bindings).first 

L'exemple ci-dessus montre que la formation de classeurs se produit, pour le moins, à un seul endroit, lorsque nous essayons d'obtenir des nouvelles sous le numéro 100 de la base de données. Les liaisons ne peuvent pas être nommées, mais seulement numérotées. Et cela complique grandement la lecture et le support des requêtes natives. Vous pouvez également utiliser l'appel de méthode find_by_sql pour la classe de modèle:

 sql = 'SELECT id, name, desc FROM schema.news WHERE id=:id' new = New.find_by_sql([sql, id: 100]).first 

Tout est plus agréable et compréhensible ici. Mais la question est, c'est plus ou moins acceptable si vous souhaitez effectuer une simple demande. Mais si la demande est vraiment compliquée, la faire passer par le modèle et Active Record lui-même est une grande perte de vitesse (lentement) et de performances (cela consomme des ressources de serveur). Pourquoi il n'y a pas de classeurs nommés lorsque vous travaillez avec des requêtes natives est un mystère pour moi, mais il existe une solution - écrire mon propre petit wrapper, qui peut fonctionner avec des classeurs nommés très simplement, ce que j'ai fait.

J'apporte le code de la classe statique:

 # Class for work with SQL query. # Can use clean SQL with hash bindings. # Convert JSON fields to hash. # Can use if not need get model object! class SqlQuery # Create sql query with hash bindings # # @param [String] sql SQL query # @param [Hash] bind bindings data for query # # @return [Array] executed SQL request data and return array with hashes def self.execute(sql, bind = {}) bindings = [] bind_index = 1 # Get all bindings if exist unless bind.empty? bind.each do |key, value| # Change name bind to $ bind sql.gsub!(/(?<!:):#{key}(?=\b)/, "$#{bind_index}") bind_index += 1 # Add new bind data bindings << [nil, value] end end # Execute query, convert to hash with symbol keys result = ActiveRecord::Base.connection.exec_query(sql, 'SQL', bindings).map(&:symbolize_keys) # Convert JSON data to hash result.map do |v| next if v.nil? v.each do |key, val| v[key] = json_to_hash(val) end end end # Convert JSON to hash if correct data # # @param [String] json string # @return [Hash] return hash if json is correct or input data def self.json_to_hash(json) JSON.parse(json, symbolize_names: true) rescue json end end 

Comme vous pouvez le voir dans le code, tout est aussi simple qu'un coin de la maison. La requête fonctionne comme ceci:

 sql = 'SELECT id, name, desc FROM schema.news WHERE id=:id' binding = { id: 100 } new = SqlQuery.execute(sql, binding).first 

La sortie n'est toujours qu'un hachage. Une petite explication. La méthode d'exécution prend une chaîne de requête et un hachage avec des liants. Il est clair que les liaisons dans la demande et le hachage doivent correspondre. Après cela, nous parcourons le hachage avec des liants et les remplaçons par des variables numérotées de la forme $ 1, $ 2, etc. dans la demande elle-même, créant simultanément un tableau de valeurs numérotées, où le premier élément du tableau est $ 1, le second est $ 2, etc. Ensuite, nous exécutons la requête à l'aide de la méthode standard exec_query, en parcourant la réponse avec le mappeur et en convertissant les clés du hachage en caractères. Après cela, nous exécutons à nouveau le mappeur en fonction de la réponse, où nous vérifions chaque valeur de champ pour le contenu de JSON qu'il contient. S'il existe JSON et qu'il est valide, convertissez-le en un hachage avec des clés et des symboles, si le champ n'est pas JSON, puis lancez une exception dans laquelle nous renvoyons la valeur. C’est tout.

Comme vous pouvez le voir, cela n'a aucun sens de mettre des centaines de toutes sortes de gemmes, gaspillant les performances globales afin d'obtenir le résultat souhaité. Vous pouvez écrire vous-même de nombreuses décisions nécessaires très rapidement, après avoir passé un minimum de temps et de code.

Liens vers github et couper des gemmes avec le plugin:
github.com/kirill-dan/active_sql_bindings
rubygems.org/gems/active_sql_bindings

Bonne chance à tous, à bientôt.

Réimprimez à partir de votre propre blog. Original ici

Source: https://habr.com/ru/post/fr438110/


All Articles