Rieles + Postgres + enlaces

imagen

Hola amigos No es ningún secreto que al trabajar en grandes proyectos con lógica compleja, Active Record no se convierte en un asistente, sino en una carga. Imagine que necesita hacer una consulta muy compleja para PostgreSQL de forma nativa (en SQL puro), donde debe estar presente un cierto número de variables. Pero hay un poco desagradable en Rails, la funcionalidad de ejecutar consultas nativas no permite el uso de enlaces con nombre. Pero hay una solución :) Probado e implementado con éxito en un proyecto con Rails API 5.2 + Ruby 2.6.0 + Postgres 11.

Entonces, un poco más sobre el problema. El método principal que le permite ejecutar sus propias consultas SQL es 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 

El ejemplo anterior muestra que la formación de aglutinantes ocurre, por decirlo suavemente, en un lugar, cuando tratamos de obtener noticias con el número 100 de la base de datos. Los enlaces no se pueden nombrar, sino solo numerar. Y esto complica enormemente la lectura y el soporte de consultas nativas. Alternativamente, puede usar la llamada al método find_by_sql para la clase de modelo:

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

Aquí todo es más agradable y comprensible. Pero la pregunta es, esto es más o menos aceptable si desea realizar una solicitud simple. Pero si la solicitud es realmente complicada, ejecutarla a través del modelo y el registro activo en sí mismo es una gran pérdida de velocidad (lentamente) y rendimiento (consume recursos del servidor). Por qué no hay carpetas con nombre cuando se trabaja con consultas nativas es un misterio para mí, pero hay una solución: escribir mi propio contenedor pequeño, que puede funcionar con carpetas con nombre de manera muy simple, lo cual hice.

Traigo el código de la clase estática:

 # 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 

Como puede ver en el código, todo es tan simple como una esquina de la casa. La consulta funciona así:

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

La salida siempre es solo un hash. Una pequeña explicación El método execute toma una cadena de consulta y un hash con carpetas. Está claro que los enlaces en la solicitud y el hash deben coincidir. Después de eso, recorremos el hash con aglutinantes y los reemplazamos con variables numeradas de la forma $ 1, $ 2, etc. en la solicitud misma, creando simultáneamente una matriz de valores numerados, donde el primer elemento de la matriz es $ 1, el segundo es $ 2, y así sucesivamente. Después de eso, ejecutamos la consulta utilizando el método estándar exec_query, ejecutando la respuesta con el asignador y convirtiendo las claves en el hash en caracteres. Después de eso, nuevamente ejecutamos el asignador de acuerdo con la respuesta, donde verificamos el valor de cada campo para el contenido de JSON en él. Si hay JSON y es válido, conviértalo a un hash con claves y símbolos, si el campo no es JSON, entonces arroje una excepción en la que devolveremos el valor. Eso es todo

Como puede ver, no tiene sentido poner cientos de todo tipo de gemas, desperdiciando el rendimiento general, para obtener el resultado deseado. Puede escribir muchas decisiones necesarias muy rápidamente, ya que ha dedicado un mínimo de tiempo y código.

Enlaces a github y gemas cortadas con el complemento:
github.com/kirill-dan/active_sql_bindings
rubygems.org/gems/active_sql_bindings

Buena suerte a todos, hasta pronto.

Reimprime desde tu propio blog. Original aquí

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


All Articles