Toda base de datos SQL posee tablas para almacenar los datos. Gracias al ORM Diesel y sus migraciones, automatizaremos la creación y actualización de las mismas.
Primera tabla con Diesel
Comienza creando tu primera migración con el comando diesel migration generate create_posts. El mismo producirá un nuevo directorio dentro de /migrations que contiene dos archivos: up.sql y down.sql. En el primero, escribiremos el código SQL para generar o modificar nuestras tablas, en el segundo el código para destruir y borrar las mismas.
-- up.sqlCREATETABLE posts ( id SERIALPRIMARYKEY, title VARCHARNOTNULL, slug VARCHARNOTNULL, body TEXTNOTNULL);
-- down.sqlDROPTABLE posts;
A continuación, utiliza el comando diesel migration run que leerá el archivo up.sql y creará el esquema apropiado para la tabla posts. Caso contrario, si quieres destruir estas tablas, utiliza diesel migration redo.
Qué son los Macros en Rust
Los Macros son similares a las funciones, con la desventaja de que su implementación es más compleja porque estás escribiendo código Rust, que generará código Rust. La definición de un Macro es generalmente más difícil de leer, entender y mantener que una función.
Este es un concepto bastante avanzado y difícil de comprender, pero es una buena oportunidad para tenerlos presente e ir averiguando más de ellos más adelante.
Otra importante diferencia entre los Macros y las funciones comunes es que debes definir su alcance antes de llamarlos en un archivo, al contrario de las funciones donde los defines donde quieras y los llamas cuando quieres.
Los Macros son altamente poderosos, permiten manipular el core de Rust o cambiar el comportamiento de algo. Si tienes experiencia con otros lenguajes como Javascript, Python, tal vez te sirva hacer una analogía con el concepto de "Decoradores" para cambiar el comportamiento de una clase o variable.
Es algo complicado de entender, pero sabiendo que son similares a las funciones y cambian el comportamieto de una cosa, podemos continuar.
Configuración del proyecto
Para establecer la conexión con la base de datos y realizar consultas a la tabla que hemos creado llamado posts, primero es necesario realizar varias importaciones en el archivo main.rs:
// Indicamos que vamos a utilizar macros#[macro_use]// Importamos Dieselexterncratediesel;// Importamos la conexión con PostgreSQLusediesel::prelude::*;usediesel::pg::PgConnection;// Importamos librerias para leer variables de entornousedotenv::dotenv;usestd::env;// Importamos esquemas de BBDD y modelospubmodschema;pubmodmodels;fnmain(){}
También vamos a crear una estructura que nos servirá como modelo de nuestra tabla posts en la base de datos y realizar el mapeo correspondiente.
Crea un archivo llamado models.rs que contendrá lo siguiente:
// Macro para indicar que los registros de la BBDD tendrán la misma forma que la estructura.#[derive(Queryable)]pubstructPost{pub id:i32,pub title:String,pub slug:String,pub body:String,}
Conexión con la BBDD
Teniendo listo todas las importaciones, establezcamos la conexión con la base de datos y ejecutemos la primera consulta.
// ...fnmain(){// Indicamos que vamos a utilizar el esquema de Posts y el modelouseself::models::Post;useself::schema::posts;useself::schema::posts::dsl::*;// Lectura de variables de entornodotenv().ok();let db_url =env::var("DATABASE_URL").expect("La variable de entorno DATABASE_URL no existe.");// Conexión con la BBDDlet conn =PgConnection::establish(&db_url).expect("No se ha podido establecer la conexión con la base de datos.");// Realizamos la consulta equivalente a: SELECT * FROM posts;let posts_result = posts.load::<Post>(&conn).expect("Error en la consulta SQL.");// Iteramos los resultados de la consultafor post in posts_result {println!("{}", post.title);}}
Finalmente, utiliza cargo run para correr tu código y verificar si está correctamente establecida la conexión a la base y si se ejecuta la consulta. Recuerda que aún no tenemos registros en la tabla, así que la misma no devolverá resultados.
Has logrado hasta aquí conectarte a una base de datos SQL con Rust y realizar consultas. ¡Felicidades!
Algo que no sabía, y que puede ser útil por si tampoco lo sabías, es que, lo que hicimos en las siguientes líneas, es la manera de crear o importar un módulo público en Rust (Un módulo puede contener métodos, constructors y structs)
pub mod models;pub mod schema;
De igual manera, en cosas como models, tenemos estructuras con variables públicas, es decir, que cualquiera que lo importe, puede acceder a esas variables
Es decir, sin la palabra pub, ese campo sería por defecto privado y solo el módulo que contiene la estructura puede acceder a él
.
Te dejo la parte de la documentación de Rust en el que hablan más a detalle de esto, con un gran ejemplo :)
Creo que a fecha de hoy que realizo este post, creo que hay un pequeño cambio con diesel y es que requiere que su conexión debe ser de tipo variable mutable.
Ya que cuando hacia el "SELECT" me daba un error, por lo mismo y es que load requiere una conexión mutable.
Al final me quedo así:
let mut conn =PgConnection::establish(&database_url).expect("we didn't connect to the database");let posts_result = posts.load::<Post>(&mut conn).expect("Error excecuting query");
Espero poder apoyar a alguien que le pueda pasar lo mismo.
En la documentacion de Diesel aparece:
let conn =&mut PgConnection::establish(&db_url).expect("No nos pudimos conectar a la BD");let posts_result = posts.load::<Post>(conn).expect("Error al ejecutar el query");
Gracias Caveli, de hecho revisando si, pero entiendo que los & significa que se va a pasar la referencia de una variable. Al final usted y yo tenemos lo mismo casi lo mismo.
En mi caso la conn es mutable pero no es referencia, en la suya ya es la referencia.
y en post result yo lo paso como referencia y usted no lo hace ya que su variable ya es la referencia de la conneccion.
Esto de las referencias es todo un tema en rust pero básicamente es tener una variable y esta variable apunta a otra sin generar una nueva instancia. Es como llamarla por su nombre sin almacenar nada... solo referencia a una nueva variable.
Mencionar que ahora se debe importar dotenvy:
use dotenvy::dotenv;
En Cargo.toml
[dependencies]
diesel = { version = "2.0.0", features = ["postgres"] }
dotenvy = "0.15"
Poe eso se usa dotenvy
en el curso basico no se explico que son los modulos, ni las importacion, tampoco sobre las macros, no he entendido gran parte del codigo que se escribio, ¿se actualizara el curso basico o se hara otro curso intermedio explicando mas sobre esos puntos?
Por lo que veo en la agenda de Platzi, no creo que se haga otro curso de Rust pronto, pero te puedo recomendar un canal de youtube que te puede servir con esos temas. El canal se llama let's get rusty
Es normal que la reversión de la creación de las tablas no funcione? Al momento de ejecutar el comando diesel migration redo no se borra la tabla creada dentro de mi base de datos aunque el comando se ejecuta sin errores
Revisa si down.rs tiene la query para eliminar la tabla
Una breve acotación:
diesel migration revert restablece eliminando la migracion.
diesel migration redo realiza por detrás:
-diesel migration revert
-diesel migration run
Es decir resume los dos comandos en uno.
XD, por fin entiendo porque no funcionaba, me molesta mucho, ir a la terminal y lo que dicen los profes no funciona o hacen cosas distintas ...
El tutor Hector creo que se confunde al decir que: #[derive(Queryable)] es un macro.
Realemente esto en Rust es un atributo (attribute). Los atributos son anotaciones que se colocan en el código para cambiar su comportamiento o dar información adicional al compilador. En este caso, el atributo #[derive(Queryable)] se utiliza para generar automáticamente el código necesario para que un tipo implemente la interfaz diesel::Queryable de la biblioteca Diesel de Rust, que se utiliza para convertir las filas de una base de datos en un tipo Rust.
Aquí les dejo un poco mas de información referente a los atributos en Rust por si desean tener un poco mas de información al respecto: https://doc.rust-lang.org/reference/attributes.html
Al igual que la "macro" #[macro_use], es un atributo que se utiliza para importar macros de otros módulos. Cuando se aplica este atributo a un módulo, se importan las macros definidas en ese módulo y se hacen disponibles para su uso en el ámbito de ese módulo.
En Rust, las macros son funciones que toman una entrada y generan código de salida. Se usan para automatizar tareas repetitivas o para proporcionar funcionalidad que no está disponible en el lenguaje. La macro #[macro_use] se utiliza comúnmente con las macros que se proporcionan en las bibliotecas de Rust para hacer que esas macros estén disponibles en el ámbito del módulo en el que se utilizan.
Por ejemplo, si una biblioteca proporciona una macro llamada my_macro, se puede importar y hacer que esté disponible en un módulo utilizando el atributo #[macro_use] de la siguiente manera:
En este ejemplo, la macro my_macro está disponible para su uso en el ámbito de main porque se importó con #[macro_use].
Espero que esta pequeña nota pueda ser de ayuda a quien lo necesite. #[...] vs println!, panic! o cualquier funcion que termine en ! no es lo mismo. Las funciones que terminan en !, son funciones macro y las anotaciones que tienen esta forma: #[...] son atributos.
#[derive(Queryable)] es una macro derivada que implementa el rasgo Queryable para una estructura. Este rasgo permite utilizar la estructura con la biblioteca Diesel para cargar el resultado de consultas tipadas estáticamente.
Las macros derivadas son un tipo de macro procedimental en Rust que permite generar automáticamente código para ciertos rasgos anotando una estructura o enum con #[derive(TraitName)]. La macro toma el TokenStream de entrada, que representa los tokens que componen la definición del struct o enum, y genera un nuevo TokenStream que contiene el código para la implementación del trait.
La mayoría de la gente utiliza las cajas syn y quote para analizar el código Rust entrante y luego generar nuevo código.
Al parecer ahora se debe guardar la conexión de la siguiente manera:
let conn =&mut PgConnection::establish(&db_url).expect("No nos pudimos conectar a la base de datos");
Recuerden que con el cargo, no hay que aplicar el instal solo dentro de las dependecias como DOTENV, tambien tenemos que añadir diesel y diesel-cli para que no llorén estas!
Obtuve este error durante el dieser setup:
Solo sucede en WSL, en powershell funciona.
Estoy teniendo problemas con la usar la conexión para traer los posts.
Me dice esto:
expected &mut {unknown}, found &PgConnection
Yo tengo el mismo problema, pero lo solucioné haciendo una función nueva
pub fn establish_connection()->PgConnection{dotenv().ok();let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");PgConnection::establish(&database_url).unwrap_or_else(|_| panic!("Error connecting to {}", database_url))}
Y llamando la así en la variable de la conexión y quitando el & del &conn
let conn =&mut establish_connection();let posts_result = posts.load::<Post>(conn).expect("Error en consulta SQL");
Agrego nuevamente la función que me quedo refeo en el primer post.
pub fn establish_connection()->PgConnection{dotenv().ok();let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");PgConnection::establish(&database_url).unwrap_or_else(|_| panic!("Error connecting to {}", database_url))}
Y llamando la así en la variable de la conexión y quitando el & del &conn
let conn =&mut establish_connection();//SELECT * FROM postslet posts_result = posts.load::<Post>(conn).expect("Error en consulta SQL");
Las macros son como la meta programación???
¡Exacto!
hola, al colocar las macros me indica este error (unused #[macro_use] import #[warn(unused_imports)] on by default) e buscado todo el dia y no se todavia como solucionarlo
Eso no es un error, es un warning, puedes continuar con el curso y eventualmente cuando usemos ese macro se irá la advertencia
con las versiones actuales de rust, no anda casi nada :p
gracias a la mayoría de los comentarios del curso , logré que funcionará :)
El error que mencionas sugiere que el módulo diesel::pg::prelude no se está importando correctamente en tu código. Asegúrate de que tienes Diesel correctamente agregado en tu Cargo.toml, incluyendo las dependencias necesarias para PostgreSQL.
Verifica que la importación sea similar a esta en tu archivo:
Además, asegúrate de que la versión de Diesel que estás usando sea compatible con tu proyecto y que diesel_cli esté instalado si necesitas ejecutar migraciones.
Yo tome la desicion de realizarlo con MYSQL para hacerlo mas rapido y sencillo de conectar los procesidimientos son los siguientes:
Cargo.tml
diesel = { version = "2.1.0", features = ["mysql"] }
Conexion:#[macro_use]extern crate diesel;
pub mod models;pub mod schema; // Modelos
use diesel::mysql::MysqlConnection;use diesel::prelude::*;use dotenvy::dotenv;use std::env;
fn main() { dotenv().ok(); let db_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let <u>conn</u> = &mut MysqlConnection::establish(&db_url).expect("Error connecting to database");
use self::models::Post; use self::schema::posts::dsl::*;
let results = posts .limit(5) .load::<Post>(<u>conn</u>) .expect("Error loading posts");
for post in results { println!("{}", post.title); println!("----------\n"); println!("{}", post.body); }
println!("Hello, world!");}
#[macro_use]extern crate diesel;pub mod models;pub mod schema;// Modelosuse diesel::mysql::MysqlConnection;use diesel::prelude::*;use dotenvy::dotenv;use std::env;fn main(){dotenv().ok();let db_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");let conn =&mut MysqlConnection::establish(&db_url).expect("Error connecting to database"); use self::models::Post; use self::schema::posts::dsl::*;let results = posts
.limit(5).load::<Post>(conn).expect("Error loading posts");for post in results { println!("{}", post.title); println!("----------\n"); println!("{}", post.body);} println!("Hello, world!");}
Macros vs. Functions
The downside to implementing a macro instead of a function is that macro definitions are more complex than function definitions because you’re writing Rust code that writes Rust code. Due to this indirection, macro definitions are generally more difficult to read, understand, and maintain than function definitions.
Another important difference between macros and functions is that you must define macros or bring them into scope before you call them in a file, as opposed to functions you can define anywhere and call anywhere.