Na carreira como Desenvolvedor de Software, o conhecimento de SQL (Structured Query Language) é uma habilidade importante. Estando presente no nosso dia a dia de trabalho ou sendo necessária apenas em momentos esporádicos, é um conhecimento que vez ou outra vamos utilizar. Para nós, profissionais da área, é melhor saber do que não saber. Entender SQL pode nos trazer novas possibilidades, nos permitir ter um controle maior sobre as operações de dados e nos trazer mais oportunidades profissionais.
Embora o Rails facilite o nosso trabalho, simplificando a interação com o banco de dados por meio do ActiveRecord, é comum ficarmos dependentes dessa conveniência oferecida. Acredito que isso ocorra com o uso de outros ORMs, e não é nossa culpa. Muitas vezes trabalhamos em ambientes dinâmicos, de muitas entregas, e em nosso tempo de estudo focamos em aprender novas tecnologias. Dessa forma, podemos esquecer de praticar conhecimentos que já estudamos na faculdade ou no início dos nossos estudos de programação. E por ser um conhecimento que não está sendo utilizado com frequência podemos acabar esquecendo. Assim como um músculo que atrofia se deixar de fazer exercícios, essas habilidades também podem atrofiar.
Neste artigo vamos explorar como fazer queries SQL puro no Rails, a utilização de Banco de Dados no Ruby sem ActiveRecord, e dicas para praticar o conhecimento de SQL
Como e quando fazer queries SQL puro no Rails?
Apesar do ActiveRecord cuidar da maior parte das nossas interações com o banco de dados, há certos momentos que escrever consultas diretas em SQL se torna mais interessante. Por exemplo, em casos onde exista a necessidade de realizar consultas complexas que envolvam joins entre várias tabelas, agregações e subconsultas, é possível que o ActiveRecord seja mais difícil de usar, apresente limitações e tenha menor desempenho. Isso é algo que acontece no uso de ORMs, em situações específicas pode ser mais trabalhoso entender como a ORM realiza determinada consulta, do que escrever diretamente em SQL. Desempenho, Complexidade e Limitações são pontos importantes, que podem tornar necessário o uso das queries feitas diretamente no banco de dados.
Para executar instruções SQL no rails podemos utilizar o método ActiveRecord::Base.connection.execute()
.
sql = "SELECT * FROM users WHERE age > 18"
result = ActiveRecord::Base.connection.execute(sql)
# Caso nossa query tenha um retorno, ele será um objeto, de tipo result do banco de dados em uso, que poderá ser iterado
result.each do |row|
puts "User ID: #{row['id']}, Name: #{row['name']}, Age: #{row['age']}"
end
Um detalhe importante: Da versão do Rails 7.2 em diante, o ActiveRecord::Base.connection
foi descontinuado. Nessas versões recentes ele foi substituído por ActiveRecord::Base.lease_connection
.
Utilizando Banco de Dados no Ruby sem ActiveRecord
Em aplicações Ruby com Sinatra é possível também utilizar a Gem do ActiveRecord para fazer esse papel de interação com o banco de dados. Mas e se quisermos fazer essa conexão sem o uso do ActiveRecord e ORMs?
Para mostrar um exemplo de conexão e interação com o banco de dados fiz um script Ruby puro utilizando a Gem pg
. Essa gem faz a comunicação entre Ruby e o SGBD PostgreSQL. O script de exemplo pode ser acessado no repositório do GitHub
Esse Script é composto de alguns métodos, e o bloco que o executa é este:
begin
start_database
conn = connect_to_database
create_pets_table(conn)
populate_pets_table(conn)
show_pets(conn)
rescue PG::Error => e
puts "Erro: #{e.message}"
ensure
conn.close if conn
end
O primeiro método chamado é o start_database
. Esse método faz uma validação para saber se o container postgres já existe. Caso não exista, ele executa um comando para criar um container docker da imagem postgres com o nome do banco de dados training_db
, usuário trainer
e senha secret
, e mapeia a porta do container para a porta 5433 . Se existir ele apenas sobe o container:
def start_database
puts 'Iniciando container de banco de dados'
if container_exists?
system('docker start postgres_training')
else
system("docker run --name postgres_training -e POSTGRES_USER=trainer -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=training_db -p 5433:5432 -d postgres")
end
sleep (7)
end
Após isso é chamado o método connect_to_database
para criar a conexão com o banco de dados. O método contém o seguinte código:
def connect_to_database
# Alias para PG::Connection.new e PG::Connection.open
PG.connect(
dbname: 'training_db',
user: 'trainer',
password: 'secret',
host: 'localhost',
port: 5433
)
end
Podemos nos conectar a um banco de dados usando o método PG.connect
, que seria o mesmo que usar PG::Connection.new
ou PG::Connection.open
. Nele passamos os argumentos do banco de dados. Nesse nosso exemplo passamos as informações definidas no nosso container postgres do docker e a porta que foi mapeada. Por padrão a porta do postgres é 5432.
Em seguida armazenamos essa conexão feita em connect_to_database
na variável conn
para utilizar essa conexão em outros métodos. Depois é chamado o método create_pets_table(conn)
. Nele utilizamos o método .exec
, para executar nossas instruções SQL:
def create_pets_table(conn)
conn.exec("CREATE TABLE IF NOT EXISTS pets (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
species VARCHAR(100),
age INT
);")
puts 'Tabela de pets criada!'
end
Esse método .exec
é responsaǘel por fazer queries SQL. Em caso de sucesso retorna um objeto PG::Result com os resultados da query, e em caso de falha retorna um PG::Error.
Na gem PG também temos o método .exec_params
, muito útil se precisamos fazer uma consulta usando parâmetros. Podemos ver seu uso seguindo o script de exemplo, quando chamamos populate_pets_table(conn)
, que contém o seguinte código:
def populate_pets_table(conn)
pets = [['ScoobyDoo', 'Dog', 5], ['Pikachu', 'Pokemon', 3], ['Noturno', 'Cat', 6], ['Tanguá', 'Monkey', 7]]
pets.each do |pet|
conn.exec_params("INSERT INTO pets (name, species, age) VALUES ($1, $2, $3);", pet)
end
puts 'Dados inseridos na tabela de pets!'
end
Nesse método tentei fazer uma ideia de seeds para popular a tabela pets. No trecho conn.exec_params("INSERT INTO pets (name, species, age) VALUES ($1, $2, $3);", pet)
, o exec_params
faz uma query para inserir dados na tabela pets: nos campos name, species e age serão inseridos os valores $1, $2 e $3.
Esses valores que vão ser inseridos devem ser informados em um array, que nos caso é a variável pet. Nesse bloco que está sendo iterado será executado primeiro conn.exec_params("INSERT INTO pets (name, species, age) VALUES ($1, $2, $3);", ['ScoobyDoo', 'Dog', 5])
, depois conn.exec_params("INSERT INTO pets (name, species, age) VALUES ($1, $2, $3);", ['Pikachu', 'Pokemon', 3])
e assim por diante.
Seguindo o código do script, é chamado o método show_pets(conn)
que usa o método exec para fazer uma querie buscando tudo na tabela pets e armazenando em uma variável que será iterada depois para printar os registros: result = conn.exec("SELECT * FROM pets")
.
No fim do script temos um rescue
que utiliza o PG::Error para fazer a tratativa de erros que possam ocorrer na nossa conexão e interação com o banco de dados. E o ensure
para garantir que a conexão será encerrada mesmo que ocorra algum erro.
rescue PG::Error => e
puts "Erro: #{e.message}"
ensure
conn.close if conn
end
Encerrar a conexão é uma boa prática, pois garante a liberação de recursos do sistema, evita problemas de desempenho e vazamento de informação.
Dicas para praticar SQL
Existem sites que podemos praticar, como SQL Teaching, SQLZoo e W3Schools. Também jogos como o SQL Murder Mystery. Além disso podemos baixar o MySQL Workbench ou pgAdmin, que são ferramentas gráficas onde podemos explorar os comandos SQL, de forma mais intuitiva e visual.
No mesmo repositório desse script utilizado como exemplo, tem outro arquivo, learning_sql.rb
, onde fiz um script interativo com uma interface que permite o usuário executar consultas SQL e aprender a fazer queries de forma prática.
Sei que podemos acabar esquecendo os conhecimentos se não colocarmos em prática, e no caso do desenvolvimento no Rails pode ser comum esquecermos o conhecimento SQL, já que ele faz as queries por nós. Fiz esse artigo com o intuito de trazer um compilado de informações que podem ajudar outros Devs que eventualmente enfrentarão esse problema.
Agradeço seu tempo e sua leitura! E vamos por a mão na massa no código!