Definición de rutas con React Router

En una aplicación React se utilizan “rutas” para definir URLs que, a su vez, activarán ciertos componentes.

Lo habitual es que nuestra aplicación tendrá un componente principal, comunmente llamado App, y será aquí donde realizaremos la definición de las rutas y otras configuraciones como el layout.

Como pre-requisito para utilizar esta funcionalidad, debemos importar los componentes BrowserRouter, Switch y Route con la siguiente directiva:

import {BrowserRouter, Route, Switch} from 'react-router-dom';

La implementación se realiza dentro del componente función App (o sea, que no lleva render()):

function App() {
  return (
    <BrowserRouter>
      <Layout>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route exact path="/posts" component={Posts} />
          <Route exact path="/posts/new" component={PostsNew} />
          <Route exact path="/posts/:postId/edit" component={PostsEdit} />
          <Route component={NotFound} />
        </Switch>
      </Layout>
    </BrowserRouter>
  );
}

En este ejemplo, tenemos 5 rutas. Las rutas son elegidas por React en base a un elemento Switch. Según la ruta que escribamos en el navegador, si encuentra una coincidencia exacta con la propiedad path, activará el component definido en la propiedad component.

Por ejemplo, si escribimos la ruta /posts/new en el navegador, se activará el componente PostsNew (obviamente, todos los componentes que se llaman aquí deben ser importados en el archivo JS).

Tenemos también un ejemplo con una ruta que recibe una variable postId. Esta variable se indica con dos puntos adelante. También tenemos el caso por descarte, si no encuentra ninguna de las rutas indicadas, mostrará el componente NotFound.

Nótese que el componente que se active estará inserto dentro del componente Layout. Este es un componente especial que se encarga de mostrar el diseño común para todas las páginas del sitio. El código completo del componente Layout podría ser algo así:

import React from 'react';

import MyNavbar from './MyNavbar';

function Layout(props) {
  return (
    <React.Fragment>
      <h1>Centraldev</h1>
      <MyNavbar />
      {props.children}
      <footer>&copy; 2021 Centraldev - All rights reserved</footer>
    </React.Fragment>
  );
}

export default Layout;

Igualmente, tengan en cuenta que esto, a su vez, irá dentro del div#app que normalmente encontramos en el archivo public/index.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
  <title>Mi página</title>
</head>

<body>
  <div id="app"><!-- aquí se inserta todo --></div>
</body>

</html>

¿Por qué es esto? Esto es porque en el archivo index.js (en src) tenemos lo siguiente:

const container = document.getElementById('app');
ReactDOM.render(<App />, container);

Este diagrama explica cómo se intercomunican los distintos archivos:

Archivos básicos en React.

Consumo de una API con React

Vamos a ver un ejemplo muy simple y básico de cómo proceder a implementar el consumo de una API REST desde React. Este artículo asume que ya se tienen algunos conocimientos básicos de React.

El primer paso consiste en inicializar las variables que necesitaremos para manejar los distintos estados de nuestro componente, para guardar los datos que recibimos de la API y el número de página.

Estas variables las crearemos como variables de estado pero antes vamos a inicializarlas con sus valores predeterminados:

state = {
    loading: true,
    error: null,
    data: {
      results:[]
    },
    nextPage: 1,
  };

Este código también puede ir en el constructor.

Los datos los vamos a solicitar a la API en el evento componentDidMount(), es decir, cuando el componente se ha insertado en el DOM. Para esto vamos a llamar a una función fetchData() que también crearemos:

componentDidMount(){
  this.fetchData();
}

fetchData = async () => {
  this.setState({ loading: true, error: null });

  try {
    const response = 
      await fetch(${url}?page=${nextPage});    const data await response.json();

    this.setState({
      data: { results: [].concat(this.state.data.results, data.results) },
      loading: false,
      nextPage: this.state.nextPage + 1
    });
  }
  catch(error){
    this.setState({loading: false, error: error});
  }
}

La función fetchData es asíncrona porque a su vez llama a métodos asíncronos, como es fetch. Por eso la debemos declarar con la palabra clave async.

En la función empezamos asignando el estado de loading en true ya que queremos indicar que estamos realizando la carga de datos desde la API. Luego utilizaremos esta variable para mostrar un mensaje de “loading” o también se puede emplear una animación. La variable de estado error la asignamos en null porque no tenemos ningún error de carga.

Dentro de un bloque try llamamos a la función fetch, la cual es asíncrona, por eso debemos llamarla con la palabra clave await. El método fetch es el que realiza la petición a la API y recibe, como argumento, la URL hacia donde debemos enviar la petición. El resultado de esta petición lo guardamos en la variable response.

Para obtener los datos a partir de la variable response llamamos a otra función asíncrona que es json().

Los datos obtenidos los guardaremos en la variable de estado data, mediante el método setState(). En nuestro ejemplo, el array con los resultados son un objeto results dentro de data.

Para ir sumando los datos que obtenemos en sucesivas paginaciones utilizamos el método concat() y vamos agregando estos resultados a this.state.data.results.

Render

El listado de los resultados es muy sencillo. Lo haremos en la función render(). Para esto utilizaremos map():

<ul>
  {this.state.data.results.map(character => (
    <li key={character.id}>{character.name}</li>
  ))}
</ul>

En el método render también vamos a mostrar el texto (o animación) para el estado “loading” y el mensaje de error, si existiese:

{this.state.loading && (
  <div>Loading...</div>
)}

if (this.state.error) {
  return (
  <div>An error has occurred: {this.state.error.message}</div>
)}

Por último, no debemos de olvidarnos del botón para cargar los resultados de las siguientes páginas. Este será un botón que solo se mostrará cuando this.state.loading sea false y llamará a la función fetchData() en su evento onClick:

{!this.state.loading && (
  <button onClick={() => this.fetchData()}>Load more</button>
)}

Consulta el tamaño de las tablas en SQL Server

Cuando estamos analizando por qué una base de datos ocupa mucho espacio, una forma de investigarlo es visualizar el tamaño que ocupa cada tabla.

Esto puede obtenerse fácilmente con una consulta como esta:

USE [NombreBD] -- reemplazar nombre base de datos
GO
SELECT
s.Name AS SchemaName,
t.Name AS TableName,
p.rows AS RowCounts,
CAST(ROUND((SUM(a.used_pages) / 128.00), 2) AS NUMERIC(36, 2)) AS Used_MB,
CAST(ROUND((SUM(a.total_pages) - SUM(a.used_pages)) / 128.00, 2) AS NUMERIC(36, 2)) AS Unused_MB,
CAST(ROUND((SUM(a.total_pages) / 128.00), 2) AS NUMERIC(36, 2)) AS Total_MB
FROM sys.tables t
INNER JOIN sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
GROUP BY t.Name, s.Name, p.Rows
ORDER BY s.Name, t.Name
GO 


Tengamos en cuenta que las bases de datos de SQL Server Express solo pueden ocupar hasta 10 GB de espacio por lo tanto es importante monitorear este dato.

SQL Table Size Query

Cómo reducir el tamaño del archivo de Log de SQL Server

SI nuestro archivo de log es muy grande probablemente tengamos problemas con el espacio en el disco más pronto que tarde.

Si nuestra base de datos tiene muchas transacciones pero maneja poco volumen de datos, es común que tengamos un archivo de datos (.mdf) relativamente pequeño y un archivo de log bastante más grande (.ldf).

Una de las operaciones más comunes para achicar el tamaño del archivo de log es recurrir a la opción Tasks > Shrink > Files del SQL Server Management Studio.

En la lista File type escogeremos entonces Log y luego nos mostrará en Available free space cuanto espacio se puede liberar. Para ejecutar la operación, hacer click en OK.

Sin embargo, es posible que ejecutes la operación Shrink File pero el tamaño del log no se reduce.

En este caso hay una alternativa que podemos emplear en el caso de bases de datos que NO son de producción: Para esto hacemos click derecho sobre la base de datos y elegimos Properties y luego Options. En la opción Recovery Mode nos aseguramos que diga Simple (no Full). Luego procedemos a hacer el Shrink como explicamos arriba.

Alternativamente podemos ejecutarlo en Transact-SQL:

ALTER DATABASE mydatabase SET RECOVERY SIMPLE
DBCC SHRINKFILE (mydatabase_Log, 1)

Tengamos en cuenta que el Recovery Mode afecta la posibilidad de restaurar una base en el futuro ya que en los logs se guarda la información de todas las transacciones. Si queremos poder volver a restaurar la base a un estado pasado en el futuro, tenemos que asegurarnos de hacer una copia de seguridad del archivo de log.

 

Tutorial: Accedé a la API de Foursquare con PHP

En este tutorial veremos un ejemplo bastante simple sobre cómo acceder a la API de Foursquare desde PHP.

El primer paso es registrar una aplicación. No importa que se trata de una aplicación de prueba. Lo importante es tener definido ya el servidor donde la alojaremos. Para esto vayan a la URL https://es.foursquare.com/developers/register.

Captura de pantalla 2015-01-28 a las 16.28.33

Allí, completen los datos que les piden para su aplicación y obtendrán los dos datos más importantes: el client_id y el client_secret. Estos datos los deben resguardar ya que son las credenciales de autenticación de su aplicación.

Ahora sí, ya pueden empezar a programar.

Básicamente, la interacción con Foursquare se maneja de la siguiente manera:

Se dirige al usuario a la página de autorización de Foursquare (https://foursquare.com/oauth2/authenticate). Aquí el usuario deberá autorizar el acceso de nuestra aplicación a su cuenta de Foursquare. Para esto, el usuario deberá loguearse con sus datos de Foursquare, si es que ya no está logueado.

Para esto, le proveemos al usuario de un link en nuestra aplicación hacia la siguiente URL:

 https://foursquare.com/oauth2/authenticate?client_id=<?=CLIENT_ID?>&response_type=code&redirect_uri=<?=REDIRECT_URI?>

Observen que debemos pasar dos parámetros: el client_id y la URL a donde queremos que Foursquare nos devuelva la respuesta.

Una vez que el usuario haya autorizado a nuestra aplicación, Foursquare nos devolverá un “code” en un parámetro de querystring, a la redirect_uri. Por ejemplo, http://redirect_URI/?code=XXX.

Con este code, el cual puede ser diferente con cada autenticación, deberemos obtener el token del usuario. Para esto llamaremos a la siguiente URL:

https://foursquare.com/oauth2/access_token
?client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&grant_type=authorization_code
&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
&code=CODE

En este caso, la respuesta será en JSON:

{ access_token: ACCESS_TOKEN }

Si vamos a guardar los usuarios en una base de datos, querremos también guardar este token con él.

Veamos cómo realizar esto con PHP:

if (isset($_GET['code'])) 
{
 $response = file_get_contents('https://foursquare.com/oauth2/access_token?client_id='. CLIENT_ID .'&client_secret='. CLIENT_SECRET .'&grant_type=authorization_code&redirect_uri=https://centraldev.net/4sqtest/index.php&code='.$_GET['code']); 
 $obj = json_decode($response);
 echo '<p><a href="index.php?page=checkins&access_token='. $obj->{'access_token'} .'">Ver checkins</a></p>';
}

Aquí vemos como accedemos a un bloque if al recibir el parámetro code por querystring y luego obtenermos el token. Seguidamente, utilizamos este token (en $obj->{‘access_token’}) para mostrar un link a otra página de nuestra aplicación que realizará otra tarea.

El siguiente paso ya es la interacción con los diferentes métodos de la API.

Por ejemplo, si queremos obtener todos los checkins de un usuario, podemos llamar al siguiente método:

https://api.foursquare.com/v2/users/self/checkins?v=20150128&oauth_token=TOKEN

Aquí vemos cómo obtener los checkins con PHP:

$url = 'https://api.foursquare.com/v2/users/self/checkins?v=20150128&oauth_token='. $access_token;
$response = file_get_contents($url);
$obj = json_decode($response);

Aquí ya tendremos los datos de los últimos checkins en la variable $obj. Aquí deberemos hacer referencia a la documentación para conocer cómo está estructurada la información JSON en esta variable.  Para esto podemos ir a la página que documenta el método (en este caso, https://developer.foursquare.com/docs/users/checkins) y allí hacer click en el botón Try it out para ver un ejemplo de utilización y los datos retornados.

Por ejemplo, para recorrer todos los checkins, podemos hacer lo siguiente:

foreach ($obj->{'response'}->{'checkins'}->{'items'} as $item)
{
  echo '<p><a href="https://es.foursquare.com/v/'.$item->{'venue'}->{'id'}.'">'. $item->{'venue'}->{'name'} .'</a> ('. 
 $item->{'venue'}->{'location'}->{'city'} .', '. $item->{'venue'}->{'location'}->{'country'} .') - '. 
 date('M Y', $item->{'createdAt'}) .'</p>';
}

De esa forma imprimiremos una lista de todos los checkins recientes.

Para un ejemplo de uso completo, he armado una pequeña aplicación de una página que lista todos los checkins realizados bajo la categoría “Hoteles”. El mismo puede ser encontrado aquí: https://centraldev.net/4sqtest/

El código fuente lo pueden bajar de https://centraldev.net/4sqtest/index.php.zip con la salvedad de que deberán crear un archivo config.php donde deberán definir dos constantes CLIENT_ID y CLIENT_SECRET con las credenciales de su aplicación, como se muestra a continuación:

<?
define('CLIENT_ID', 'XXX');
define('CLIENT_SECRET', 'XXX');
?>

 

Autenticación de usuarios con ASP.NET

ASP.NET ofrece tres mecanismos de autenticación: Windows, Passport y Formularios. De estos tres me voy a dedicar a explicar autenticación con formularios en detalle y voy a hacer un breve comentario sobre los otros dos.

La autenticación Windows permite al programador delegar la responsabilidad de la autenticación a Windows y al sistema de archivos NTFS, aprovechando también las funcionalidades de seguridad integradas en IIS. Normalmente, cuando un cliente realiza una petición a una páginas ASP.NET o a un web service, estas peticiones son manejadas por IIS. Si configuramos la autenticación en modo “Windows”, IIS delegará la tarea al sistema operativo. El usuario será autenticado en base a las credenciales que proveyó cuando inició sesión en Windows.

La autenticación Passport utiliza un servicio centralizado provisto por Microsoft. Para utilizar Passport hay que registrarse en el servicio, aceptar el acuerdo de licencia, pagar la cuota del servicio e instalar el SDK de Passport.

Finalmente, el método del cual vamos a hablar con mayor detalle es autenticación por formularios. Este método ofrece muchas ventajas al desarrollador, siendo la flexibilidad una de las más importantes. Será responsabilidad del desarrollador diseñar el formulario de inicio de sesión, implementar algún tipo de repositorio para las credenciales de usuario y realizar la validación contra ese repositorio, entre otras cosas.

Para utilizar autenticación por formularios es necesario realizar la siguiente configuración en el archivo web.config:

<configuration>
    <system.web>
        <authentication mode=”Forms”>
            <forms name=”auth_cookie” loginUrl=”Login.aspx” />
        </authentication>

        <authorization>
            <deny users=”?” />
        </authorization>
    </system.web>
</configuration>

Noten que puede haber, además de esto, otras configuraciones actualmente presentes en el web.config.

Con esto, lo que hemos hecho es:

  1. Definir el modo de autenticación.
  2. Denegar la autorización de acceso a los usuarios anónimos (simbolizados con el signo “?”).

Veamos ahora cómo realizar una autenticación sencilla siguiendo los siguientes pasos:

1.Creemos una página de inicio Default.aspx simple, para validar el ejemplo.

2.Creemos una página de login sencilla como la siguiente:

`<form id="form1" runat="server">
<h1>Login</h1>
<asp:Panel ID="panelMensaje" runat="server" Visible="true" EnableViewState="False" Width="100%">
  <asp:Label ID="lblMensaje" runat="server"></asp:Label>
</asp:Panel>
<p>Usuario:<br />
  <asp:TextBox ID="txtUsuario" runat="server"></asp:TextBox> </p>
<p>Password:<br />
 <asp:TextBox ID="txtPassword" runat="server" TextMode="Password"></asp:TextBox>
</p>
<p><asp:Button ID="btnLogin" runat="server" Text="Login" OnClick="btnLogin_Click" /></p>
</form>`

3.Utilicemos el siguiente code-behind:

public partial class Login : System.Web.UI.Page
{
  private const string usuario = "jose";
  private const string password = "abc123";

  protected void Page_Load(object sender, EventArgs e)
  {

  }
  protected void btnLogin_Click(object sender, EventArgs e)
  {
      if (txtUsuario.Text == usuario && txtPassword.Text == password)
      {
          FormsAuthentication.RedirectFromLoginPage(txtUsuario.Text, false);
      }
      else
      {
          panelMensaje.Visible = true;
          lblMensaje.Text = string.Format(
            "Usuario {0} no autenticado. Intente nuevamente.", txtUsuario.Text);
      }
  }
}

Con este ejemplo, y el web.config que comentamos antes, debería redirigirnos a la página Login.aspx cuando quisiéramos acceder a nuestra Default.aspx.

Otros parámetros del tag <form> son los siguientes:

  • protection: indica si la aplicación debe usar validación, encriptación, ninguno o ambos métodos para proteger la cookie. El valor predeterminado es All, que utiliza ambos métodos. None indica ningún método. Encryption utiliza Triple-DES para encriptar la cookie y Validation especifica que se valide la información leída en la cookie antes de utilizarla.
  • timeout: expecifica el número de minutos de vida de la cookie.
  • slidingExpiration: valor true o false indicando si la cookie se refresca con cada petición. En ASP.NET 1.1, el valor predeterminad es true y en 2.0 es false.

Autenticación contra el web.config

El ejemplo anterior mostraba cómo autenticar usuarios contra datos fijos en el archivo de code-behind. Otra forma de guardar los datos de los usuarios es en el archivo code-behind.

<configuration>
    <system.web>
        <authentication mode=”Forms”>
            <forms name=”auth_cookie” loginUrl=”Login.aspx”>
                <credentials passwordFormat="Clear">
                    <user name="marcelo" password="abc123" />
                    <user name="pablo" password="zxy987" />
                </credentials>
            </forms>
        </authentication>

        <authorization>
            <deny users=”?” />
        </authorization>
    </system.web>
</configuration>

Guardar los passwords en formato “Clear” no es lo más recomendable. Es posible también guardarlos hasheados en MD5 y SHA1. Para hashear los passwords podemos utilizar el método HashPasswordForStoringInConfigFile() de la clase FormsAuthentication.

Para autenticar de este modo debemos utilizar el método Authenticate de la clase FormsAuthentication. Autenticación contra una base de datos

Este tal vez es el método más común. En este ejemplo se trabaja con una función Authenticate dentro de la página aunque lo más frecuente es tener toda la lógica de autenticación en algún componente separado.

protected void btnLogin_Click(object sender, EventArgs e)
{
    if (Authenticate(txtUsuario.Text, txtPassword.Text))
    {
        FormsAuthentication.RedirectFromLoginPage(txtUsuario.Text, false);
    }
    else
    {
        panelMensaje.Visible = true;
        lblMensaje.Text = string.Format(
            "Usuario {0} no autenticado. Intente nuevamente.", txtUsuario.Text);
    }
}

public bool Authenticate(string usuario, string password)
{
    // defino la consulta (uso parámetros para evitar sql injection
    string query = "select count(*) from usuarios where usuario=@usuario and password=@password";

    // defino la conexión
    SqlConnection conn = new SqlConnection(ConfigurationManager.AppSettings["ConnectionString"]);

    // defino el command y le asigno los parámetros a la consulta.
    SqlCommand cmd = new SqlCommand(query, conn);
    cmd.Parameters.AddWithValue("@usuario", usuario);
    cmd.Parameters.AddWithValue("@password", password);

    // ejecuto la consulta.
    conn.Open();
    int count = (int)cmd.ExecuteScalar();
    conn.Close();

    // devuelvo true solo si obtuve 1 resultado
    return count == 1;
}

Redirigir a una página específica

Para redirigir a una página específica, distinta de la que podría venir en el parámetro RedirectUrl, primero enviamos la cookie y luego hacemos el redireccionamiento:

FormsAuthentication.SetAuthCookie(txtUsuario.Text, false);
Response.Redirect("OtraPagina.aspx");

Cerrar la sesión

FormsAuthentication.SignOut();

Locations

Por defecto, todos los directorios y subdirectorios, incluyendo archivos, donde se encuentre el web.config estarán protegidos al activar el mecanismo de autenticación. Si necesitamos que alguna ubicación específica tenga permisos diferentes, podemos (1) recurrir a un web.config por cada directorio o (2), mucho mejor, definir elementos en el web.config principal.

<location path="imagenes">
    <system.web>
        <authorization>
            <allow users="*" />
        </authorization>
    </system.web>
</location>

<location path="estilos.css">
    <system.web>
        <authorization>
            <allow users="*" />
        </authorization>
    </system.web>
</location>

 

 

Primeras impresiones de CakePHP

Uno de los primeros artículo de este blog hablaba de mis primeras impresiones sobre Ruby on Rails. Ahora voy a hablar sobre otro framework MVC que estuve investigando y utilizando en los últimos días. Se trata de CakePHP, como podrán intuirlo, para PHP.

Llegué a CakePHP porque me gustó mucho la filosofía detrás de Ruby on Rails, pero no tenía tiempo de ponerme a aprender un lenguaje nuevo, como es Ruby. Me pregunté entonces si no existiría algo similar en un lenguaje que conociera, como PHP. Así fue como encontré CakePHP. También encontré otros frameworks MVC para PHP, que no tuve tiempo de investigar, como por ejemplo, el francés symfony.

Creo que no me equivoco si digo que CakePHP es una copia de Ruby on Rails. Las funcionalidades son idénticas, así como el nombre de muchos de los métodos. Pero bueno, por lo menos, podemos programar con PHP al estilo Rails. De hecho, este mismo blog lo desarrollé con CakePHP en tan solo un par de horas.

Sin embargo, CakePHP tiene un problema grave: la falta de buena documentación y ejemplos. Creo que por más bien que funcione, si falta eso, es una gran contra. CakePHP tiene un manual muy básico y, a mi parecer, muy mal organizado. El manual no llega a cubrir con un nivel aceptable todas las características del framework y remite casi todo el tiempo a la documentación de la “API”. Pero la documentación de la API es todavía peor, por no decir casi inservible. Dificil y oscura para navegar, y sin ejemplos que ilustren el uso de los métodos, se hace imposible entender cómo hacer ciertas cosas.

Sin dudas, CakePHP es una buena alternativa a Rails, para programadores de PHP, pero una documentación tan pobre y un manual casi muerto hacen que realmente pueda llegar a ser más fácil aprender un muy buen lenguaje nuevo, como es Ruby y un framework mucho mejor documentado, y con mayor soporte de la comunidad, como esRuby on Rails.

 

Referencia de LLBLGen Pro

Esta es una referencia rápida de LLBLGen Pro a la que podrán acceder para consultar la forma de realizar tareas básicas con este framework.

La documentación la pueden bajar acá. Está disponible en varios formatos.

La clave para empezar a usar la documentación esta rápido es leer el capítulo “Tutorials and Examples -> How do I … ?”

Luego, para ahondar más en conceptos teóricos pueden leer ” Using the generated code”. Cuando habla de los dos modelos de código soportados, SelfServicing y Adapter , el que nos interesa a nosotros es el primero, ya que es el que vamos a usar.

El trabajo con LLBLGen Pro se divide en dos procesos:

  1. Generación de código: LLBLGen Pro es una aplicación de escritorio para generar código. Resumiento, este proceso es así: se crea un proyecto, se le dice con qué base de datos se va a trabajar, el programa lee la estructura de la base de datos y genera un proyecto de tipo “librería de clases” en .NET. Este paso ustedes NO lo van a necesitar hacer ni aprender, ya que la base de datos y el código lo voy a generar yo (obviamente, si les interesa aprenderlo o necesitan modificarlo, no hay problema; yo siempre subo el proyecto de LLBLGen Pro al SVN).
  2. Uso del código generado: Una vez generado el proyecto de acceso a datos y agregado a la Solución de VS en la que van a trabajar, ya pueden utilizar el código generado. El código generado contiene varias clases, pero las que más van a usar ustedes con las “EntityClasses” y las “CollectionClasses”.

El uso del código generado es muy sencillo. Les paso algunos ejemplos (también se los adjunto).

NOTA: Para diferenciar mejor cuáles son palabras que vienen del framework de LLBLGen Pro, las que creo yo son en castellano. Por ejemplo, con “PersonaEntity” se pueden dar cuenta que yo creé una tabla llamada Persona y al generar código, me creó la clase con el nombre de la tabla + el sufijo “Entity”.

Guardar un nuevo registro en la base de datos:

PersonaEntity persona = new PersonaEntity();

persona.Nombre = "Marcelo";
persona.Apellido = "Ruiz";

persona.Save();

Traer un registro de la base de datos

int idPersona = 1;
PersonaEntity persona = new PersonaEntity();
persona.FetchUsingPK(idPersona);

Traer un registro de la base de datos (otra forma)

PersonaEntity persona = new PersonaEntity(IdPersona);

// No estoy seguro qué pasa si el IdPersona no existe,
// pero eso verifico que no sea nuevo.
if (!persona.IsNew)
{
    Console.WriteLine("Nombre = "+ persona.Nombre);
}
else
{
    Console.WriteLine("Persona no encontrada");
}

Actualizo un registro

PersonaEntity persona = new PersonaEntity();
if (persona.FetchUsingPK(1))
{
    persona.SegundoNombre = "Hernan";
    persona.Save();
}
else
{
    Console.WriteLine ("Persona no encontrada");
}

Borrar un registro

PersonaEntity persona = new PersonaEntity(IdPersona);
persona.Delete();

Filtros

PersonaCollection personas = new PersonaCollection();

// quiero un filtro equivalente al SQL "WHERE Edad > 18 AND Edad < 40"
IPredicateExpression filter = new PredicateExpression();
filter.Add(PersonaFields.Edad > 18);
filter.AddWithAnd(PersonaFields.Edad < 40);

personas.GetMulti(filter);

// recorro la colección:
foreach (PersonaEntity persona in personas)
{
    Console.WriteLine (persona.Nombre +" "+ persona.Apellido);
    foreach (TelefonoEntity telefono in persona.Telefonos)
    {
        Console.WriteLine(telefono);
    }
}

Filtro y ordenamiento

PersonaCollection personas = new PersonaCollection();

IPredicateExpression filter = new PredicateExpression();
filter.Add (PersonaFields.Edad > 18);
filter.AddWithAnd(PersonaFields.Edad < 40);

ISortExpression sorter = new SortExpression();
sorter.Add(PersonaFields.Name | SortOperator.Ascending);
sorter.Add(PersonaFields.Apellido | SortOperator.Ascending);

personas.GetMulti(filter, 0, sorter);

// recorro la colección:
foreach (PersonaEntity persona in personas)
{
    Console.WriteLine(persona.Nombre +" "+ persona.Apellido);
    foreach (TelefonoEntity telefono in persona.Telefonos )
    {
        Console.WriteLine(telefono);
    }
}

Otro filtro más elaborado

PersonaCollection personas = new PersonaCollection();

// quiero un filtro equivalente al SQL "WHERE ((Edad < 18 OR Edad > 40) AND Nacionalidad = 'Argentino')"
IPredicateExpression filtroCompleto = new PredicateExpression();

IPredicateExpression filtroPorEdades = new PredicateExpression();
filtroPorEdades.Add(PersonaFields.Edad < 18);
filtroPorEdades.AddWithOr(PersonaFields.Edad > 40);

filtroCompleto.Add(filtroPorEdades);
filtroCompleto.AddWithAnd(PersonaFields.Nacionalidad == "Argentino");

personas.GetMulti(filtroCompleto);

// recorro la colección:
foreach (PersonaEntity persona in personas)
{
    Console.WriteLine(persona.Nombre +" "+ persona.Apellido);
    foreach (TelefonoEntity telefono in persona.Telefonos)
    {
        Console.WriteLine(telefono);
    }
}

Guardar datos asociados de un solo paso:

PersonaEntity persona = new PersonaEntity();
persona.Nombre = "Marcelo";
persona.Apellido = "Ruiz";

TelefonoEntity telefono1 = new TelefonoEntity();
telefono1.Numero = "4567-1234";
telefono1.Descripcion = "Casa";

TelefonoEntity telefono2 = new TelefonoEntity();
telefono2.Numero = "4000-7896";
telefono2.Descripcion = "Trabajo";

persona.Telefonos.Add(telefono1);
persona.Telefonos.Add(telefono2);

// Guardo con "true", para indicar que guarde tmb los
// datos relacionados (telefonos).
bool resultado = persona.Save(true);
if (resultado)
  Console.WriteLine("OK");
else
  Console.WriteLine("Error");

Limpiar la cache de CakePHP al actualizar Modelos

Uno de los errores más frecuentes que se suelen ver al trabajar con CakePHP es que, por más que actualicemos nuestra base de datos (por ejemplo, agregando nuevos campos), los modelos no se ven actualizados.

Esto se debe al hecho de que CakePHP cachea los modelos en la carpeta app/tmp/cache/models.

Cuando estamos en modo development, este no es el caso. El problema aparece en producción. En estos casos lo que tenemos que recordar siempre es borrar el contenido de esa carpeta para forzar el refresco de los modelos.

Es importante recordar también que toda la carpeta tmp y sus subcarpetas necesitan permisos de escritura.

Impresiones de Ruby on Rails

En este artículo voy a comentar las primeras impresiones que me dio Ruby on Rails, visto desde la óptica de alguien que viene de programar aplicaciones web en ASP, PHP y ASP.NET.

Ruby on Rails es un framework para el desarrollo de aplicaciones web con basadas en bases de datos. Se trata de un framework “RAD” (Rapid Application Development) que nos permitirá crear aplicaciones web totalmente funcionales en un tiempo mucho menor que utilizando otras tecnologías, por ejemplo, ASP.NET.

Ruby on Rails (RoR) me sorprendió de entrada con características muy ventajosas que me hicieron preguntar por qué en ASP.NET o en PHP tengo que escribir hasta 10 veces más código para lograr lo mismo.

La respuesta no es “porque RoR es mejor”. El tema es que RoR está muy enfocado en una dirección, y es “el desarrollo de aplicaciones web basadas en bases de datos”. RoR también se maneja mucho con ciertas convenciones que de alguna forma nos limitan en esa dirección, pero nos permiten lograr resultados más rápido.

Un ejemplo de estas limitaciones es que RoR espera que cada una de las tablas de nuestra base de datos tenga un campo autonumérico definido como la primary key llamadoid. Si cumplimos con esa convención, RoR promete hacernos las cosas más fácil.

Por supuesto, RoR permite ciertos deslices de esas convenciones, pero pagando el costo de mayor cantidad de código.

Sinceramente no me puse a investigar qué pasa si queremos trabajar con modelos de datos un poco más complejos. Por ejemplo, si queremos modelar herencia en la base de datos, algo que suelo utilizar bastante. Otra cosa que noté es que las tablas de asociación de muchos-a-muchos no se modelan en RoR, es decir, no son representadas como objetos en el modelo; con esto, no sé qué pasaría si yo quiero agregar algún campo extra a estas tablas para calificar una asociación. Sinceramente son cosas que tengo que investigar.

Patrón MVC

Ruby on Rails implementa el patrón MVC. Por alguna razón este patrón está de moda. Aunque, pensandolo bien, es un patrón que funciona MUY bien para aplicaciones web basadas en datos.

No voy a explicar el patrón MVC aquí, solo voy a explicarles cómo lo implementa RoR, ya que me costó un poquito entenderlo.

El Modelo (la M de MVC), en Ruby on Rails contiene clases que representan a las tablas de nuestra base de datos. Una característica destacable es que nosotros creamos estas clases pero no es necesario definir los atributos para acceder a los campos de las tablas que representan. Rails infiere esta información automáticamente a partir del nombre de la clase (para esto sirven las convenciones).

En las clases del Modelo podemos, además, agregar otra lógica propia de cada clase. Por ejemplo, si tenemos una clase Product, en principio, se vería así:

class Product < ActiveRecord::Base

end

Supongamos que queremos agregar código para calcular el precio con impuestos. En ese caso, la clase Product es el lugar indicado para hacerlo.

class Product < ActiveRecord::Base

  def price_plus_taxes
    price * 1.21 
  end

end

También podemos definir métodos estáticos:

class Product < ActiveRecord::Base

  def price_plus_taxes
    price * 1.21 
  end

  def self.find_available_products
    Product.find_by_available(1)
  end

end

Una de las confusiones que tuve fue con el tema de los Controllers y las Views (C y V en MVC). Creí erróneamente que también debe existir un Controller por cada tabla y una carpeta dentro de View por cada tabla. Si bien es bastante común esto no tiene por qué ser así.

Los Controllers son clases que agrupan un conjunto lógico de páginas. Por ejemplo, páginas para listar y visualizar artículos. En ese caso tendríamos un Controller llamadoArticlesController con métodos list (para listar) y show (para visualizar). A su vez, tendríamos una carpeta articles con plantillas que llevarán los mismos nombres que los métodos: list y show.

Sin embargo, la forma en que manejamos las entidades de nuestro modelo en nuestros controllers es totalmente libre. Solo tenemos que tener en cuenta la relación que hay entre Controllers y Views, como mencioné arriba.

Características principales de Ruby on Rails

Hasta aquí hice una breve presentación de RoR y expliqué cómo está implementado el patrón MVC. Voy a hablar ahora de las características que más destaco de MVC, sobre todo porque me encantaría tenerlas en otros lenguajes.

En general, encuentro que las características que más me atraen de Rails son aquellas que facilitan el trabajo del programador. Creo que Rails sigue la misma filosofía de Ruby, de programar para seres humanos y no para máquinas. Las características de Rails están pensadas para el mundo real; se pensó en cuáles son las necesidades cotidianas más comunes de cualquier programador de aplicaciones web y se implementaron soluciones realmente inteligentes.

Mapeo automático

Al crear una clase de tipo Model, Rails automáticamente infiere las propiedades de la misma, mapeandolas a los campos de la tabla de la base de datos. Rails sabe a qué tabla corresponde cada clase del Model gracias a las convenciones de nombres.

Lo mejor de esto es que, al agregar un nuevo campo a una tabla, no hace falta tocar nada en el Model; la nueva propiedad estará disponible automáticamente.

Hasta el momento no se me ocurre si esto es posible de hacer con .NET. Tal vez se pueda utilizando un poco de Reflection, pero lo dudo; y si se puede, debe ser difícil. Claro, una diferencia fundamental entre Ruby y el CLR de .NET es que Ruby tiene un soporte nativo para meta programación y los lenguajes de .NET son compilados, lo que hace que esto sea un poco dificil.

Validación

Otro ejemplo de una solución inteligente. Mientras que en frameworks como ASP.NET o incluso en CakePHP (otro framework MVC, pero para PHP) las validaciones se realizan a nivel de interfaz de usuario, en Rails se definen las reglas de validación en el Modelo.

Validar que el nombre y precio de un producto no estén vacíos, y que el precio sea un número válido, es tan fácil como agregar esta línea al modelo:

class Product < ActiveRecord::Base
  validates_presence_of :title, :price
  validates_numericality_of :price
end

Automáticamente Rails va a impedir que guardemos datos que no cumplan con dichas validaciones, y nos va a mostrar los errores correspondientes.

ASP.NET ofrece los controles de validación. Pero configurarlos y usarlos no es tan simple. Además, debemos implementarlos cada vez que trabajemos con la entidad.

Funciones find

Leer registros de la base de datos para llenar objetos o colecciones es por lejos una de las tareas más comunes. Para facilitarnos esta tarea, Rails tiene muchas variantes de la función find. Por ejemplo, todas las clases del Modelo tienen una función find para traer todos los registros de su tipo. Por ejemplo:

Product->find(:all)

Devolverá un array con todos los objetos Product de la base de datos.

Product->find(:first)

Devolverá el primer objecto Product.

Esta función acepta también más argumentos para definir un poco mejor los resultados que queremos y cómo los queremos ordenados. Por ejemplo:

Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)

Traerá todas las Person que cumplan con la condición del segundo argumento.

Pero donde esto se pone realmente interesante es cuando llamamos a funciones como:

Product.findByPrice(9.99)

Para traer todos los productos cuyo precio sea 9.99.

Wine.findByType('Malbec')

Para traer todos los vinos cuyo tipo sea Malbec.

Todas las funciones findBy<campo> son automáticamente generadas por Rails en tiempo de ejecución, gracias a las características de meta-programación del lenguaje Ruby.

Esto nos puede ahorrar realmente muchísimo trabajo. ¡Piensen en la cantidad de veces que necesitaron recurrir a funciones de este tipo! Si bien en otros lenguajes podemos construir funciones del tipo findBy<campo>, para evitar la repetición del código, de todas formas tenemos que escribirlas al menos una vez. En Rails, ya están todas “escritas”.

Formularios

Otra de las características destacables de Rails es el manejo de formularios asociados a entidades del Modelo. Mediante el método form_for, creamos un formulario HTML que estará asociado a una entidad determinada:

<% form_for :person, @person, :url => { :action => "update" } do |f| %>
First name: <%= f.text_field :first_name %>
Last name : <%= f.text_field :last_name %>
Biography : <%= f.text_area :biography %>
Admin?    : <%= f.check_box :admin %>
<% end %>

En el ejemplo, creamos un formulario para la entidad Person (esto lo definimos mediante el símbolo :person. La data se la pasamos en la variable @person. Y en el tercer argumento definimos qué acción ejecutar (en este caso, update).

Vean que luego utilizamos métodos especiales para crear los campos del formulario. Al utilizar los mismos nombres de los campos de nuestras tablas para los campos del formulario, Rails automáticamente sabrá cómo completarlos y sabrá luego cómo y dónde guardarlos cuando enviemos este formulario.

Sin dudas, esto simplifica muchísimo las cosas. Fíjense cómo controlamos este formulario desde la clase PeopleController:

@person = Person.findById(1);

¡Listo! No hizo falta ni siquera ir campo por campo asignando la propiedad correspondiente… form_for lo hace por nosotros.

Guardar

Lo bueno de guardar en Rails es que, cuando recibimos un form, recibimos también un array params, con todos los valores del formulario enviado. Entonces, con pasar este array params al método update o create, automáticamente Rails sabrá cómo guardar la entidad.

El método flash

El método flash puede parecer algo tonto, pero es muy útil. Su funcionalidad principal es pasar info entre peticiones diferentes. Uno de los principales usos es para mostrar notificaciones o mensajes de error.

Conclusión

Como ven, Ruby on Rails tiene muchísimas cosas interesantes. Cosas que tal vez no vamos a encontrar en otros frameworks y que se las vamos a envidiar.