Rel + Postgres + binding

gambar

Halo teman-teman. Bukan rahasia lagi bahwa bekerja pada proyek-proyek besar dengan logika kompleks, Rekaman Aktif tidak menjadi asisten, tetapi beban. Bayangkan Anda perlu membuat kueri yang sangat kompleks untuk PostgreSQL dengan cara asli (dalam SQL murni), di mana sejumlah variabel harus ada. Tetapi ada satu hal yang tidak menyenangkan di Rails, fungsionalitas menjalankan query asli tidak memungkinkan penggunaan binding yang disebutkan. Tetapi ada solusinya :) Diuji dan berhasil diimplementasikan pada proyek dengan Rails API 5.2 + Ruby 2.6.0 + Postgres 11.

Jadi, sedikit tentang masalahnya. Metode utama yang memungkinkan Anda untuk mengeksekusi query SQL Anda sendiri adalah 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 

Contoh di atas menunjukkan bahwa pembentukan binder terjadi, dengan kata lain, melalui satu tempat, ketika kami mencoba untuk mendapatkan berita di bawah angka 100 dari database. Binding tidak dapat disebutkan namanya, tetapi hanya diberi nomor. Dan ini sangat menyulitkan membaca dan mendukung pertanyaan asli. Atau, Anda dapat menggunakan panggilan metode find_by_sql untuk kelas model:

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

Semuanya lebih menyenangkan dan dapat dimengerti di sini. Tetapi pertanyaannya adalah, ini kurang lebih dapat diterima jika Anda ingin melakukan permintaan sederhana. Tetapi jika permintaan benar-benar rumit, maka menjalankannya melalui model dan Rekaman Aktif itu sendiri adalah kehilangan besar dalam kecepatan (lambat) dan kinerja (memakan sumber daya server). Mengapa tidak ada pengikat bernama ketika bekerja dengan kueri asli adalah misteri bagi saya, tetapi ada solusi - tulis pembungkus kecil saya sendiri, yang dapat bekerja dengan pengikat bernama sangat sederhana, yang saya lakukan.

Saya membawa kode kelas statis:

 # 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 

Seperti yang dapat Anda lihat dari kode, semuanya sesederhana sudut rumah. Kueri bekerja seperti ini:

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

Output selalu hanya hash. Sedikit penjelasan. Metode eksekusi mengambil string kueri dan hash dengan binder. Jelas bahwa binding dalam permintaan dan hash harus cocok. Setelah itu, kita menggilir hash dengan binder dan menggantinya dengan variabel bernomor dalam bentuk $ 1, $ 2, dll. Dalam permintaan itu sendiri, secara bersamaan membuat array nilai bernomor, di mana elemen pertama array adalah $ 1, yang kedua adalah $ 2, dan seterusnya. Kemudian kami mengeksekusi kueri menggunakan metode exec_query standar, menjalankan respons dengan mapper dan mengonversi kunci dalam hash ke karakter. Setelah itu, kami kembali menjalankan mapper sesuai dengan jawabannya, di mana kami memeriksa setiap nilai bidang untuk konten JSON di dalamnya. Jika ada JSON dan itu valid, lalu konversikan ke hash dengan kunci dan simbol, jika bidangnya bukan JSON, maka lemparkan pengecualian untuk mengembalikan nilai. Itu saja.

Seperti yang Anda lihat, tidak masuk akal untuk menempatkan ratusan jenis permata, menyia-nyiakan kinerja keseluruhan, untuk mendapatkan hasil yang diinginkan. Anda dapat menulis sendiri banyak keputusan yang diperlukan dengan sangat cepat, setelah menghabiskan waktu dan kode minimum.

Tautan ke github dan potong permata dengan plugin:
github.com/kirill-dan/active_sql_bindings
rubygems.org/gems/active_sql_bindings

Semoga beruntung untuk semuanya, sampai jumpa lagi.

Cetak ulang dari blog Anda sendiri. Asli di sini

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


All Articles