martes, 17 de enero de 2012

Un "group by" con Core Data

Cuando se trabaja con bases de datos siempre llega un momento en el que hay que hacer algo más complejo que recuperar datos de forma directa y manipularlos. Saber cual es el registro que tiene el valor más alto o más bajo en una propiedad en concreto; identificar los valores diferentes que tienen una propiedad; etc, son situaciones que, aunque no sean cotidianas, vamos a tener que enfrentarnos con ellas.

Si nos basamos en el ejemplo que utilizamos en la entrada anterior, podría ser de utilidad conocer que colores de pelo diferentes o que colores de ojos tenemos en nuestra base de datos. Con SQL esto se suele hacer con la clausulas "distinct" o "group by". La clausula distinct ahora mismo está en desuso y si bien no es 100 % equivalente a la clausula group by si que es cierto que los resultados obtenidos son análogos.

En Cocoa, si tienes una base de datos sqlite puedes hacer perfectamente un group by y gestionar los resultados. Sin embargo, hay una forma más sencilla de hacerlo: con Key-value coding (en adelante KVC) que no solo sirve para los objetos de Core Data si no también para arrays de diccionarios y objetos que estén preparados para KVC. Para ello usaremos los operadores de colección.

Un array con el color de pelo de los individuos de nuestra base de datos se obtendría así:

NSArray *coloresDePelo = [arrayDeIndividuos valueForKeyPath:"@distinctUnionOfArrays.colorPelo"];

El arrayDeIndividuos está formado por objetos de tipo Individuo que es un NSManagedObject y que por lo tanto admite KVC. Si el objeto Individuo fuese un diccionario (NSDictionary o NSMutableDictionary) también funcionaría. El operador que hemos usado es @distinctUnionOfArrays.

Una cosa más para que nos den la Matricula de Honor. Es muy posible que el array esté desordenado, para obtener un array con los colores de pelo ordenados alfabéticamente añadiríamos la siguiente instrucción:

NSArray *coloresDePeloOrdenados = [coloresDePelo sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];

La documentación de Apple sobre KVC la puedes encontrar aquí. Ahí podrás ver otros operadores de colección.
Si en vez de leer sobre KVC te apetece escuchar, el episodio 21 de 85 % Cocoa hace una excelente introducción.

jueves, 12 de enero de 2012

Cursores dinámicos y NSCompoundPredicate

Hace unos años, en un proyecto lleno de anécdotas y experiencias, el grupo de desarrolladores me contaba que habían creado servicios para cada una de las tablas del modelo de datos con el objetivo de centralizar los accesos a la tablas. La idea me pareció interesante: estaba harto de tocar programas que tenían la misma query y había cambiado algún criterio por lo que unificar las consultas en un único servicio era genial.

Les sugerí que, a la hora de montar los cursores, preparasen toda salida de datos de forma que fuera reutilizable. De esta forma, a medida que se fueran creando nuevas consultas, solo habría que incluir la nueva query. "No habrá que dar de alta nuevas consultas, hacemos cursores dinámicos..." dijo uno de ellos con gran orgullo.

 CURSORES DINÁMICOS... Dios mío... se han atrevido a utilizar SQL dinámico, el mayor sacrilegio en cualquier instalación con un mainframe, pensé yo. Cuando les dije que no se autorizaría la subida a producción de ningún programa con SQL dinámico me dijeron: "No, no. Si los hacemos con el SQL de toda la vida".

Lo que hacían era montar el where de tal forma que incluían todos los campos por los que se podría preguntar y comprobaban si el campo de la tabla era igual a la variable o si la variable estaba vacía. Esto tenía dos resultados: con un único cursor se obtenían los mismo resultados que con cursores ad-hoc (menos código que picar) y al abrir el cursor, el DB2 hacía un TABLESPACE SCAN y el programa aparecía en todas las estadísticas de pésimo rendimiento (gran bronca del DBA).
SELECT * FROM PERSONAS
 WHERE (EDAD = :EDAD OR :EDAD = 0)
   AND (SEXO = :SEXO OR :SEXO = ' ')
   AND (ALTURA = :ALTURA OR :ALTURA = 0)
   AND (PESO = :PESO OR :PESO = 0)
   AND (COLOROJOS = :COLOROJOS OR :COLOROJOS = ' ')
   AND (COLORPELO = :COLORPELO OR :COLORPELO = ' ')

Por eso, cuando la semana pasada me empeñe en montar un predicado variable para un proyecto de iOS y termine encontrando NSCompoundPredicate, pensé: "Esto si que es un cursor dinámico"

NSPredicate es una clase que se utiliza para definir condiciones lógicas que acoten una búsqueda. Esta clase se puede utilizar para obtener un array de objetos más pequeño a partir de otro más grande utilizando un método de la clase NSArray que es filteredArrayUsingPredicate:

Por ejemplo: tenemos un array llamado personas que contiene objetos de tipo Individuo. El objeto individuo tiene diferentes propiedades: edad, sexo, altura, peso, colorOjos, colorPelo, etc. Si queremos tener un array más pequeño con las personas que son mujeres y tiene los ojos azules haríamos lo siguiente:

 NSPredicate *predicate = [NSPredicate predicateWithFormat:
                        @"(sexo == %@ AND colorOjos == %@)", 
                        individuo.sexo, 
                        individuo.colorOjos]; 
NSArray *mujeresDeOjosAzules = [personas filteredArrayUsingPredicate:predicate];

En el ejemplo anterior, individuo.sexo contendría el valor "Mujer" e individuo.colorOjos contendría el valor "Azul". Bastante fácil, no?.

Bueno, supongamos ahora que estamos montando una especie de "Quien es quien" en el que dejamos que pregunten de forma aleatoria por un campo, por dos, por todos, etc... pero sin ningún control. En este caso deberíamos evaluar todas las posibilidades y definir un NSPredicate para cada una de ellas, evaluando previamente cada situación.
Con pocas propiedades es asumible montar los diferentes NSPredicate, con seis propiedades las combinaciones van a ser unas cuantas (si mis recuerdos de combinatoria no fallan serían 63 combinaciones diferentes)... ¿tenemos que codificarlas todas? No, existe NSCompoundPredicate, la panacea de los cursores dinámicos.

El ejemplo quedaría así:

 NSMutableArray *predicateArray = [NSMutableArray array];
if (individuo.edad) {
    [predicateArray addObject:[NSPredicate predicateWithFormat:@"(edad == %@)", individuo.edad]];
}
if (individuo.sexo) {
    [predicateArray addObject:[NSPredicate predicateWithFormat:@"(sexo == %@)", individuo.sexo]];
}
if (individuo.altura) {
    [predicateArray addObject:[NSPredicate predicateWithFormat:@"(altura == %@)", individuo.altura]];
}
if (individuo.peso) {
    [predicateArray addObject:[NSPredicate predicateWithFormat:@"(peso == %@)", individuo.peso]];
}
if (individuo.colorOjos) {
    [predicateArray addObject:[NSPredicate predicateWithFormat:@"(colorOjos == %@)", individuo.colorOjos]];
}
if (individuo.colorPelo) {
    [predicateArray addObject:[NSPredicate predicateWithFormat:@"(colorPelo == %@)", individuo.colorPelo]];
}
NSCompoundPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicateArray];

Lo que hemos hecho es un NSPredicate por cada propiedad que tenemos que evaluar. Comprobando previamente que esa propiedad esté informada. Cada NSPredicate lo incluimos en un array que posteriormente le pasaremos a la clase NSCompoundPredicate a través del método correspondiente.
Esta clase solo posee tres métodos: andPredicateWithSubpredicates para recuperar los objetos que cumplan todas las condiciones, orPredicateWithSubpredicates para recuperar los objetos que cumplen alguna condición y notPredicateWithSubpredicates que, creo, sirve para recuperar los objetos que no cumplen ninguna condición.

Si queréis saber más sobre NSPredicate y NSCompoundPredicate, lo mejor es consultar la documentación de Apple:

NSPredicate
NSCompoundPredicate

sábado, 3 de diciembre de 2011

Planificando la descarga de informes de iTunes Connect

Hace poco me puse con una tarea que tenía pendiente desde el verano: automatizar la descarga de informes del iTunes Connect.

El 30 de Agosto, en el Apple Developer Connection (en el iOS Developer News también) publicaron esto: Automatically Download iTunes Connect Reports
Lo que nos contaban ahí es que habían desarrollado una nueva herramienta para poder descargar los informes de ventas del iTunes Connect sin tener que acceder a dicho portal. Esto está muy bien, primero porque la descarga de varios informes es bastante tediosa, segundo porque es normal que no te acuerdes de entrar a descargarlos y puedes automatizar la descarga y tercero porque las herramientas para el seguimiento de las ventas brillan por su ausencia así que si empiezan a hacer alguna es una buena señal.

Voy a insistir un poco más en la importancia de la planificación de estas descargas: tanto con esta herramienta como desde el portal solo puedes descargarte los últimos 14 informes diarios y los últimos 13 informes semanales (de ventas, no los que echan en la primera de TVE los sábados por la noche). Si planificas una tarea de descarga diaria, mal se tiene que dar para que al menos una vez en 14 días tu Mac no esté encendido a la/s hora/s planificada/s.

En el enlace de iPad DevZone nos mostraban un Bash script para ejecutar la descarga, con comprobación de existencia de fichero descargado para no repetir además de los pasos a seguir para planificar este script correctamente.

Lo que escribo a continuación es, básicamente, la traducción del contenido de ese post con alguna aclaración más para los que no tengan ni idea de lo que es un script bash ni de como se planifica (yo era uno de esos hasta hace quince días).

1.- Creamos un directorio para nuestros scripts, por ejemplo: ~/bin/itc (para los no familiarizados con esta notación esto es crear una carpeta que se llame "bin" dentro de nuestra carpeta de usuario que, a su vez, contenga otra carpeta llamada "itc")
2.- Con nuestro editor de texto favorito (yo lo hice con TextWrangler) creamos un archivo llamado "download_itc_reports_sh" y lo guardamos en la carpeta "itc" que hemos creado antes. A continuación copiamos uno de los scripts que están al final de este post, lo pegamos y modificamos los parámetros de la sección de "Configuration" con nuestros propios datos. 
USER_NAME es nuestro usuario de acceso al iTC, USER_PASSWD la contraseña, VENDOR_ID el código de compañía y DIR_DEST el directorio donde vamos a descargar los informes. En mi caso particular, el directorio de descarga está dentro de la carpeta de DropBox que tengo en el Mac y así tengo el backup de los informes automatizado... ;-)
3.- En la misma carpeta donde dejamos este fichero, tenemos que tener la herramienta de descarga automática que no es otra cosa que una clase Java. Si no la has descargado todavía puedes hacerlo desde este enlace. Tendrás que tener Java instalado para poder utilizar esta herramienta.

Los siguientes pasos ya hay que darlos en el Terminal.

4.- Si queremos ejecutar manualmente el script, escribimos en el terminal lo siguiente: $ ~/bin/itc/download_itc_reports.sh
5.- Para planificarlo tendremos que usar otra herramienta escondida dentro del Terminal: Crontab
  1. Para editar las entradas de Crontab escribimos lo siguiente en el terminal: $ crontab -e
  2. En una nueva linea en blanco (o en la primera si no tenemos nada) escribimos lo siguiente 0 13,14,16 * * * ~/bin/itc/download_itc_reports.sh 
    Breve explicación sobre crontab. Cada registro consta de seis argumentos: minutos, horas, día del mes, mes, día de la semana y comando a ejecutar. Varios valores para el mismo argumento se separan por comas. El ejemplo se entiende como: "en el minuto cero de las 13, 14 y 16 horas de cualquier día, de cualquier mes y cualquier día de la semana ejecutar el comando ~/bin/itc/download_itc_reports.sh. Puedes encontrar más información de crontab en este enlace.
  3. Si ya hemos creado otro script (por ejemplo: uno para informes diarios y otro para los semanales) habría que incluir otra linea con la planificación deseada y el nombre del script.
  4. Una vez que hemos terminado de editar, guardamos y comprobamos nuestro trabajo escribiendo en el terminal: $ crontab -l
  5. Y ya está, si el Mac está encendido a las horas planificadas se ejecutarán los scripts.
Los informes son ficheros planos que pueden ser importados a cualquier hoja de cálculo o base de datos que nos hayamos creado. Yo uso una aplicación que se llama AppStar Lite que permite importar estos informes y visualizarlos de una forma agradable.
Actualmente podemos descargarnos dos tipos de informe: Diarios y Semanales. A continuación hay un script para cada uno.
Además, cada tipo de informe tiene dos subtipos: Summary y Opt-In. No tengo muy clara la diferencia entre los primeros y los segundos. Los scripts están escritos para descargar los Summary, para descargar los Opt-In simplemente habría que cambiar una palabra por la otra.

Script para descarga de informes diarios:
#!/usr/bin/env bash

#
# Copyright 2011, Shark Intelligence, s.r.o.
# Released under the MIT license
#

#-------------------------------------------------------------------------------
# Configuration
#-------------------------------------------------------------------------------

# ITC user name
USER_NAME="user name" 

# ITC user password
USER_PASSWD="user password" 

# vendorid: find it at: https://reportingitc.apple.com/subdashboard.faces
VENDOR_ID="vendor Id" 

# directory where the reports are downloaded; Current dir is used if empty;
DIR_DEST=~/projects/XXX/reports-sales 

# number of days beginning with yesterday which the reports are downloaded for
NUM_OF_DAYS_TO_DOWNLOAD=14 

# Currently only Sales reports are supported
REPORT_TYPE="S" 

# Currently only Daily report frequency is supported
REPORT_FREQ="D" 

#-------------------------------------------------------------------------------
# Program start
#------------------------------------------------------------------------------- 

# Adjust input vars
#
if [ -z "$DIR_DEST" ]; then DIR_DEST=`pwd -P`; fi 

# Print info
#
echo Downloading ITC sales reports for $NUM_OF_DAYS_TO_DOWNLOAD days into destination: $DIR_DEST 

# set CLASSPATH which should point the this script location
#
THIS_SCRIPT=$0
cd `dirname $THIS_SCRIPT`
CLASSPATH=`pwd -P` 

# set dir where the reports are downloaded to
#
cd $DIR_DEST 

# loop for requested NUM_OF_DAYS_TO_DOWNLOAD
#
for (( DAY=NUM_OF_DAYS_TO_DOWNLOAD; DAY>0; DAY-- ))
do 

DT_CUR_STR="date -v-${DAY}d +%Y%m%d"
DT_CUR=`$DT_CUR_STR`
FILE_CUR="${REPORT_TYPE}_${REPORT_FREQ}_${VENDOR_ID}_${DT_CUR}.txt.gz" 

if [ $(find . -name $FILE_CUR -maxdepth 1 -type f | wc -l) == "0" ]
then 

# Report is missing so download one
#
echo $DT_CUR: MISSING! Download initiated...
java -cp $CLASSPATH Autoingestion $USER_NAME $USER_PASSWD $VENDOR_ID Sales Daily Summary $DT_CUR 

else 

# Report is found
#
echo "$DT_CUR: already here" 

fi 

done
Script para descarga de informes semanales:
#!/usr/bin/env bash

#
# Copyright 2011, Shark Intelligence, s.r.o.
# Released under the MIT license
#

#-------------------------------------------------------------------------------
# Configuration
#-------------------------------------------------------------------------------

# ITC user name
USER_NAME="user name" 

# ITC user password
USER_PASSWD="user password"

# vendorid: find it at: https://reportingitc.apple.com/subdashboard.faces
VENDOR_ID="vendor Id" 

# directory where the reports are downloaded; Current dir is used if empty;
DIR_DEST=~/projects/XXX/reports-sales 

# number of days beginning with yesterday which the reports are downloaded for
NUM_OF_WEEKS_TO_DOWNLOAD=13 

# Currently only Sales reports are supported
REPORT_TYPE="S" 

# Currently only Daily report frequency is supported
REPORT_FREQ="W" 

#-------------------------------------------------------------------------------
# Program start
#------------------------------------------------------------------------------- 

# Adjust input vars
#
if [ -z "$DIR_DEST" ]; then DIR_DEST=`pwd -P`; fi 

# Print info
#
echo Downloading ITC sales reports for $NUM_OF_WEEKS_TO_DOWNLOAD days into destination: $DIR_DEST 

# set CLASSPATH which should point the this script location
#
THIS_SCRIPT=$0
cd `dirname $THIS_SCRIPT`
CLASSPATH=`pwd -P` 

# set dir where the reports are downloaded to
#
cd $DIR_DEST 

# loop for requested NUM_OF_WEEKS_TO_DOWNLOAD
#
for (( WEEK=NUM_OF_WEEKS_TO_DOWNLOAD; WEEK>0; WEEK-- ))
do

# set value for last sunday
#
DT_DAY_OF_WEEK=`date +%w`
let DAYS=(WEEK-1)*7+DT_DAY_OF_WEEK 

DT_CUR_STR="date -v-${DAY}d +%Y%m%d"
DT_CUR=`$DT_CUR_STR`
FILE_CUR="${REPORT_TYPE}_${REPORT_FREQ}_${VENDOR_ID}_${DT_CUR}.txt.gz" 

if [ $(find . -name $FILE_CUR -maxdepth 1 -type f | wc -l) == "0" ]
then 

# Report is missing so download one
#
echo $DT_CUR: MISSING! Download initiated...
java -cp $CLASSPATH Autoingestion $USER_NAME $USER_PASSWD $VENDOR_ID Sales Daily Summary $DT_CUR 

else 

# Report is found
#
echo "$DT_CUR: already here" 

fi 

done

miércoles, 26 de octubre de 2011

No empezamos muy bien... es difícil sacar tiempo para escribir un blog. 

De momento me conformo con dejar aquí un par de sitios web bastante interesantes que he encontrado últimamente:

  • MaaSive: Es un sitio web que ofrece persistencia en la nube. Lo encontré gracias a un tweet de Brandon  Trebitowski y el video demostrativo deja claro lo que es: un framework que podríamos utilizar para sustituir (o complementar según se quiera) a Core Data y almacenar todos los datos en la nube. Soluciones como esta hay unas cuantas: amazon cloud services, google app engine, etc. La ventaja de este framework es la clara orientación a iOS. Está todavía en fase alfa.
  • Parse:  Este es el que más impresionado me ha dejado. En algún momento pensarás en hacer una app que requiera notificaciones "push". Al margen de lo engorroso que pueda resultar montar todo el servicio de notificaciones, lo más probable es que la solución que encontremos tenga un coste económico ya que requiere de un servidor.
    Parse encapsula todo el proceso de notificación en un framework para que sea sencillo de implementar en una app y además es gratuito para pocos usos (relativamente, que ya me gustaría a mí tener una app que hiciera más de un millón de push al mes).
Hasta aquí por hoy. Estoy trabajando en algunas cosillas que me gustaría compartir pero todavía no están terminadas. Espero no tardar tanto en escribir el siguiente.

miércoles, 31 de agosto de 2011

DISPLAY 'HOLA MUNDO'

Espero que este no sea otro intento más de escribir un blog y pueda darle continuidad.

Llevo muchos años pensando en lo que podría escribir en un blog... tantos que hasta ahora no había escrito ninguno. Como últimamente estoy muy centrado en el tema de la programación para iOS y leo muchos blogs donde la gente cuenta cosas que ha aprendido o sabe hacer para ayudar al resto de la comunidad de desarrolladores (entre ellos a mi), he pensado que una buena idea para empezar sería devolver los favores escribiendo mis "avances". De eso es de lo que trata este blog... de como un triste programador de COBOL intenta hacer app´s para su iPhone.

Y para darle más cache a esta primera entrada, me voy a permitir el lujo de citar a Marcus Zarra que en su post "Why so serious?" hace una afirmación que ningún desarrollador debe olvidar jamás... o al menos eso pienso yo.

"We as developer must remember that we are not the target for 99% of the software that is written. We are not the final judge on what will or will not work. If anything, we are the last people that should have an opinion on what is good or bad. We should be the ones that step back and watch what the “normals” do with it.They determine the success or failure; not us."