Rails + Postgres + ligações

imagem

Olá amigos. Não é segredo que, trabalhando em grandes projetos com lógica complexa, o Active Record não se torna um assistente, mas um fardo. Imagine que você precisa fazer uma consulta muito complexa para o PostgreSQL de maneira nativa (em SQL puro), onde um certo número de variáveis ​​deve estar presente. Mas há um pouco desagradável no Rails, a funcionalidade de executar consultas nativas não permite o uso de ligações nomeadas. Mas existe uma solução :) Testada e implementada com sucesso em um projeto com a API do Rails 5.2 + Ruby 2.6.0 + Postgres 11.

Então, um pouco mais sobre o problema. O principal método que permite executar suas próprias consultas SQL é 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 

O exemplo acima mostra que a formação de ligantes ocorre, para dizer o mínimo, em um único local, quando tentamos obter notícias abaixo do número 100 do banco de dados.Bindings não podem ser nomeados, mas apenas numerados. E isso complica bastante a leitura e o suporte de consultas nativas. Como alternativa, você pode usar a chamada do método find_by_sql para a classe model:

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

Tudo é mais agradável e compreensível aqui. Mas a questão é: isso é mais ou menos aceitável se você deseja executar uma solicitação simples. Mas se a solicitação for realmente complicada, executá-la através do modelo e do próprio Active Record é uma grande perda de velocidade (lentamente) e desempenho (ela consome recursos do servidor). Por que não há binders nomeados ao trabalhar com consultas nativas é um mistério para mim, mas existe uma solução - escreva meu próprio pequeno invólucro, que pode trabalhar com os binders nomeados com muita simplicidade, o que eu fiz.

Trago o código da classe 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 você pode ver no código, tudo é tão simples quanto um canto da casa. A consulta funciona assim:

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

A saída é sempre apenas um hash. Uma pequena explicação. O método execute usa uma string de consulta e um hash com ligantes. É claro que as ligações na solicitação e no hash devem corresponder. Depois disso, percorremos o hash com ligantes e os substituímos por variáveis ​​numeradas do formato $ 1, $ 2 etc. na própria solicitação, criando simultaneamente uma matriz de valores numerados, em que o primeiro elemento da matriz é $ 1, o segundo é $ 2 e assim por diante. Depois disso, executamos a consulta usando o método exec_query padrão, executando a resposta com o mapeador e convertendo as chaves no hash em caracteres. Depois disso, novamente executamos o mapeador de acordo com a resposta, onde verificamos cada valor de campo para o conteúdo de JSON nele. Se houver JSON e for válido, converta-o em um hash com chaves e símbolos, se o campo não for JSON, em seguida, lance uma exceção na qual retornamos o valor. Isso é tudo.

Como você pode ver, não faz sentido colocar centenas de todos os tipos de pedras preciosas, desperdiçando o desempenho geral para obter o resultado desejado. Você pode escrever muitas decisões necessárias muito rapidamente, gastando um tempo e código mínimos.

Links para o github e cortar gemas com o plugin:
github.com/kirill-dan/active_sql_bindings
rubygems.org/gems/active_sql_bindings

Boa sorte a todos, até breve.

Reimprima a partir do seu próprio blog. Original aqui

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


All Articles