miércoles, 31 de diciembre de 2014

Generador automático de contraseñas en PHP

Os presento un sencillo código en PHP que genera automáticamente contraseñas:

<?php

function generar_password($longitud) {
   $codigos = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
   $max_cod = strlen($codigos) - 1;
   $resultado = "";
   $item = 0;

   for ($i = 0; $i < $longitud; $i++) {
      if ($i == 0) $item = rand(36, $max_cod);
      else if ($i < $longitud - 1) $item = rand(0, $max_cod);
      else $item = rand(26, 35);

      $resultado = $resultado . $codigos[$item];
   }
   return $resultado;
}

echo "Password: ".generar_password(8);

?>

A la función generar_password se le pasa como argumento el número de caracteres (longitud) de la contraseña. El primer carácter será siempre una letra en mayúsculas, y la última será siempre un número, para asegurarnos de que, al menos, contiene uno de estos caracteres.

martes, 21 de octubre de 2014

Instalación del driver de MongoDB en Python

Para desarrollar aplicaciones Python basadas en la base de datos MongoDB

, es necesario instalar PyMongo, el driver oficial para Python

Instalación en Linux

La forma más sencilla de instalar PyMongo es mediante el siguiente comando:

$ easy_install pymongo

Si no lo tienes, puede instalarlo mediante el siguiente comando:

sudo apt-get install python-setuptools

Otra forma de instalarlo es mediante el gestor de paquetes pip. Si no lo tienes instalado, puedes adquirirlo mediante el siguiente comando:

$ sudo apt-get install python-pip

A continuación instalamos el driver mediante el siguiente comando:

$ pip install pymongo

Instalación en Windows

Para Windows disponemos de instaladores estándar. Basta con descargar el archivo .exe del driver más reciente de:

https://pypi.python.org/pypi/pymongo/#downloads

Y lanzar el instalador.

Probando PyMongo

Nada mejor que un ejemplo práctico para comprobar el funcionamiento de PyMongo. Para ello, crearemos un fichero llamado demoMongoDB.py, con el siguiente código:

from pymongo import MongoClient

conexion = MongoClient('localhost', 27017)
basedatos = conexion.pruebas
coleccion = basedatos.pruebas

print "Num. documentos:", coleccion.count()

for doc in coleccion.find():
   print doc

En la primera línea importamos el driver pymongo y la clase MongoClient, la cual gestiona una conexión cliente a la base de datos. A continuación creamos una conexión a una base de datos MongoDB localizada en nuestra propia máquina (localhost) y en el puerto estándar (27017). Después, desde la conexión, abrimos la base de datos pruebas. Después, desde la base de datos abierta, abrimos la colección pruebas. A continuación, visualizamos el número de documentos que tiene la colección. Por último, visualizamos todos los documentos de la colección.

Más info:

Instalación del driver de MongoDB para PHP

Instalación en Linux

Antes de proceder a la instalación y configuración del driver de MongoDB en Linux, es necesario tener instalado antes pecl. Si no lo tienes instalado, utiliza el siguiente comando:

sudo apt-get install php5-dev php5-cli php-pear

Para utilizar MongoDB desde PHP, primero hay que descargar el driver correspondiente en la siguiente URL:

http://pecl.php.net/package/mongo

Una vez descargado, procedemos a descomprimir el archivo y a compilar y a instalar el driver mediante los siguientes comandos:

$ tar zxvf mongodb-mongodb-php-driver-.tar.gz
$ cd
$ phpize
$ ./configure
$ make all
$ sudo make install

Debería mostrar un mensaje como el siguiente:

Installing shared extensions: /usr/local/lib/php/extensions/no-debug-zts-20121212/

La instalación debería configurar el archivo php.ini, apuntando al archivo mongo.so a través del parámetro extensions. Para verificarlo, primero localizamos el archivo mongo.so mediante el siguiente comando:

$ sudo find / -name mongo.so

Ese archivo debería estar localizado en el directorio

/usr/local/lib/php/extensions/no-debug-zts-<fecha>

Para verificar si se ha configurado correctamente, ejecutaremos el siguiente comando:

$ php -i | grep extension_dir

El resultado debería ser similar al siguiente:

extension_dir => /usr/local/lib/php/extensions/no-debug-zts-20121212 => /usr/local/lib/php/extensions/no-debug-zts-20121212

Después, modificar el archivo php.ini y configurar el siguiente parámetro:

extension=mongo.so

Por último, reiniciar el servidor de Apache.

Instalación en Windows

La instalación en Windows es más sencilla. Lo primero que hay que hacer es descargar la última versión del driver en la siguiente URL:

https://s3.amazonaws.com/drivers.mongodb.org/php/index.html

A continuación, descomprimir el archivo descargado y copiar el archivo php_mongo.dll en el directorio de extensiones PHP, el cual, normalmente, se llama ext.

Después, modificar el archivo php.ini y añadir la siguiente línea:

extension=php_mongo.dll

Por último, reiniciar el servidor de Apache.

domingo, 19 de octubre de 2014

Fechas en MongoDB

MongoDB es una base de datos muy potente a la hora de utilizar valores de fecha. En este artículo analizaremos cómo gestionar correctamente las fechas y cómo encontrar los datos en base a fechas.

Agregar fechas

La forma más sencilla de insertar una fecha es la siguiente:

> db.coleccion.insert({"fecha" : new Date()})

El comando anterior agrega un nuevo documento a la colección, con un campo fecha que contendrá la fecha y hora actuales. Si listamos el contenido del documento, obtendremos un resultado como el siguiente:

> db.coleccion.find()

{ "_id" : ObjectId("5443e87e5894b090804c822e"), "fecha" : ISODate("2014-10-19T16:36:14.197Z") }

El tipo de dato ISODate contendrá la fecha en formato internacional (año-mes-día), seguido de la hora con precisión de milésimas de segundo.

Para agregar una fecha específica sin especificar la hora, se puede utilizar la función Date() con el siguiente formato:

> db.coleccion.insert({fecha: new Date("2014, 10, 21")})

Si queremos agregar una fecha y hora concretas, podemos utilizar el tipo ISODate() como función para especificar el valor:

> db.coleccion.insert({fecha: ISODate("2014-10-19T18:56:20.197Z")})

Búsqueda de fechas

Para hacer una búsqueda exacta (por fecha y hora), podemos utilizar el siguiente formato:

> db.coleccion.find({"fecha" : ISODate("2014-10-20T22:00:00Z")})

{ "_id" : ObjectId("5443f41b5894b090804c8234"), "fecha" : ISODate("2014-10-20T22:00:00Z") }

Si deseamos buscar todos los documentos a partir de una fecha específica:

> db.coleccion.find({"fecha" : {"$gt" : ISODate("2014-10-18T00:00:00")}})

También podemos utilizar la siguiente sintaxis:

> db.coleccion.find({"fecha" : {"$gt" : new Date("2014-10-18")}})

En ambos casos indicamos a la función find() (encontrar) que compare el campo "fecha" con el valor a buscar, que es un nuevo documento que contiene el operador "$gt" (greater than ó mayor que) y la fecha a partir de la cual debe hacer coincidir el resultado.

Los operadores condicionales utilizados por MongoDB para evaluar las condiciones de los valores a encontrar son los siguientes:

  • $gt: greater than (mayor que)
  • $gte: greater than or equal (mayor o igual que)
  • $lt: less than (menor que)
  • $lte: less than or equal (menor o igual que)
  • $ne: not equal (distinto de)
  • $in: in (dentro de(un array)
  • $nin: not in (no dentro de (un array))

Por ejemplo, si deseamos encontrar aquellos documentos que se encuentren entre dos fechas dadas:

> start = ISODate("2014-10-18T00:00:00Z")
ISODate("2014-10-18T00:00:00Z")
> end = ISODate("2014-10-20T00:00:00Z")
ISODate("2014-10-20T00:00:00Z")
> db.coleccion.find({"fecha" : {"$gte" : start, "$lte" : end}})

Al encadenar una lista de criterios a evaluar, por defecto asumirá que se aplica una operación lógica "$and". Lo anterior sería lo mismo que este comando:

> db.coleccion.find({"$and" : [{"fecha" : {"$gte" : start}}, {"fecha" : {"$lte" : end}}]})

Si queremos omitir el rango anterior utilizaríamos el operador lógico "$or":

> db.coleccion.find({"$or" : [{"fecha" : {"$lt" : start}}, {"fecha" : {"$gt" : end}}]})

Los operadores lógicos utilizados por MongoDB son los siguientes:

  • $and: Todas las condiciones del array se deben cumplir
  • $or: Se debe cumplir, al menos, una condición del array
  • $not: Invierte la condición.

Puede ocurrir que deseemos obtener documentos que se encuentren entre una lista de posibles coincidencias. Para ello, utilizaremos el operador condicional "$in", seguido de un array con la lista de valores a evaluar:

> db.coleccion.find({"fecha" : {"$in" : [ISODate("2014-10-20T22:00:00Z"), ISODate("2014-10-19T18:56:20.197Z"), ISODate("2014-10-15T18:56:20.197Z")]}})

En el caso de que quisiéramos cualquier documento excepto los que se encuentran en la lista, utilizaríamos el operador condicional "$nin".

Optimización de las búsquedas

Para que nuestras búsquedas sean mucho más rápidas y eficientes, necesitaríamos indexar la colección por el campo fecha:

> db.coleccion.ensureIndex({"fecha" : 1})
{
   "createdCollectionAutomatically" : false,
   "numIndexesBefore" : 1,
   "numIndexesAfter" : 2,
   "ok" : 1
}

El comando ensureIndex permite crear un índice en base a la lista de campos especificados. Con un valor 1, indicamos que el campo esté ordenado de alfabéticamente o de menor a mayor. Si el valor fuese -1, el orden sería a la inversa.

lunes, 13 de octubre de 2014

Cómo sincronizar el Calendario de Outlook en Android

Me ha costado bastante descubrir cómo sincronizar el calendario de Outlook en Android. Por más que buscas en la aplicación Outlook, no encontrarás en calendario por ninguna parte, si no, solamente, una opción de ajuste para realizar la sincronización. Es decir, sincroniza los datos pero no ofrece la herramienta de calendario.

Por deducción, entendí que la aplicación Outlook solamente hacía de puente para los datos entre el servidor de Outlook y otra aplicación que lo gestionase, como es el Calendario de Android. Sin embargo, cuando abres el Calendario de Android no ves las tareas del calendario de Outlook. Si vas a Ajustes, verás que en la parte izquierda está configurada tu cuenta de Gmail, pero no la de Outlook. Si vas a la opción Añadir Cuenta y eliges Outlook.com, te pedirá los datos de configuración de tu cuenta en Outlook (Hotmail/Exchange), pero al final te dirá que la cuenta ya existe y está duplicada, por lo que no te dejará añadir la cuenta.

Finalmente realicé las siguientes acciones para configurarlo de la forma correcta:

  1. Desinstalar Outlook en Android.
  2. Volver a instalar Outlook en Android, PERO SIN CONFIGURAR LA CUENTA.
  3. Acceder a Configuración de Android
  4. Seleccionar Cuentas y Sincronización
  5. Seleccionar AGREGAR CUENTA
  6. Seleccionar Outlook.com
  7. Configurar los datos de tu cuenta de Outlook
  8. Al final aparecerá la cuenta en la lista de Cuentas y Sincronización.
  9. Seleccionar esta cuenta. Aparecerán su opciones de configuración
  10. Comprobar que están activadas las opciones Sincronizar Calendario y Sincronizar Contactos
  11. Acceder a la herramienta Calendario de Android
  12. Ir a Ajustes
  13. Comprobar que en la parte izquierda se ha añadido la cuenta de Outlook

martes, 7 de octubre de 2014

Automatizar Samba mediante scripts

Una de las facetas más útiles e importantes es poder realizar copias de nuestros archivos en repositorios remotos Samba. Pero lo más interesante es hacerlo de forma automática, sin una intervención humana. Esto toma gran relevancia a la hora de gestionar backups o copias de seguridad.

Mediante la herramienta smbclient podemos conectar remotamente con Samba, ofreciéndonos una consola de comandos especial para lanzar los comandos específicos de Samba. Pero esto, obviamente, requiere una sesión Samba y una interacción con el usuario para cada comando. Y lo que necesitamos es realizar de forma automática todas estas tareas.

Lo primero que tenemos que hacer es lanzar smbclient para que se conecte de forma automática a través de un fichero de configuración que contiene los datos de conexión. Normalmente, este fichero se llama .smbclient, a fin de tener una coherencia de nombre y porque dicho fichero aparece oculto. Este fichero ha de contener la siguiente configuración:

username=usuario_samba
password=contraseña
domain=grupo_trabajo

Para que smbclient se conecte al repositorio de Samba de forma automática, debe indicarse el fichero de configuración de la siguiente manera:

sudo smbclient //SERVIDOR/repositorio -A .smbclient

donde "SERVIDOR" será la IP o el nombre del servidor en donde está Samba, y "repositorio" será el nombre del repositorio al que se quiere acceder.

Ahora viene lo más interesante: ejecutar la secuencia de comandos de smbclient sin intervención humana. Esto se consigue si añadimos el parámetro -c seguido de una cadena de texto con la secuencia de comandos smbclient, separados con punto y coma (;).

sudo smbclient //SERVIDOR/repositorio -A .smbclient -c "comando1; comando2; ..."

He aquí un ejemplo:

sudo smbclient //192.168.98.200/mirepositorio -A .smbclient -c "cd Backups/LDAP; lcd /home/miusuario/backups/ldap; recurse; prompt; mput *; exit;"

Los comandos más utilizados de smbclient son los siguientes:

  • cd: Cambia de directorio en el repositorio Samba
  • lcd: Cambia de directorio local (en nuestra máquina)
  • ls [máscara]: Muestra el contenido del directorio actual (en Samba)
  • mget [máscara]: Descarga desde directorio actual en Samba, los archivos que coincidan con la máscara, y los aloja en el directorio actual en local.
  • mput [máscara]: Sube desde el directorio actual en local, los archivos que coincidan con la máscara, y los aloja en el directorio actual en Samba.
  • recurse: Realiza la transferencia de archivos mget/mput de forma recursiva.
  • prompt: Omite la confirmación de cada operación en cada fichero.
  • exit: Abandona smbclient.
  • help [comando]: Muestra la ayuda.

Exportar e importar datos en LDAP

Os paso un par de comandos muy útiles para realizar backups de un LDAP.

Para exportar todos los datos de LDAP en un archivo plano:

slapcat -l /ruta/archivobackup.ldif

Para importar los datos del fichero a LDAP:

slapadd -c -l backup.ldif

Usar comando sudo sin contraseña

El comando sudo de Unix es muy útil, ya que permite ejecutar comandos de nivel de administración como si fuéramos un superusuario. Para la ejecución del comando nos solicitará la contraseña de superusuario o de algún usuario definido en el fichero /etc/sudoers, el cual define qué usuarios pueden lanzar comandos sudo.

Cuando definimos scripts, el comando sudo puede suponer un inconveniente, especialmente cuando pretendemos automatizar tareas, tales como backups, ya que precisa de una intervención del usuario para introducir la contraseña.

Para llevar a cabo esta clase de tareas sin que se requiera la contraseña de superusuario, debemos realizar modificaciones en el fichero /etc/sudoers. Pero esto no se puede hacer directamente, ya que tiene permisos de sólo lectura. Para ello, debemos ejecutar el siguiente comando:

sudo visudo

La herramienta visudo está concebida especialmente para editar con seguridad el fichero /etc/sudoers, permitiendo, además, verificar la sintaxis y los errores.

Dentro del fichero /etc/sudoers podemos definir un grupo de programas autorizados y prohibidos a ejecutar con sudo, mediante la directiva Cmnd_Alias:

Cmnd_Alias AUTORIZADO = /ruta/programa1, /ruta/programa2
Cmnd Alias PROHIBIDO = !/ruta/programa1, !/ruta/programa2
usuario ALL = (ALL) AUTORIZADO
usuario ALL = PROHIBIDO

En realidad, podemos definir en un mismo grupo los programas permitidos y prohibidos. La diferencia entre ambos estaría en que los programas prohibidos deben llevar como prefijo el signo de admiración (!).

Si deseamos asignar todos los permisos sudo a un usuario específico, podríamos utilizar, directamente, una línea como ésta:

usuario ALL = (ALL) ALL

Si deseamos que, además, no requiera de contraseña, se utilizaría la siguiente línea:

usuario ALL = (ALL) NOPASSWD: ALL

Obviamente, esto no sería muy recomendado por seguridad. Podría definirse esta política de seguridad únicamente a ciertos programas:

Cmnd_Alias AUTORIZADO = /ruta/programa1, /ruta/programa2
usuario ALL = (ALL) NOPASSWD: AUTORIZADO

El archivo /etc/sudoers tiene una gran capacidad de configuración, además de lo visto anteriormente. Podemos, por ejemplo, definir alias para usuarios, así como también grupos de usuarios. Para más información, se puede consultar la ayuda que ofrece Unix:

man 8 sudo
man 5 sudoers
man 8 visudo

lunes, 6 de octubre de 2014

Programar eventos en Linux con CRON

Linux (y otros sistemas operativos basados en UNIX) posee una poderosa herramienta que permite programar eventos. Gracias a ello, podremos configurar que, cada cierto tiempo, se lance algún comando o programa. Esto es realmente útil, para tareas periódicas, tales como backups o copias de seguridad, transferencia de datos de un sistema a otro, copia de ficheros entre programas, etc.

CRON es un administrador de procesos en segundo plano (o demonios), y se encarga de revisar, a cada minuto, la lista de tareas o procesos programados en el fichero crontab, el cual está ubicado en /etc/crontab. Dentro de este fichero podemos configurar el intervalo en el que se ejecutarán las tareas, automatizando éstas. Cuando CRON revisa crontab, comprueba si hay alguna tarea o proceso que ejecutar en ese momento.

Para editar el crontab, ejecutar el siguiente comando:

sudo crontab -e

Con esto, se abrirá el editor para modificar el crontab correspondiente al usuario actual.

Las líneas de este fichero tienen el siguiente formato:

m h dom mon dow [usuario] comando

donde:

  • m: (minute) Minuto (0 a 59)
  • h: (hour) Hora (0 a 23)
  • dom: (day of month) Día del mes (1 a 31)
  • mon: (month) Mes (1 a 12)
  • dow: (day of week) Día de la semana (0 (domingo) a 6 (sábado))
  • usuario: Nombre del usuario Unix que lanza la tarea
  • comando: Comando Unix que ejecuta la tarea

Los parámetros m, h, dom, mon y dow pueden tener valores especiales:

  • * : (asterisco) Cualquier valor
  • valor1[,valor2...] : (lista separada por comas) Cualquiera de los valores especificados en la lista

Ejemplos:

  • 30 * * * * root sudo apt-get update & sudo apt-get dist-upgrade:Actualiza el sistema (Debian, Ubuntu, Mint...) cada minuto 30, a todas horas, todos los días y todos los meses
  • 0 23 * * 1,3,5 echo "Se ha ejecutado":A las 23:00 horas los lunes(1), miércoles(3) y viernes(5)
  • 0 23 * * mon,wed,fri echo "Se ha ejecutado":A las 23:00 horas los lunes(mon), miércoles(wed) y viernes(fri)
  • 30 7 1 * * root /etc/backup/backup.sh:Lanza el script backup.sh a las 7:30 de día 1 de cada mes, cualquier día de la semana

Existen algunas plantillas que facilitan el uso de los parámetros de frecuencia:

  • @hourly root sudo apt-get update & sudo apt-get dist-upgrade:Actualiza el sistema (Debian, Ubuntu, Mint...) en el primer minuto de cada hora (0 * * * * ...)
  • @daily root sudo apt-get update & sudo apt-get dist-upgrade:Actualiza el sistema (Debian, Ubuntu, Mint...) una vez al día, a las 00:00 horas (0 0 * * * ...)
  • @midnight root sudo apt-get update & sudo apt-get dist-upgrade:Actualiza el sistema (Debian, Ubuntu, Mint...) una vez al día, a las 00:00 horas (0 0 * * * ...)
  • @weekly root sudo apt-get update & sudo apt-get dist-upgrade:Actualiza el sistema (Debian, Ubuntu, Mint...) una vez a la semana (domingos, a las 00:00 horas) (0 0 * * 0 ...)
  • @monthly root sudo apt-get update & sudo apt-get dist-upgrade:Actualiza el sistema (Debian, Ubuntu, Mint...) una vez al mes (día uno, a las 00:00 horas) (0 0 1 * * ...)
  • @yearly root sudo apt-get update & sudo apt-get dist-upgrade:Actualiza el sistema (Debian, Ubuntu, Mint...) una vez a la año (día 1 de Enero, a las 00:00 horas) (0 0 1 1 * ...)
  • @annually root sudo apt-get update & sudo apt-get dist-upgrade:Actualiza el sistema (Debian, Ubuntu, Mint...) una vez a la año (día 1 de Enero, a las 00:00 horas) (0 0 1 1 * ...)

Otros parámetros de crontab:

  • crontab -l: Muestra las tareas CRON del usuario actual
  • crontab -r: Elimina el crontab

Para ver el log de CRON:

sudo nano /var/log/syslog

Puede ocurrir que en el log aparezca un error como éste:

CRON[8380]: (CRON) info (No MTA installed, discarding output)>/p>

Esto se debe a que CRON necesita tener configurado un servidor de correo para las notificaciones. Por ello, será necesario instalar postfix

sudo apt-get install postfix

Otra forma de evitar estos mensajes es cambiar la salida de las notificaciones, añadiendo al final de cada tarea en crontab lo siguiente:

[tarea] >/dev/null 2>&1

miércoles, 27 de agosto de 2014

Crea tu propio benchmarking de páginas web

El siguiente código en Python te permitirá realizar tus propios benchmarkings de páginas Web, simulando un escenario concurrente de muchos usuarios al mismo tiempo accediendo al mismo sitio Web. El resultado se almacena en un archivo de log que después podrá llevarse a una hoja de cálculo para calcular estadísticas de tiempos, o para identificar errores.

import time
import datetime
import urllib2
import logging
import threading
 
# Coleccion de urls a solicitar
urls = [
   'http://192.168.0.3/oiddigity_actual/index.html',
   'http://192.168.0.3/oiddigity_actual/registrado.asp',
   'http://192.168.0.3/oiddigity_actual/inicio.html',
   'http://192.168.0.3/oiddigity_actual/botones2.html',
   'http://192.168.0.3/oiddigity_actual/horafecha.asp'

 
# Nombre del fichero de log para guardar resultados
fich_log = 'testeo.log'
 
# Numero de hilos
num_hilos = 200
 
# Numero de procesos por hilo
num_procesos = 100
 
# Conteo de errores
num_errores = 0
 
# Metodo: proceso_urls()
# Procesa las urls, retornando el
# tiempo que consume
def proceso_urls():
   inicio = datetime.datetime.now()
   resultado = ""
 
   # Solicita y lee todas las urls
   for i in urls:
      try:
         peticion = urllib2.Request(i)
         respuesta = urllib2.urlopen(peticion)
         contenido = respuesta.read()
         resultado = "|OK"
         break
      except Exception as e:
         resultado = "|NOK[" + i + "]" + format(str(e))
 
   fin = datetime.datetime.now()
   tiempo = inicio.strftime("%T.%f")+'|'+fin.strftime("%T.%f")+"|"
   tiempo += get_tiempo(fin-inicio)
   tiempo += resultado
 
   return tiempo
 
# Metodo: get_tiempo()
# Retorna, a partir de un time, el tiempo en formato %H|%M|%S|%f
def get_tiempo(tiempo):
   resultado = ""
   segundos = tiempo.seconds
 
   if (segundos>=3600):
      horas = int(segundos/3600)
      resultado += str(horas) + "|"
   else:
      resultado += "0|"
 
   if (segundos>=60):
      minutos = int(segundos/60)
      resultado += str(minutos) + "|"
   else:
      resultado += "0|"
 
   resultado += str(segundos) + "|" + str(tiempo.microseconds)
 
   return resultado
 
# Metodo: proceso_hilo()
# Metodo principal del hilo
def proceso_hilo(count):
   global num_errores # Referencia a variable global compartida
 
   print 'Iniciando hilo ', count
 
   for i in range(num_procesos):
      resultado = proceso_urls()
 
      if (resultado.find("NOK") > 0):
         num_errores += 1
         print "#", num_errores, " > ", str(count), "[", str(i), "] > ", resultado
 
      logging.info(''+str(count)+"|"+str(i)+"|"+resultado)
 
   print 'Hilo', count, 'finalizado'
 
 
# Configuracion del fichero de log
logging.basicConfig(filename=fich_log, filemode='w', level=logging.DEBUG)
# Cabecera del log
logging.info('Hilo|Proceso|Inicio|Fin|Horas|Minutos|Segundos|Milisegundos|Resultado')
 
print '----------------------------------'
print 'Testeo. 2014 by Rafael Hernamperez'
print '----------------------------------'
print 'URLs a testear:'
 
for i in urls:
   print ' - ', i
 
print '> Ejecutando ', num_hilos, ' hilos'
print '> Total procesos: ', num_hilos * num_procesos
 
# Genera los hilos y los ejecuta
threads = list()
 
for i in range(num_hilos):
   t = threading.Thread(target=proceso_hilo, args=(i,))
   threads.append(t)
   t.start()

La variable urls permite definir las urls que van a solicitarse. Una página web puede tener varios iframes incluyendo otras partes de código web (otras páginas o servicios), las cuales han de invocarse para tener tiempos reales.

La variable fich_log define el nombre del fichero de log, en el cual se volcarán todos los resultados.

La variable num_hilos define el número de conexiones concurrentes. Esto simulará el escenario de n usuarios accediendo al mismo tiempo a las urls definidas.

La variable num_procesos define el número de veces que cada hilo llamará a las urls definidas.

La variable num_errores recoge el número de errores que se pueda producir durante el testeo.

El método proceso_urls() solicita las urls definidas y leyendo las respuestas por parte del servidor web. Tras el proceso guarda en el log el resultado con los tiempos y los posibles errores.

El método get_tiempo() retorna un literal con la descomposición del tiempo que se pasa por parámetro. Este literal se utiliza para el log, y es invocado por proceso_urls() para obtener el desglose del tiempo que ha tardado el proceso en ejecutarse.

El método proceso_hilo() es utilizado por cada uno de los hilos para lanzar todos los procesos definidos en la variable num_procesos. Es decir, repite num_procesos veces el acceso a las urls definidas.

Por último, para hacer funcionar esta aplicación, crea un fichero llamado (por ejemplo) testeo.py, copia y pega el código, guárdalo y ejecútalo desde la consola o terminal mediante el comando:

$ python testeo.py

Configurar la concurrencia en Apache y MySQL

Llega un momento en el que una aplicación Web tiene tanto éxito que el número de conexiones concurrentes satura las capacidades del servidor. Empiezan a aparecer errores de timeout o errores 500 Internal Server Error.

Para evitar ésto, se puede configurar Apache y MySQL para que admitan más concurrencia, es decir, más conexiones funcionando al mismo tiempo.

Concurrencia en Apache

Para configurar la concurrencia en Apache, hemos de configurar el archivo httpd.conf (el archivo será apache2.conf si está instalado en un Linux basado en Debian e instalado mediante sudo apt-get install apache2) o el archivo 000-default.conf.

Lo primero que debemos hacer es extraer la configuración actual de Apache. Para ello ejecutaremos el siguiente comando desde la terminal:

$ apache2 -V

El resultado será similar al siguiente:
Server version: Apache/2.2.22 (Debian)
Server built: Jul 24 2014 15:34:00
Server's Module Magic Number: 20051115:30
Server loaded: APR 1.4.6, APR-Util 1.4.1
Compiled using: APR 1.4.6, APR-Util 1.4.1
Architecture: 64-bit
Server MPM: Prefork
threaded: no
forked: yes (variable process count)
Server compiled with....
-D APACHE_MPM_DIR="server/mpm/prefork"
-D APR_HAS_SENDFILE
-D APR_HAS_MMAP
-D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
-D APR_USE_SYSVSEM_SERIALIZE
-D APR_USE_PTHREAD_SERIALIZE
-D APR_HAS_OTHER_CHILD
-D AP_HAVE_RELIABLE_PIPED_LOGS
-D DYNAMIC_MODULE_LIMIT=128
-D HTTPD_ROOT="/etc/apache2"
-D SUEXEC_BIN="/usr/lib/apache2/suexec"
-D DEFAULT_PIDLOG="/var/run/apache2.pid"
-D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
-D DEFAULT_LOCKFILE="/var/run/apache2/accept.lock"
-D DEFAULT_ERRORLOG="logs/error_log"
-D AP_TYPES_CONFIG_FILE="mime.types"
-D SERVER_CONFIG_FILE="apache2.conf"

Los primeros ajustes a realizar son:

  • KeepAlive On: Permite conexiones persistentes (más de una petición por conexión)
  • MaxKeepAliveRequests 100: Número máximo de peticiones permitidas durante una conexión persistente
  • KeepAliveTimout 5: Número de segundos de espera para la próxima petición en la misma conexión

A continuación, se ha de configurar el módulo de multiprocesamiento (MPM). En el ejemplo anterior lo conoceremos por la primera línea de la sección "Server compiled with...", en el parámetro APACHE_MPM_DIR. Como se puede observar, el módulo de multiprocesamiento es prefork. Por tanto, hemos de buscar en el archivo de configuración de Apache la sección correspondiente a este módulo, el cual contendrá los siguientes parámetros:

<IfModule mpm_prefork_module>
   StartServers 50
   MinSpareServers 20
   MaxSpareServers 70
   ThreadLimit 64
   ThreadsPerChild 25
   ServerLimit 500
   MaxClients 500
   MaxRequestsPerChild 0
</IfModule>

Los significado de estos parámetros es el siguiente:

  • StartServers: Número de servidores con los que arranca
  • MinSpareServers: Número mínimo de procesos de servidor que se mantienen de repuesto
  • MaxSpareServers: Número máximo de procesos de servidor que se mantienen de respuesto
  • ThreadLimit: Número máximo de hilos concurrentes
  • ThreadsPerChild: Número de hilos en funcionamiento en cada proceso del servidor
  • ServerLimit: Núḿeros máximo de servidores
  • MaxClients: Número máximo de procesos de servidor con los que arranca
  • MaxRequestsPerChild: Número máximo de peticiones que un proceso puede servir

NOTA: En el caso de nuestro Apache no tenga instalado un módulo de multiprocesamiento, podemos instalar uno mediante los siguientes comandos:

sudo apt-get update
sudo apt-get install apache2-mpm-prefork

Concurrencia en MySQL

Aunque configuremos Apache para que abastezca una gran cantidad de concurrencia, si nuestras webs acceden a bases de datos MySQL, encontrarán otro cuello de botella, pues MySQL también tiene una configuración predeterminada de concurrencia a la base de datos (unas 100 conexiones).

Para ajustar la concurrencia en MySQL, hemos de editar en el archivo my.cnf los siguientes parámetros:

  • max_connections: Número máximo de conexiones
  • thread_concurrency: Número de hilos concurrentes

A continuación, hay que rearrancar la base de datos para que MySQL asuma los cambios:

/etc/init.d/mysql restart

NOTA: Este comando es para Linux. En Windows, es similar, salvo la ruta.

Por último, verificamos que MySQL está funcionando con los valores actualizados. Para ello, arrancamos el cliente MySQL:

mysql -u root -p

Una vez nos logamos como administrador (hay que introducir la contraseña), en la línea de comandos del cliente MySQL introducimos la siguiente sentencia:

mysql> SHOW VARIABLES;

Buscamos las variables max_connections y thread_concurrency para verificar sus valores.

lunes, 25 de agosto de 2014

Conceptos básicos en CakePHP

Instalar Adobe Reader en Linux Debian, Ubuntu, Mint...

En primer lugar, descargar el paquete de instalación para Linux de Adobe Reader:

$ wget http://ardownload.adobe.com/pub/adobe/reader/unix/9.x/9.5.5/enu/AdbeRdr9.5.5-1_i386linux_enu.deb

A continuación instalar Adobe Reader:

$ sudo dpkg -i --force-architecture AdbeRdr9.5.5-1_i386linux_enu.deb

Configurar mod_rewrite para Kumbia y CakePHP

Kumbia y CakePHP son dos frameworks MVC (Modelo Vista Controlador) para PHP muy populares y bastante similares. Para empezar a utilizar ambos, es requisito fundamental instalar Apache y realizar algunos ajustes en la configuración. El ajuste más crítico es el de mod_rewrite, el cual permite a Apache gestionar los direccionamientos (routings) generados por estos frameworks.

Para realizar esta configuración, es necesario configurar el archivo httpd.conf. Nota: en el caso de una instalación en distribuciones basadas en Ubuntu, es posible que no exista este archivo. Ello se debe a que está configurado para que utilice el archivo apache2.conf en su lugar).

LoadModule rewrite_module modules/mod_rewrite.so

Más adelante, en el mismo archivo, encontraremos una sección como ésta:

<Directory />
   ...
</Directory>

Esta sección configura los permisos o roles que tendrá, para apache, el acceso al directorio especificado y a sus subcarpetas. En este caso comenzaría en el directorio raíz del equipo, lo cual es muy crítico y peligroso. Por ello, debemos definir (si no lo tenemos ya), una sección que apunte al directorio de nuestra aplicación Kumbia o CakePHP, el cual suele ubicarse en el directorio principal de páginas Web de Apache. En su interior deberíamos tener la siguiente configuración:

<Directory "/var/www/miaplicacionweb">
   Options FollowSymLinks
   AllowOverride All
   Require all granted
</Directory>

Una vez hechos los cambios, se guarda el archivo y se reinicia el servidor Apache para que reconozca la nueva configuración:

sudo /etc/init.d/apache2 start

Con esto debería funcionar el routing de nuestras aplicaciones.

Posibles problemas

Si al lanzar el contenido de la aplicación da problemas con mod_rewrite, puede ser que la ruta del archivo mod_rewrite.so no sea la correcta. En ese caso hay buscar la ruta del mismo con:

$ locate mod_rewrite.so

ó con

$ sudo find / find -name mod_rewrite.so

y modificar el archivo httpd.conf (o apache2.conf, si es el caso), con la ruta correcta.

Si aún así sigue teniendo problemas, es posible que PHP no esté utilizando el módulo mod_rewrite. Para saberlo, lanzar una página phpinfo() y buscar el literal "mod_rewrite". Debería aparecer en una sección llamada "Configuration apache2handler", en la fila "Loaded Modules". Si no aparece, podemos instalar el módulo mediante el siguiente comando:

$ sudo a2enmod rewrite

Si a pesar de todo lo anterior, sigue sin funcionar, puede ser que Apache esté configurado para utilizarse como servidor virtual. En ese caso, la configuración de Apache sale del fichero de configuración alojado en /etc/apache2/sites-available. En dicho directorio habrá dos ficheros: 000-default.conf y default-ssl.conf. Si no se ha instalado o configurado Apache para utilizar protocolo SSL, editaremos el primer archivo y lo configuraremos como el httpd.conf. En el caso de utilizar el protocolo SSL, configuraremos el segundo archivo.

Tras realizar los cambios, habrá que reiniciar el servidor Apache y debería funcionar todo.

jueves, 21 de agosto de 2014

Guía de Instalación de Apache y PHP en Ubuntu y Linux Mint

Instalación de Apache y PHP5:

$ sudo apt-get install apache2
$ sudo apt-get install php5
$ sudo apt-get install libapache2-mod-php5
$ sudo /etc/init.d/apache2 restart

Para arrancar, parar y rearrancar Apache:

$ sudo /etc/init.d/apache2 start
$ sudo /etc/init.d/apache2 stop
$ sudo /etc/init.d/apache2 restart

Adicionalmente, se pueden instalar extensiones o módulos pdo, con lo que podremos añadir funcionalidades a php, tales como acceder a bases de datos, internacionalización, encriptación, json, etc.

$ sudo apt-get install php5-dev
$ sudo apt-get install php5-mysql
$ sudo apt-get install php5-pgsql
$ sudo apt-get install php5-sqlite
$ sudo apt-get install php5-intl
$ sudo apt-get install php5-mcrypt
$ sudo apt-get install php5-gd
$ sudo apt-get install php5-curl
$ sudo apt-get install php5-json

Para conocer los módulos PHP instalados con apt-get:

$ dpkg --list | grep php

Para ver la versión de PHP:

$ php -v

Para ver la información completa de la configuración de PHP:

$ php -i

Para ver los módulos instalados con PHP:

$ php -m

Para verificar que Apache y PHP están configurados bien, crear un archivo info.php en /var/www (o en el directorio html por defecto de Apache):

<?php
phpinfo();
?>
Abrir un navegador y escribir la url: http://localhost/info.php. Debería aparecer la siguiente pantalla con toda la información de nuestra instalación de PHP:

lunes, 5 de mayo de 2014

Clases y objetos en Ruby (IV)

Cuando utilicemos clases y objetos, puede ser interesante tener un alcance más amplio y compartido que el del propio objeto. De esta manera, podemos compartir un mismo valor para todos los objetos de una misma clase. También podemos definir métodos que puedan ser de ámbito generalista, de modo que podemos invocarlos sin necesidad de instanciar un objeto.

VARIABLES DE CLASE

Una variable de clase es una variable que es compartida entre varios objetos de esa clase. No hay que confundir una propiedad con una variable de clase. La propiedad es una variable con una entidad y valor propios del propio objeto. Una variable de clase, sin embargo, es una entidad única y compartida para todos los objetos instanciados de esa clase.

Para declarar una variable de clase, se escribe dos arrobas (@@) antes del nombre de la variable.

En el siguiente ejemplo definimos una clase con una propiedad y una variable de clase.

class MiClase
   @@varClase = 1

   def initialize
     @propiedad = 1
   end

   def propiedad=(valor)
     @propiedad=valor
   end

   def propiedad
     @propiedad
   end

   def varClase=(valor)
     @@varClase=valor
   end

   def varClase
     @@varClase
   end
end

obj1 = MiClase.new
obj2 = MiClase.new

# Se refiere a una variable distinta en cada objeto
obj1.propiedad = 10
obj2.propiedad = 20

# Se refiere a la misma variable en todos los objetos
obj1.varClase = 25 # Valdra 25 en todos los objetos
obj2.varClase = 30 # Valdra 30 en todos los objetos (cambia 25 por 30)

puts "obj1 > @propiedad = #{obj1.propiedad} / @@varClase = #{obj1.varClase}"
puts "obj2 > @propiedad = #{obj2.propiedad} / @@varClase = #{obj2.varClase}"

El resultado será el siguiente:

obj1 > @propiedad = 10 / @@varClase = 30
obj2 > @propiedad = 20 / @@varClase = 30

Una variable de clase tiene un alcance privado, por lo que, para acceder a su valor, requiere que se escriban métodos de acceso.

METODOS DE CLASE

Un método de clase es un método que no está ligado a un objeto en particular, el cual puede ser invocado sin crear una instancia de objeto.

La utilidad de los métodos de clase es la de facilitar de herramientas de uso general y/o genéricas.

La forma de declarar y de invocar a este tipo de métodos es anteponiendo el nombre de la clase (seguido de un punto) al nombre del método.

class MiClase
   def initialize(propiedad)
     @propiedad=propiedad
   end

   # Metodo de instancia
   def calcular_cubo
     @propiedad * @propiedad
   end

   # Metodo de clase
   def MiClase.sumar(valor1, valor2)
     valor1 + valor2
   end
end

# El metodo de clase se puede invocar sin instanciar objetos
puts "La suma de 2 + 5 es #{MiClase.sumar(2, 5)}"

# El metodo de instancia requiere un objeto
obj = MiClase.new(3)
puts "El cubo de 3 es #{obj.calcular_cubo}"



TE PUEDE INTERESAR

viernes, 25 de abril de 2014

Clases y objetos en Ruby (III)

En los anteriores artículos sobre Clases y objetos en Ruby, hemos abordado los fundamentos acerca de la creación de clases, el acceso a las propiedades de un objeto y la visibilidad de las variables. En este artículo aprenderemos a aprovechar la herencia de clases y el control de acceso o visibilidad de los métodos de una clase.

HERENCIA

La herencia es una funcionalidad del paradigma de la programación orientada a objetos. Esta funcionalidad permite reutilizar el código, con el consiguiente ahorro de tiempos, esfuerzos y costes.

Gracias a la herencia podemos crear nuevas clases que hereden propiedades y métodos de otras clases. La clase de la cual se hereda se suele denominar clase padre o superclase, y la clase que hereda se suele denominar clase hija, clase especializada o subclase.

Para entenderlo mejor, aprovecharemos el ejemplo de la clase Humano. Esta clase es muy genérica y contiene las propiedades y métodos comunes a todo ser humano. Podríamos querer especializar esta clase en una nueva clase llamada Profesional, con la cual vamos a tratar un ser humano trabajador. Esta nueva clase heredará todas las propiedades de Humano, añadiendo nuevas propiedades y métodos especializados de un trabajador.

Para que una clase herede de otra ha de utilizarse la siguiente sintaxis:

class subclase < superclase
...
end

La clase Profesional quedaría así:

load "Humano.rb"

class Profesional < Humano

   def initialize(nombre, apellidos, edad, altura, peso, sexo, profesion, especialidad, experiencia)
     super(nombre, apellidos, edad, altura, peso, sexo)
     @profesion = profesion
     @especialidad = especialidad
     @experiencia = experiencia
   end

   def to_s
     super + " > Profesion: #{@profesion} - Especialidad: #{@especialidad} - Experiencia: #{@experiencia} años"
   end
end

un_profesor = Profesional.new("Andres", "Garcia Tomillar", 56, 1.82, 97.5, "hombre", "Profesor", "Literatura", 27)
puts un_profesor.to_s
un_profesor.edad = 57
puts un_profesor.to_s

La primera línea:

load "Humano.rb"

Permite cargar la clase Humano para poder ser utilizada por la subclase Profesional. Esto se realiza en el caso de que la superclase resida en otro archivo distinto al de la subclase.

En la declaración de la clase estamos indicando que la clase Profesional es una subclase de la superclase Humano.

Después, inicializamos las propiedades de esta nueva clase, por lo que hay que inicializar las propiedades de Humano (superclase) y de la propia clase Profesional (subclase).

El método initialize tiene los mismos parámetros que el constructor de la superclase, más sus parámetros propios o especializados.

La sentencia super invoca al mismo método de la superclase. En el caso del método initialize, invoca al método initialize de la clase Humano, pasando los parámetros correspondientes. En el caso del método to_s, la llamada al método retorna la cadena devuelta por método to_s de la superclase, y le concatena otra cadena con el resultado de las propiedades de la subclase, y la nueva cadena es retornada.

En la línea

un_profesor.edad = 57

se invoca al método heredado de la superclase, asignando el valor a esta propiedad heredada.

El resultado final será el siguiente:

Andres Garcia Tomillar, hombre, de 56 años, de 97.5 kg y 1.82 m > Profesion: Profesor - Especialidad: Literatura - Experiencia: 27 años
Andres Garcia Tomillar, hombre, de 57 años, de 97.5 kg y 1.82 m > Profesion: Profesor - Especialidad: Literatura - Experiencia: 27 años


VISIBILIDAD DE LOS METODOS

Al tratar una compleja aplicación con objetos, es necesario tener un control de acceso a los métodos. En artículos anteriores aprendimos la visibilidad el control de acceso a las propiedades. Ahora le toca el turno a los métodos.

ACCESO PUBLICO

Los métodos, por defecto, son declarados con una visibilidad pública. Es decir, que cualquiera puede acceder a los mismos.

La única excepción sería el método initialize, el cual tiene siempre un acceso privado.

ACCESO PROTEGIDO

Un método de acceso protegido puede ser invocado únicamente por la clase que lo define y sus subclases. Puede decirse que es un método estrictamente “familiar”, porque permanece exclusivamente dentro de su familia.

ACCESO PRIVADO

Un método de acceso privado es accesible únicamente por la clase que lo define y sus subclases directas (primer nivel de jerarquía), dentro de ese mismo objeto.

Un método privado sólo puede ser invocado dentro del contexto del objeto de llamada. No se puede acceder directamente a los métodos privados de otro objeto.

DEFINICION DE LA VISIBILIDAD

Para definir si un método tiene una determinada visibilidad, es necesario escribir el método dentro de un bloque de código mediante la palabra clave public (opcional), protected o private.

La estructura quedaría de la siguiente manera:

class Clase

   # bloque para metodos publicos
   # (la palabra public es opcional)
public

   def metodos_publicos
     ...
   end

   # bloque para metodos protegidos
protected

   def metodos_protegidos
     ...
   end

   # bloque para metodos privados
private

   def metodos_privados
     ...
   end

end

Otra forma más simple de organizar y definir el control de acceso a los métodos, es definiendo todos los métodos de forma normal, y, al final, definir qué control de acceso poseen los distintos métodos. La estructura quedaría de la siguiente manera:

class Clase

   def metodo1
     ...
   end

   def metodo2
     ...
   end

   def metodo3
     ...
   end

   def metodo4
     ...
   end

   public :metodo1, metodo3
   protected :metodo2
   private :metodo4
end

Esta estructura es una simplificación de la estructura anterior. El precompilador de Ruby organizará automáticamente el acceso como se ha visto en la primera estructura.

EJEMPLO DE CONTROL DE ACCESO

El siguiente ejemplo ilustra el funcionamiento del control de acceso a los métodos.

class Clase1

   def initialize
     @var = 1
   end

   def var
     @var
   end

   def incrementar
     @var = @var + 1
   end

   def incrementar_por(incremento)
     @var = var + incremento
   end

   def decrementar
     @var = @var - 1
   end

   public :var, :incrementar
   protected :incrementar_por
   private :decrementar

end

class Clase2 < Clase1

   def initialize
     super
     @var = var
   end

   def procesar
     @var = incrementar
     @var = incrementar_por(10)
     @var = decrementar
     @var #retorna el resultado
   end
end

class Clase3 < Clase2

   def initialize
     @var = 10
     @objeto = Clase2.new
   end

   # Este metodo accede al metodo heredado de
   # la instancia de Clase2
   def incrementar
     @objeto.incrementar
   end

   # Este metodo accede al metodo heredado de
   # la instancia de Clase2
   def incrementar_por(incremento)
     @objeto.incrementar_por(incremento)
   end

   # Este metodo accede al metodo heredado de
   # la instancia de Clase2
   # Este metodo dara un error, pues se intenta
   # acceder a un metodo privado de Clase1
   # directamente a traves de un objeto de Clase2
  # def decrementar
    # @objeto.decrementar
  # end

   # Este metodo accede a los metodos heredados
   # directamente de Clase1
   def procesar
     @var = incrementar
     @var = incrementar_por(10)
     @var = decrementar
     @var #retorna el resultado
   end
end

ejemplo = Clase2.new
puts ejemplo.procesar # Retorna 11
puts ejemplo.var # Acceso a metodo publico
# ejemplo.incrementar_por(3) # Dara un error de acceso
# ejemplo.decrementar # Dara un error de acceso

ejemplo2 = Clase3.new
puts ejemplo2.incrementar # Retorna 2
puts ejemplo2.incrementar_por(5) # Retorna 7
# puts ejemplo2.decrementar # Dara un error de acceso
puts ejemplo2.procesar #Retorna 17

Se han creado tres clases. Clase1 es la superclase. Clase2 es subclase directa de Clase1. Clase3 es subclase directa de Clase2, pero de segundo nivel con respecto a Clase1.

En Clase1 se han definido varios métodos con distinto control de acceso.

En la ejecución se crea una instancia de Clase2. Dentro de esta clase hay un método procesar, el cual accede, desde dentro de la clase, a todos los métodos heredados de Clase1, pues tiene visibilidad a sus métodos públicos, protegidos y privados.

Si intentamos acceder directamente a los métodos heredados de Clase1, a través de una instancia de Clase2, no tendremos acceso a los métodos protegidos ni privados. Éstos son sólo accesibles desde el interior de la propia Clase2.

La Clase3 hereda directamente de Clase2. Además, define una propiedad @obj que es una también una instancia de Clase2, a fin de poder comprobar el control de acceso mediante herencia y mediante instancia a esta clase. En el caso de instancia, no tendremos acceso al método privado heredado de Clase1.




TE PUEDE INTERESAR

jueves, 24 de abril de 2014

Clases y objetos en Ruby (II)

En el primer artículo sobre Clases y Objetos en Ruby, hablamos sobre las propiedades de los objetos y cómo se accedía a dichas propiedades para consultar o modificar sus valores. A modo de recordatorio, comentábamos que las propiedades de un objeto estaban gestionadas por unas variables con visibilidad de instancia, es decir que sólo podían ser accedidas desde la propia clase, y no directamente desde fuera de ella. Para acceder a estas variables había que crear e invocar a ciertos métodos para acceso de lectura (consulta) o de escritura (modificación).

En este artículo simplificaremos aún más la forma de acceder a las propiedades, y veremos en detalle la visibilidad de las variables en una clase.

SIMPLIFICANDO EL ACCESO A LAS PROPIEDADES

Recordemos el código de nuestra clase Humano:

class Humano

   def initialize(nombre, apellidos, edad, altura, peso, sexo)
     @nombre = nombre
     @apellidos = apellidos
     @edad = edad
     @altura = altura
     @peso = peso
     @sexo = sexo
   end

   def to_s
     "#{@nombre} #{@apellidos}, #{@sexo}, de #{@edad} años, de #{@peso} kg y #{@altura} m"
   end

   def nombre
     @nombre
   end

   def apellidos
     @apellidos
   end

   def edad
     @edad
   end

   def altura
     @altura
   end

   def peso
     @peso
   end

   def sexo
     @sexo
   end

   def nombre=(nombre)
     @nombre = nombre
   end

   def apellidos=(apellidos)
     @apellidos = apellidos
   end

   def edad=(edad)
     @edad = edad
   end

   def altura=(altura)
     @altura = altura
   end

   def peso=(peso)
     @peso = peso
   end

   def sexo=(sexo)
     @sexo = sexo
   end

end

un_humano = Humano.new("Adela","Sanchez Gomez", "27", 1.67, 55.7, "mujer")
otro_humano = Humano.new("Javier", "Martin Lopez", "44", 1.74, 80.5, "hombre")
puts "Valor de un_humano: " + un_humano.to_s
puts "Valor de otro_humano: " + otro_humano.to_s
un_humano.peso = 71
un_humano.edad = 32
puts un_humano.nombre + " " + un_humano.apellidos + " tiene #{un_humano.edad} años de edad y #{un_humano.peso} kg de peso"


Para consultar el valor de una propiedad utilizábamos un método que tenía el mismo nombre que la propiedad, retornando el valor de la propiedad, representado por una variable de instancia:

def nombre
   @nombre
end

Ruby permite simplificar toda esta sintaxis, mediante la siguiente abreviatura:

attr_reader :propiedad1, :propiedad2, ...

A la hora de ejecutar la aplicación, el pre-compilador de Ruby generará, automáticamente, los métodos de acceso de lectura correspondientes.

Para modificar el valor de una propiedad utilizábamos un método que tenía el mismo nombre que la propiedad más el signo igual, asignando a la propiedad el valor pasado por parámetro:

def nombre=(nombre)
   @nombre = nombre
end

Ruby permite simplificar toda esta sintaxis, mediante la siguiente abreviatura:

attr_writer :propiedad1, :propiedad2, ...

A la hora de ejecutar la aplicación, el pre-compilador de Ruby generará, automáticamente, los métodos de acceso de escritura correspondientes.

Así pues, el código final de la aplicacion se reduciría drásticamente:

class Humano
   attr_reader :nombre, :apellidos, :edad, :altura, :peso, :sexo
   attr_writer :nombre, :apellidos, :edad, :altura, :peso, :sexo

   def initialize(nombre, apellidos, edad, altura, peso, sexo)
     @nombre = nombre
     @apellidos = apellidos
     @edad = edad
     @altura = altura
     @peso = peso
     @sexo = sexo
   end

   def to_s
     "#{@nombre} #{@apellidos}, #{@sexo}, de #{@edad} años, de #{@peso} kg y #{@altura} m"
   end

end

un_humano = Humano.new("Adela","Sanchez Gomez", "27", 1.67, 55.7, "mujer")
otro_humano = Humano.new("Javier", "Martin Lopez", "44", 1.74, 80.5, "hombre")
puts "Valor de un_humano: " + un_humano.to_s
puts "Valor de otro_humano: " + otro_humano.to_s
un_humano.peso = 71
un_humano.edad = 32
puts un_humano.nombre + " " + un_humano.apellidos + " tiene #{un_humano.edad} años de edad y #{un_humano.peso} kg de peso"


VISIBILIDAD DE LAS VARIABLES

VARIABLES DE METODO

Las variables definidas en un método, ya sea como parámetro o como parte del cuerpo del propio método, solamente tienen visibilidad dentro del propio método. Es decir, se crean en el método y se destruyen cuando termina la ejecución del método.

def metodo(nombre)
   mi_nombre = nombre
end

Las variables de método tienen su propia instancia en memoria, por lo que, aunque exista una variable de visibilidad superior con idéntico nombre, éstas se asumen como diferentes:

def metodo(nombre)
   @nombre = nombre
end

En este caso, se asigna a la variable de instancia @nombre el valor de la variable de método nombre pasada por parámetro. Cuando termina la ejecución del método, la variable nombre se destruye, pero la variable @nombre sigue vigente con el valor asignado.



VARIABLES DE INSTANCIA

Las variables de instancia son variables con una visibilidad para toda la clase, es decir, que puede ser utilizada por cualquier método de la misma mientras el objeto instanciado esté vivo. Estas variables son privadas a la clase, y no pueden ser accedidas fuera de ella, a no ser que se utilicen métodos que accedan de forma explícita a los mismos.

Por ejemplo:

class Clase
   def initialize
     @var=12
   end
end

miClase = Clase.new()
puts "Valor: #{miClase.var}"

Esto generará el siguiente error:

Clase.rb:24:in '<main>': undefined method 'var' for #<Clase:0x0000000318de38 @var=12> (NoMethodError)

Tampoco permitirá acceder mediante la siguiente expresión:

puts "Valor: #{miClase.@var}"

Generará el siguiente error:

Clase.rb:24: syntax error, unexpected tIVAR, expecting '('puts "Valor: #{miClase.@var}"

Ello se debe a que al escribir el nombre del objeto y un punto, lo siguiente que espera es un método, no una variable.

Como vimos en el artículo anterior, el acceso de lectura y escritura se realizará mediante dos métodos que tienen el mismo nombre que la propiedad:

class Clase
   def initialize
     @var=12
   end

   # Metodo de consulta a propiedad
   def var
     @var
   end

   # Metodo de asignacion a propiedad
   def var=(var)
     @var=var
   end
end

miClase = Clase.new()
puts "Valor inicial: #{miClase.var}"
# Llama al metodo de asignacion a propiedad
miClase.var = 24
# Llama al metodo de consulta a propiedad
puts "Valor cambiado: #{miClase.var}"

El resultado es el siguiente:

Valor inicial: 12
Valor cambiado: 24

Podemos utilizar otros métodos para acceder a las variables de instancia de otras maneras:

class Clase
   def initialize
     @var=12
   end

   def incrementar
     @var = @var+1
   end

   def incrementar_por(incremento)
     # incremento es variable de metodo
     @var = @var+incremento
   end

   # Metodo de consulta a propiedad
   def var
     @var
   end

   # Metodo de asignacion a propiedad
   def var=(var)
     # Asignacion de variable de metodo a variable de instancia
     @var=var
   end
end

miClase = Clase.new()
puts "Valor inicial: #{miClase.var}"
# Llama al metodo de asignacion a propiedad
miClase.var = 24
# Llama al metodo de consulta a propiedad
puts "Valor cambiado: #{miClase.var}"
# Llama al metodo incrementar dos veces
miClase.incrementar
miClase.incrementar
puts "Valor incrementado: #{miClase.var}"
# Llama al metodo incrementar_por
# incremento de 10
miClase.incrementar_por(10)
puts "Valor incrementado por 10: #{miClase.var}"

El método incrementar suma 1 al valor actual de la variable de instancia.

El método incrementar_por suma el valor pasado por parámetro al valor actual de la variable de instancia.

El resultado será el siguiente

Valor inicial: 12
Valor cambiado: 24
Valor incrementado: 26
Valor incrementado por 10: 36



TE PUEDE INTERESAR

Clases y objetos en Ruby (I)

Este es el primero de una serie de artículos que revisa en profundidad el paradigma de la programación orientada a objetos mediante Ruby. Estos artículos no pretenden ser una clase magistral de este paradigma, pero sus ejemplos ayudan a entender claramente tanto sus principios como su aplicación en Ruby.

En este primer artículos aprenderemos conceptos básicos, tales como clase, objeto, propiedad y método. También aprenderemos cómo podemos consultar y modificar el valor de una propiedad.

INTRODUCCION

En un lenguaje de programación orientado a objetos, toma como concepto primordial el hecho de que todo cuanto existe se interpreta y se ve como un objeto, el cual puede tener unas propiedades o atributos, así como también unos métodos o acciones.

Todo comienza por una clase, que es una definición genérica, un molde o una abstracción de un tipo de objeto. A partir de la clase se generarán instancias u objetos, que son las entidades físicas de la clase.

EJEMPLO DE CLASE Y OBJETO

Imaginemos a un ser humano. La definición genérica de un ser humano puede tener una serie de propiedades comunes, como un nombre, unos apellidos, un sexo, una edad, una altura y un peso.

Un ser humano puede desempeñar diferentes acciones, tales como respirar, mirar, escuchar, andar, nadar, etc.

Yo, tú, él, ella, aquel, aquella... somos objetos o entidades físicas de un ser humano. Como entidades físicas, somos realidades y no abstracciones. Tenemos las propiedades comunes de un ser humano, pero los valores de nuestras propiedades son únicos e inherentes a cada uno.

Para llevar a cabo este ejemplo, crearemos el siguiente ejemplo en un fichero llamado Humano.rb:

class Humano
   def initialize
     @nombre = "Anonimo"
     @apellidos = ""
     @edad = 33
     @altura = 1.75
     @peso = 73.5
     @sexo = "hombre"
   end

   def to_s
     "#{@nombre} #{@apellidos}, #{@sexo}, de #{@edad} años, de #{@peso} kg y #{@altura} m"
   end
end

un_humano = Humano.new()
puts un_humano.to_s

Una clase en Ruby comienza con la declaración class, seguida del nombre de la clase. Al ser una definición genérica, su nombre empieza con mayúsculas.

Esta clase Humano tiene dos métodos o acciones: initialize y to_s

El método initialize es un método especial común a todas las clases. Es un método constructor, es decir, que cuando creemos una instancia o un objeto de esta clase, éste se invocará automáticamente en primer lugar para inicializar el objeto. Este método se suele utilizar para inicializar los valores de las propiedades del objeto y/o lanzar algún método de arranque. En este ejemplo, define seis variables de instancia, las cuales comienzan por el símbolo @, y representan cada una de las propiedades o atributos. Este tipo de variables sólo tienen visibilidad dentro del objeto, asegurando así la seguridad de los datos.

El método to_s es el símil del método toString de otros lenguajes de programación. Este método se utiliza para que nos devuelva una cadena de texto, que suele ser la información más relevante del objeto. En nuestro caso, retornará un texto con los valores de cada una de las propiedades del objeto. Este método es opcional.

Fuera de la definición de la clase, se han introducido dos líneas que se ejecutarán al lanzar este programa desde el intérprete de Ruby.

La primera línea crea una variable a la que se le asigna una nueva instancia de objeto. Con el método new, Ruby crea una nueva instancia de la clase Humano.

La segunda línea visualiza en pantalla la cadena retornada por el método to_s

Para ejecutar esta aplicación, ejecutaremos la siguiente línea desde la consola de comandos del sistema operativo:

ruby Humano.rb
Anonimo , hombre, de 33 años, de 73.5 kg y 1.75 m


INICIALIZANDO PROPIEDADES

En el ejemplo anterior estamos limitados, pues podemos crear múltiples objetos o instancias, pero todos tendrán los mismos valores en sus propiedades. Es decir, crearemos clones de humanos, donde, en su esencia, son objetos distintos, pero que en su apariencia son idénticos.

Para resolver esto, modificaremos el método constructor, permitiendo definir estas propiedades al mismo tiempo que se crea el objeto:

class Humano

   def initialize(nombre, apellidos, edad, altura, peso, sexo)
     @nombre = nombre
     @apellidos = apellidos
     @edad = edad
     @altura = altura
     @peso = peso
     @sexo = sexo
   end

   def to_s
     "#{@nombre} #{@apellidos}, #{@sexo}, de #{@edad} años, de #{@peso} kg y #{@altura} m"
   end

end

un_humano = Humano.new("Adela","Sanchez Gomez", "27", 1.67, 55.7, "mujer")
otro_humano = Humano.new("Javier", "Martin Lopez", "44", 1.74, 80.5, "hombre")
puts "Valor de un_humano: " + un_humano.to_s
puts "Valor de otro_humano: " + otro_humano.to_s

El método constructor tiene una lista de parámetros, los cuales son variables de ámbito del método (fuera de ella no tienen sentido). Cada una de estas variables recogerá un valor, y dicho valor se asignará, dentro del método, a una variable de ámbito de instancia. Es importante entender que una variable de método sólo tiene visibilidad dentro de un método, y que una variable de instancia tiene visibilidad en toda la clase. Aunque las variables tengan el mismo nombre, su ámbito y entidad son diferentes. Así:

@nombre = nombre

Asigna a la variable de instancia @nombre, el valor de la variable de método nombre.

El resultado será que, cada vez que se crea un objeto, se inicializará dicho objeto con los valores que pasemos como parámetros en el método new

La aplicación arrancará en la primera línea inmediatamente después de la definición de la clase. Se crearán dos objetos (un_humano y otro_humano), cada uno con unas propiedades o atributos concretos. A continuación, se visualizará los valores de dichas propiedades.

Valor de un_humano: Adela Sanchez Gomez, mujer, de 27 años, de 55.7 kg y 1.67 m
Valor de otro_humano: Javier Martin Lopez, hombre, de 44 años, de 80.5 kg y 1.74 m


ACCEDIENDO AL VALOR DE UNA PROPIEDAD

El ejemplo ha ido mejorando, pero tiene el inconveniente de que nos muestra todos los valores en el orden y el formato que el método to_s ha definido. Si queremos acceder libremente a una determinada propiedad, para cualquier propósito, hemos de definir métodos específicos dentro de la clase, con el mismo nombre que la propiedad:

def nombre
   @nombre
end

def apellidos
   @apellidos
end

def edad
   @edad
end

def altura
   @altura
end

def peso
   @peso
end

def sexo
   @sexo
end

Cada uno de los métodos retorna el valor de la propiedad que tiene su mismo nombre.

De esta manera, podemos consultar el valor de cada una de las propiedades del objeto de la siguiente manera:

nombre_objeto.nombre_propiedad

Como en la siguiente línea:

puts un_humano.nombre + " " + un_humano.apellidos + " tiene " + un_humano.edad + " años de edad"
Adela Sanchez Gomez tiene 27 años de edad


CAMBIANDO EL VALOR DE UNA PROPIEDAD

Nuestro humano va mejorando. Podemos conocer el valor de cada una de sus propiedades en cualquier momento. Sin embargo, un objeto no tiene por qué ser estático de por vida, si no que puede cambiar el valor de sus propiedades en cualquier momento. No hemos de matar a nuestro ser humano para crear un clon con los nuevos valores de sus propiedades.

La propiedad más proclive a cambiar sería su edad, su peso o su altura. Pero también es posible que quiera cambiar su nombre, sus apellidos y (¿por qué no?) su sexo.

La forma de poder asignar valores a una propiedad concreta es definiendo un método que tenga el mismo nombre de la variable más el signo = (igual), seguido de un parámetro que capture el valor y se lo asigne a la propiedad:

def nombre=(nombre)
   @nombre = nombre
end

def apellidos=(apellidos)
   @apellidos = apellidos
end

def edad=(edad)
   @edad = edad
end

def altura=(altura)
   @altura = altura
end

def peso=(peso)
   @peso = peso
end

def sexo=(sexo)
   @sexo = sexo
end

Cada uno de los métodos asigna a la propiedad con su mismo nombre, el valor pasado como parámetro.

De esta manera, podemos asignar el valor a cada una de las propiedades de la siguiente manera:

nombre_objeto.nombre_propiedad = nuevo valor

Como en las siguientes líneas:

un_humano.peso = 71
un_humano.edad = 32

Aquí podemos ver el resultado:

puts un_humano.nombre + " " + un_humano.apellidos + " tiene #{un_humano.edad} años de edad y #{un_humano.peso} kg de peso"
Adela Sanchez Gomez tiene 32 años de edad y 71 kg de peso


ENTENDIENDO EL ACCESO A LAS PROPIEDADES

En Ruby, las propiedades de un objeto son exclusivamente variables de instancia. Su visibilidad es privada y se accede estrictamente desde la propia clase. Esto asegura el encapsulamiento de éstas, así como también la seguridad de los datos.

En otros lenguajes de programación, resulta muy cómodo utilizar definir una clase de la siguiente manera:

/tr>
public class MiClase {
   public valor1 as Integer;
   public valor2 as String;
   ...
}

e instanciar la clase en objetos con acceso público a sus propiedades:

Set miObjeto As new MiClase;
miObjeto.valor1=1;
miObjeto.valor2="Valor"

Ruby contempla esta forma de gestionar los datos, gracias a los hashes o diccionarios, con mayor funcionalidad si cabe.

El problema de utilizar las clases de esta manera es la seguridad de la información, y el uso incontrolado o indebido por otras clases de una aplicación compleja. Para evitar ésto, las propiedades se definen como privadas a la clase (variables de instancia). De esta manera, su acceso está controlado de forma exclusiva por la propia clase, y para poder consultar o modificar el valor de una determinada propiedad, es necesario el uso de un método específico.

En el caso de Ruby, el uso de estos métodos ofrece una sintáxis muy similar a como si accediéramos de forma pública, lo que facilita mucho su semántica. Sin embargo, podemos hacer uso de una técnica llamada getters y setters, tal y como se realiza en otros lenguajes de programación.

Esta técnica consiste en definir un método get (obtener) y un método set (establecer) para el acceso a la propiedad. Por ejemplo, para acceder a la propiedad nombre, se utilizarían estos dos métodos:

def getNombre()
   @nombre
end

def setNombre(nombre)
   @nombre = nombre
end

A la hora de utilizar el objeto, lo realizaríamos de la siguiente manera:

objeto.setNombre(“Felipe”)
mi_nombre = objeto.getNombre()

El resultado es el mismo a como hemos visto anteriormente, aunque el nombre del método no es el mismo que el de la propiedad. La técnica vista anteriormente es más sencilla e intuitiva.




TE PUEDE INTERESAR