domingo, 3 de noviembre de 2013

iPhone y Rails con devise, desde la App

Hace unos meses, escribí la enrtada iPhone y Rails con devise, en la que indicaba los pasos a seguir para preparar una app de Ruby on Rails para su acceso desde aplicaciones móviles sobre la gema devise. Debido al comentario realizado por Alejandro DV, incluyo ahora los pasos a realizar en el lado de la aplicación móvil.
Aunque aquí indicaré cómo realizarlo en Objective C, para aplicaciones con iOS, las llamadas serían similares para aplicaciones Android, ya que simplemente consiste en incluir el parámetro con el token en las llamadas correspondientes.


En primer lugar, mediante el editor de Xcode, crearemos la aplicación. Una vez hecho esto, crearemos nuestro LoginController. Esta será a la primera página que se acceda al abrir la aplicación, para asegurarnos de que el usuario se loga en nuestro sistema. En mi caso, la declaración quedaría así:

@interface LoginController : UIViewController <uinavigationcontrollerdelegate>
{
    UIActivityIndicatorView *activityIndicator;
    NSMutableData *responseData;

}

@property (nonatomic, retain) IBOutlet UITextField *username;
@property (nonatomic, retain) IBOutlet UITextField *password;
@property (nonatomic, retain) IBOutlet UIButton *envio;

Tendremos el activityIndicator, para indicar que estamos procesando la llamada, los campos de usuario y password, y el botón de envío.

Necesitaremos la implementación del IBAction del botón para enviar la información:


- (IBAction) envioPushed:(id)sender
{

    activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    activityIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
    activityIndicator.center = self.view.center;
    [self.view addSubview: activityIndicator];
    [activityIndicator startAnimating];

    
    NSString *login_url = [NSString stringWithFormat:@"http://%@:3000/%@", URL_SERVER, @"api/token"];
    NSURL *url = [NSURL URLWithString:login_url];
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
    NSString *params = [NSString stringWithFormat:@"user=%@&password=%@", username.text, password.text];
    NSData *myRequestData = [ NSData dataWithBytes: [ params UTF8String ] length: [ params length ] ];
    [urlRequest setHTTPMethod:@"POST"];
    [urlRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    [urlRequest setHTTPBody: myRequestData ];
    
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
    if(connection) {
        responseData = [[NSMutableData alloc] init];
    } else {
        NSLog(@"connection failed");
        [activityIndicator stopAnimating];
        [[UIApplication sharedApplication] endIgnoringInteractionEvents];
        AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
    }
    
}

De este modo, enviaremos una petición POST al servidor, a la url /api/token. Esto llamará a la identificación vía token en el servidor, tal y como lo habíamos implementado.
Posteriormente, en el delegate de la conexión (connectionDidFinishLoading)   deberemos guardar el token en un objeto NSUserDefauts, para tenerlo accesible desde el resto de la aplicación.

En las sucesivas llamadas a servicios de la aplicación Rails, deberemos incluir el parámetro auth_token, para que reconozca al usuario.

jueves, 16 de mayo de 2013

Semantic web. No es tan difícil.



La web semántica (Semantic Web) es la manera de representar los datos de la web de forma estructurada. El objetivo es conseguir una web que "se entienda" a sí misma, se busquen conceptos y no palabras.
La representación de este contenido viene dada, generalmente, por el formato RDF (Resource Description Framework) y es una forma de dar todo el contenido asociado a un término. Para realizar búsquedas dentro de este contenido, se usa el lenguaje de querys SPARQL.

En este post me voy a centrar en una forma de obtener el significado de las palabras que contiene un texto. Es decir, a partir de un fragmento, podremos conocer los principales términos y a qué se refieren.
Para ello vamos a usar DBPedia. Esto es una comunidad que se ha dedicado a introducir toda la información de Wikipedia en formato RDF, de modo que están todos los términos que necesitamos.

Para evitarnos tener que usar el lenguaje SPARQL, DBepedia nos ofrece su servicio Spotlight, de código abierto. Us servicio RESTful por medio del cual podemos realizar consultas, obteniendo las entidades asociadas a un texto dado.
Buscando por ahí, he encontrado además una gema que encapsula esta llamada: https://github.com/fumi/dbpedia-spotlight-rb

Así, si queremos acceder desde una aplicación rails, con instalar esta gema y realizar la llamada al servicio, obtendremos la lista de entidades asociadas. Para hace resto:


SPOTLIGHT_ACCESS = DBpedia::Spotlight("http://spotlight.dbpedia.org/rest/")
texto = "President Obama on Monday will call for a new minimum tax rate for individuals making more than $1 million a year to ensure that they pay at least the same percentage of their earnings as other taxpayers, according to administration officials."
entities = SPOTLIGHT_ACCESS.annotate texto
entities

=> [{"@URI"=>"http://dbpedia.org/resource/Presidency_of_Barack_Obama", "@support"=>"134", "@types"=>"DBpedia:OfficeHolder,DBpedia:Person,Schema:Person,Freebase:/book/book_subject,Freebase:/book,Freebase:/book/periodical_subject,Freebase:/media_common/quotation_subject,Freebase:/media_common,DBpedia:TopicalConcept", "@surfaceForm"=>"President Obama", "@offset"=>"0", "@similarityScore"=>"0.18565504252910614", "@percentageOfSecondRank"=>"-1.0"}, {"@URI"=>"http://dbpedia.org/resource/Rick_Monday", "@support"=>"96", "@types"=>"DBpedia:BaseballPlayer,DBpedia:Athlete,DBpedia:Person,Schema:Person,Freebase:/people/measured_person,Freebase:/people,Freebase:/sports/drafted_athlete,Freebase:/sports,Freebase:/sports/pro_athlete,Freebase:/baseball/baseball_player,Freebase:/baseball,Freebase:/people/person", "@surfaceForm"=>"Monday", "@offset"=>"19", "@similarityScore"=>"0.0737665593624115", "@percentageOfSecondRank"=>"0.6866002476572552"}, {"@URI"=>"http://dbpedia.org/resource/Call_option", "@support"=>"123", "@types"=>"", "@surfaceForm"=>"call", "@offset"=>"31", "@similarityScore"=>"0.12362345308065414", "@percentageOfSecondRank"=>"0.710047498083316"}, {"@URI"=>"http://dbpedia.org/resource/Maxima_and_minima", "@support"=>"131", "@types"=>"", "@surfaceForm"=>"minimum", "@offset"=>"46", "@similarityScore"=>"0.05654768645763397", "@percentageOfSecondRank"=>"-1.0"}, {"@URI"=>"http://dbpedia.org/resource/Individual", "@support"=>"312", "@types"=>"", "@surfaceForm"=>"individuals", "@offset"=>"67", "@similarityScore"=>"0.12983855605125427", "@percentageOfSecondRank"=>"-1.0"}, {"@URI"=>"http://dbpedia.org/resource/Million", "@support"=>"492", "@types"=>"", "@surfaceForm"=>"1 million", "@offset"=>"97", "@similarityScore"=>"0.12119115144014359", "@percentageOfSecondRank"=>"-1.0"}, {"@URI"=>"http://dbpedia.org/resource/University", "@support"=>"5001", "@types"=>"Freebase:/organization/organization_type,Freebase:/organization,Freebase:/business/company_type,Freebase:/business,Freebase:/tv/tv_subject,Freebase:/tv,Freebase:/education/school_type,Freebase:/education,Freebase:/book/book_subject,Freebase:/book,Freebase:/fictional_universe/type_of_fictional_setting,Freebase:/fictional_universe,Freebase:/architecture/building_function,Freebase:/architecture,DBpedia:TopicalConcept", "@surfaceForm"=>"year", "@offset"=>"109", "@similarityScore"=>"0.08789163082838058", "@percentageOfSecondRank"=>"0.9540837774225911"}, {"@URI"=>"http://dbpedia.org/resource/Payment", "@support"=>"129", "@types"=>"Freebase:/media_common/quotation_subject,Freebase:/media_common", "@surfaceForm"=>"pay", "@offset"=>"134", "@similarityScore"=>"0.11993571370840073", "@percentageOfSecondRank"=>"-1.0"}, {"@URI"=>"http://dbpedia.org/resource/Percentage", "@support"=>"165", "@types"=>"", "@surfaceForm"=>"percentage", "@offset"=>"156", "@similarityScore"=>"0.18815511465072632", "@percentageOfSecondRank"=>"-1.0"}, {"@URI"=>"http://dbpedia.org/resource/Income", "@support"=>"648", "@types"=>"Freebase:/media_common/quotation_subject,Freebase:/media_common,Freebase:/book/book_subject,Freebase:/book,DBpedia:TopicalConcept", "@surfaceForm"=>"earnings", "@offset"=>"176", "@similarityScore"=>"0.16177840530872345", "@percentageOfSecondRank"=>"-1.0"}, {"@URI"=>"http://dbpedia.org/resource/Tax", "@support"=>"1540", "@types"=>"Freebase:/tv/tv_subject,Freebase:/tv,Freebase:/organization/organization_sector,Freebase:/organization,Freebase:/book/book_subject,Freebase:/book", "@surfaceForm"=>"taxpayers", "@offset"=>"194", "@similarityScore"=>"0.15324345231056213", "@percentageOfSecondRank"=>"-1.0"}, {"@URI"=>"http://dbpedia.org/resource/Administration_%28government%29", "@support"=>"126", "@types"=>"", "@surfaceForm"=>"administration", "@offset"=>"218", "@similarityScore"=>"0.195627361536026", "@percentageOfSecondRank"=>"0.6496970292489601"}, {"@URI"=>"http://dbpedia.org/resource/Official", "@support"=>"196", "@types"=>"Freebase:/people/profession,Freebase:/people,Freebase:/fictional_universe/character_occupation,Freebase:/fictional_universe,Freebase:/book/book_subject,Freebase:/book", "@surfaceForm"=>"officials", "@offset"=>"233", "@similarityScore"=>"0.11004404723644257", "@percentageOfSecondRank"=>"-1.0"}] 

Con esto obtenemos una lista de entidades, con su categoría asociada, que podemos usar, por ejemplo, para identificar la categoría de un texto.



Nota: Si necesitamos realizar la petición tras un proxy...
SPOTLIGHT_ACCESS.class.http_proxy proxy_host, proxy_port


viernes, 10 de mayo de 2013

Conectar Rails con Facebook, Twitter (II)


En esta entrada continuaremos con algo que habñia dejado hace tiempo. Un tutorial para conctar nuestra aplicación con Rails y devise con las redes sociales. En la primera parte de  Conectar Rails con Facebook y Twitter,  explicaba los pasos necesarios para conseguir los identificadores de aplicación y configurar  devise.
Ahora, el paso que queda, es simplemente implementar un callback que obtenga los datos del usuario de la red social y lo relacione con el nuestro.
Primero, deberemos definir en el fichero routes.rb dónde queremos que vaya el callback:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

Esto provoca que el callback al volver de Facebook, Twitter, ... vaya a nuestro controlador. Este controlador (Users::OmniauthCallbacksController) deberá definir los métodos de las estrategias usadas:

  def twitter
    @user = User.find_for_twitter_oauth(request.env["omniauth.auth"], current_user, request.remote_ip)

    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format?
    else
      session["devise.twitter_data"] = request.env["omniauth.auth"]
      redirect_to root_url 
    end
  end

Así, buscaremos al usuario por su UID de la red social (Twitter, en este caso). Deberemos implementar este método en el modelo de usuario de devise:

  def self.find_for_twitter_oauth(auth, signed_in_resource=nil, location = nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(name: auth.extra.raw_info.name,
                         provider: auth.provider,
                         uid: auth.uid,
                         email: auth.info.email || auth.extra.raw_info.screen_name,
                         password: Devise.friendly_token[0, 20]
      )

      user.save
    end

    user.save

    user
  end

Ahora ya tendremos el usuario. En caso de que no existiera, se crea uno nuevo en base de datos. Esto depende si queremos conectar la cuenta de Twitter de nuestro usuario con la aplicación, o queremos directamente un login mediante el propio usuario .

Con esto, a falta de algún detalle, estaría listo el proceso de autenticaciṕn de usuarios mediante Twitter, Facebook, Github, Google Plus, ...

martes, 2 de abril de 2013

Rails Session Store con Mongoid

En una aplicación rails en la que usamos una base de datos Mongo DB y no usamos ActiveRecord, probablemente tendremos que cambiar el session store, ya que la cookie_store es bastante limitada.

Tras buscar un rato,  he optado por usar mongo_session_store. Esta gema permite el uso de sesiones en Mongo Db, tanto usando el mongo mapper como mongoid.

Además la configuración es bastante sencilla:

En el Gemfile:

gem "mongo_session_store-rails3"

Y en config/initializers/session_store.rb

TwitterResearch::Application.config.session_store :mongoid_store


miércoles, 13 de marzo de 2013

Subir Ficheros en Rails con Mongodb y GridFS

Cuando subimos ficheros (imágenes, vídeos, ...) con una aplicación web, hay ocasiones en las que no se pueden guardar en el FileSystem del propio servidor. Ya sea por espacios, permisos de escritura, tener varias instancias levantadas, ...
Para solucionarlo hay varias opciones:

Una es almacenarlo en una BBDD SQL como un dato BLOB (Binary Large Objects). Por regla general, la pega es que  necesitaremos una conversión previa de bytes, que es lo que guarda la base de datos, al fichero en sí.

Otra opción es usar un almacenamiento en la nube, por medio de Amazon S3, por ejemplo. De esta manera subimos el fichero directamente al servidor que nos de el servicio, y cuando se necesite acceder, lo haremos por URL. El único problema es que estos servicios suelen tener un coste añadido, aunque no demasiado elevado.

La tercera opción que propongo y que explicaré a continuación, es realizar el almacenamiento en MongoDB.
El límite de datos para un documento de MongoDB tipo BSON es de 16MB, un tamaño que se nos quedará corto para el uso que queremos darle. Para solucionar esto, esta base de datos cuenta con la especificación GridFS, mediante la cual el fichero se divide en trozos (chunks) de 256KB. Además se guarda información relativa al fichero (metadata).

La implementación en Rails es bastante sencilla. En primer lugar, incluimos la gema carrierwave-mongoid en la aplicación. Suponemos que tenemos ya mongoid instalado.
gem "carrierwave-mongoid"

Preparamos el formulario para subir ficheros:

= form_for :image_form, :url => { :controller => "ficheros", :action => :create }, :html => { :method => :post, :id => 'uploadForm', :multipart => true , :remote => true} do |f|
  %p
    = f.file_field :upload_data

  %p
    = submit_tag 'Subir fichero', :class => 'btn btn-primary'

En nuestro controlados, tendremos que tener un modelo que simplemente guarde el ID del fichero para poder acceder, y mediante esta gema, almacenaremos el fichero:


    @fichero = Fichero.new

    grid_fs = Mongoid::GridFs
    g = grid_fs.put(params[:image_form][:upload_data])

    @fichero.idfichero = g.id.to_s
    @fichero.save

Y con esto ya tenemos el fichero en MongoDB, y referencia a este en idFichero.

Si lo que queremos es mostrar la imagen en alguna página de la aplicación, lo haremos enviando el contenido del fichero desde el controlador. Desde la vista:

= image_tag imageuploaded_path(codigo.id)

imageupload es una ruta al action showimage del controlador:

Y desde el controlador hacemos un send_data:

  def showimage
    grid_fs = Mongoid::GridFs
    file = grid_fs.get(Fichero.find(params[:id]).idfichero)

    send_data file.data, :type => file.contentType, :disposition => 'inline'
  end

En nuestra base de datos de MongoDB nos creará las colecciones: fs.chunks y fs.files


miércoles, 6 de marzo de 2013

Convertir vistas de erb a haml

Al crear una aplicación en rails, los ficheros de vistas, por defecto, se crean de tipo erb. Puede que en ese momento, o más adelante, tras tener más vistas, queramos cambiar a HAML, ya que es una opción más limpia para programar.
Para ello primero incluimos la gema haml-rails en el Gemgile:

gem "haml-rails"
Luego, después de hacer bundle install, ejecutamos el siguiente comando por consola:
find . -name '*erb' | xargs ruby -e 'ARGV.each { |i| puts "html2haml -r #{i} #{i.sub(/erb$/,"haml")}"}' | bash

Con esto, se crearán los ficheros correspondientes de tipo HAML.

lunes, 18 de febrero de 2013

Ramas remotas en Git

En este post explicaré cómo hacer una gestión básica de las ramas remotas en Git.
Como ya vimos en el post sobre comandos básicos de Git, crear una rama en local es tan sencillo como usarl el comando checkout con la opción -b.

Una vez que tenemos la rama 'nuevarama' creada en local, para subirla al repositorio remoto, simplemente tendrmos que ejecutar:

git push origin nuevarama

Una vez hecho esto, podemos trabajar y seguir subiendo los cambios a esta rama remota usando el mismo comando. Una vez que tengamos la funcionalidad de la rama finalizada, haremos un merge contra master y así tendremos esta rama actualizada.
Ahora la nuevarama ya no es necesaria. Para eliminarla del origen,  deberemos hacer lo mismo, pero con dos puntos delante del nombre de la rama. Esto eliminará esta rama remota:

git push origin :nuevarama

Si ya tenemos una rama remota y lo que queremos es trabajar con ella, el comando deberá ser:

git checkout -b nuevarama origin/nuevarama

Para saber las ramas que existen en un proyecto, ejecutamos el comando git branch. Ejecutándolo así, nos dará las ramas locales. Si usamos la opción -r, las remotas, y con la opción -a, todas.

En este post sobre git podemos obtener más información.

martes, 22 de enero de 2013

JQuery: multi listas ordenables

A continuación veremos como implementar una lista de selección, donde podemos elegir varios elementos y pasarlos a otra lista. En esta nueva lista, podremos ordenar los elementos.

Vamos a realizar la generación con HAML y los scripts con JQuery, y el relleno de la lista se hará mediante código ruby, como vimos en el post de cómo crear combos de selección.

La parte que creará las listas será la siguiente:

= select_tag :selectfrom, options_for_select(@lista_elementos.map{|x| [x[:nombre], x[:codigo]]}), :multiple => true, :size => 15

.buttons_containers
  .buttons
    %button#btn-add{:type => "button"}
      = '→'.html_safe
    %br
    %button#btn-remove{:type => "button"}
      = '←'.html_safe

= select_tag :selectto, nil, :multiple => true

.buttons_containers
  .buttons
    %button#btn-up{:type => "button"}
      = '↑'.html_safe
    %br
    %button#btn-down{:type => "button"}
      = '↓'.html_safe


Esto creará las listas, que dando los estilos adecuados, no quedarán así:



Ahora tenemos que meter la funcionalidad, para poder pasar los elementos de una lista a la otra con los botones del medio:

  $('#btn-add').click(function(){
    $('#selectfrom option:selected').each( function() {
      $('#selectto').append("");
      $(this).remove();
    });
  });
  $('#btn-remove').click(function(){
    $('#selectto option:selected').each( function() {
      $('#selectfrom').append("");
      $(this).remove();
    });
  });


Así pasaremos de una lista a otra todas las opciones que estén seleccionadas. Para introducir la ordenación de la lista de la derecha:

  $('#btn-up').bind('click', function() {
    $('#selectto option:selected').each( function() {
      var newPos = $('#selectto option').index(this) - 1;
      if (newPos > -1) {
        $('#selectto option').eq(newPos).before("");
        $(this).remove();
      }
    });
  });

  $('#btn-down').bind('click', function() {
    var countOptions = $('#selectto option').size();
    $('#selectto option:selected').each( function() {
      var newPos = $('#selectto option').index(this) + 1;
      if (newPos < countOptions) {
        $('#selectto option').eq(newPos).after("");
        $(this).remove();
      }
    });
  });


Y así podremos subir o bajar elementos:



Si finalmente queremos tratar los elementos seleccionados para realizar alguna acción, como por ejemplo enviar a otra página, lo podemos hacer con un bucle de JQuery:

  $('#btnsumbit').click(function(){
    $('#selectto option').each(function(index, value){
      alert($(this).val());
    })
  });

Este bucle each de JQuery recorre todos los elementos del select final.

lunes, 21 de enero de 2013

JAVA: distintos componentes en JPanel

A continuación vamos a ver la manera de sacar, fácil y rápidamente, diversos componentes en un JPanel, que saldrán a modo de diálogo. Vamos a usar instancias de la clase JOptionPane.

Cuadro de texto


Para mostrar un cuadro de texto, y almacenar el input en una variable, simplemente haremos:

String name = JOptionPane.showInputDialog(new JFrame(),
    "Element Name", "");


Y en la variable name tendremos el texto introducido.

Opciones tipo Si/No/Cancelar


En este caso, damos al usuario dos o tres opciones. Podrá elegir una pulsando el botón adecuado:

Object[] options = {"opcion A", "opcion B", "opcion C"};
int opt = JOptionPane.showOptionDialog(new JFrame(), "Type", "Select option type",
  JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
  options, options[1]);

El entero que recibimos en opt será el índice de la opción seleccionada.

Spinner numérico


En esta ocasión, el usuario verá un spinner donde podrá elegir un valor numérico:


SpinnerModel sm = new SpinnerNumberModel(0,0,360, 25);
JSpinner jsp = new JSpinner(sm);
JOptionPane.showOptionDialog(null, jsp, "Number of cars per hour", JOptionPane.CLOSED_OPTION, JOptionPane.CLOSED_OPTION, null, null, null);
El valor retornado lo tendremos en sm.getValue();



Lista de opciones desplegable

Aquí, mostramos un desplegable con las opciones que puede seleccionar el usuario:


Object[] speedOptions = {"30","50","70","90","100","120"};

Object seleccion = JOptionPane.showInputDialog(
  new JFrame(),
     "Select max speed",
     "Options",
     JOptionPane.QUESTION_MESSAGE,
     null,  
     speedOptions, 
     speedOptions[4]);


En el objeto de retorno seleccion obtendremos el valor del objeto del Array seleccionado.



Más Información en chuwiki: JOptionPane y diálogos modales.

martes, 15 de enero de 2013

Ruby: Funciones en el valor de una Hash

Una Hash esta formada por pares clave-valor. en ocasiones, necesitaremos quealguno de esos valores sea una llamada a un método. Pongamos, por ejemplo, Time.now.

Si creamos la hash directamente con la llamada....

 hTime = {:a => Time.now}
 => {:a=>2013-01-15 10:45:58 +0100}

Cada vez que volvamos a acceder al elemento :a obtendremos lo mismo. No repite la llamada al método:

 
 hTime[:a]
 => 2013-01-15 10:45:58 +0100

La solución para  cuando queremos que el método se ejecute cada vez, es introducir una función lambda, con lo que podremos ejecutar el contenido del valor de la hash cuando queramos:

 hTime = {:a => lambda {Time.now}}
 => {:a=>#} 

Ahora, al hacer la llamada con la ejecución del procedimiento lambda:

 
hTime[:a].call
 => 2013-01-15 10:54:11 +0100

Si la repetimos, comprobamos que nuestra función se está llamando de nuevo:
 
hTime[:a].call
 => 2013-01-15 10:55:19 +0100

Un mini-ejemplo aplicado. Tenemos una hash. Uno de sus miembros es un array de numeros. El otro
miembro lo usaremos para saber si la suma del array es par.

 
hNumeros = {:nums => [1,2,3], :total_par => lambda{ hNumeros[:nums].sum.even?}}

hNumeros[:total_par].call
 => true 

Modificamos el array...
 
 hNumeros[:nums] << 7
 => [1, 2, 3, 7] 

hNumeros[:total_par].call
 => false 

Y de esta forma, realizamos la llamada a métodos y funciones desde el valor de una Hash

viernes, 11 de enero de 2013

Ordenar Arrays en Ruby

Para ordenar un Array en Ruby, hay varios métodos.

Si tenemos un array normal, de enteros, por ejemplo:

Con el método sort se realiza el orden por defecto:

[1,2,56,8,6, 12].sort
 => [1, 2, 6, 8, 12, 56]

Si queremos otro orden, hay que incluir un bloque a la llamada. Por ejemplo, primero los impares:

 [1, 5, 7, 2, 56, 8, 3, 6, 12].sort{|x,y| y%2 <=> x%2}
 => [1, 5, 7, 3, 56, 8, 2, 6, 12] 


En este caso, salen primero los impares y luego los pares. Pongamos que queremos hacer esto, pero además, que salgan los números ordenados. Es decir, primero por par/impar, y luego por tamaño. Para ordenar por varios campos:

[1, 5, 7, 2, 56, 8, 3, 6, 12].sort{|x,y| [x%2, x] <=> [y%2, y]}
 => [2, 6, 8, 12, 56, 1, 3, 5, 7] 

En el caso de que en lugar de un array normal como el anterior, tuviéramos un array de objetos, de hash o de cualquier tipo de dato, la operación es similar:

[{:a=>1, :b=>"a"}, {:a=>11, :b=>"f"}, {:a=>34, :b=>"ah"}, {:a=>154, :b=>"dfg"}, {:a=>323, :b=>"tre"}, {:a=>16, :b=>"mh"}].sort{|x,y| x[:a] <=> y[:a]}
 => [{:a=>1, :b=>"a"}, {:a=>11, :b=>"f"}, {:a=>16, :b=>"mh"}, {:a=>34, :b=>"ah"}, {:a=>154, :b=>"dfg"}, {:a=>323, :b=>"tre"}] 


De la misma manera, con varios campos:

 [{:a=>1, :b=>"a"}, {:a=>11, :b=>"f"}, {:a=>34, :b=>"ah"}, {:a=>154, :b=>"dfg"}, {:a=>323, :b=>"tre"}, {:a=>16, :b=>"mh"}].sort{|x,y| [y[:b], x[:a]] <=> [x[:b], y[:a]]}
 => [{:a=>323, :b=>"tre"}, {:a=>16, :b=>"mh"}, {:a=>11, :b=>"f"}, {:a=>154, :b=>"dfg"}, {:a=>34, :b=>"ah"}, {:a=>1, :b=>"a"}]

martes, 8 de enero de 2013

Crear un combo de selección en un formulario de Rails

Dentro de la poca complejidad que tiene hacer un formulario en Rails, quizá el combo de selección puede que sea el único con algo más de dificultad.

El resultado que queremos obtener es algo como:

<select id="persona" name="persona">
 <option>1</option> 
 <option>2</option>
 <option>3</option>
 <option>4</option>
</select>

Para esto, las opciones que tenemos son:

Meter el option directamente

Es decir, Html puro con el conjunto de los option, en una cadena, y escapándolo con html_safe. Funcionará, aunque en mi opinión no es una opción muy elegante.

Obtener una colección de opciones del modelo

Si el dato que queremos recuperar en el obtión es un objeto en sí mismo, lo correcto es formar a partir de este objeto el option.
select_tag "personas", options_from_collection_for_select(@personas, "id", "nombre")

De esta manera crearemos un 'select' con todos los objetos que haya en @personas, mostrando el nombre para la selección, y enviando el ID cuando se haga el submit.

Array con los valores


Para el caso en que simplemente queramos enviar un array con valores:

select_tag :personas, options_for_select((1..10).to_a, nil)


El primer parámetro es el array de valores. Si es un array donde cada elemento , a su vez, es un array de dos elementos, el segundo es el valor, y el primero el que se muestra.

El segundo parámetro determina el valor que estará seleccionado por defecto.

viernes, 4 de enero de 2013

CookieOverflow y las sesiones de Rails

Si al acceder a nuestra aplicación en Rails, nos da el error:

ActionDispatch::Cookies::CookieOverflow error

Es muy posible que se deba a que almacenamos en sesión más datos de los permitidos (4Kb) . Esto pasa si usamos la cookie_store de Rails.

Si vamos a necesitar almacenar datos en la sesión de usuario, lo mejor es hacerlo en base de datos. La modificación es muy sencilla. Se crean las tablas de sesión con el generador de Rails y se cambia el
session_store para que use active_record_store.

Ejecutamos:
rake db:sessions:create
rake db:migrate
Y en el fichero 'config/initializers/session_store.rb':

MyApp::Application.config.session_store :cookie_store, key: '_myapp_session'

Así nos evitaremos estos problemas de espacio, ya que generalmente hay bastantes cosas que conviene tenerlas en sesión para una mayor agilidad en la aplicación.

miércoles, 2 de enero de 2013

Deshacer una migración en ruby

En ocasiones, cuando realizamos una migración en Ruby, nos damos cuenta de que nos hemos dejado algo. Un tipo de dato mal, un nombre que no es el adecuado, ...

Para deshacer el comando migrate, simplemente ejecutamos:

rake db:rollback

Y desharemos la última migración realizada.

Un detalle importante, que guarda relación con esto y que puede llevar a errores en algunos casos:

Cuando una migración tiene varias partes, como por ejemplo, cambios en varias tablas, y hay un error al realizar una de ellas, no se guardará la ejecución del comando migrate.

Por ejemplo, en una migración cambiamos primero el nombre de un campo en una tabla y en segundo lugar el tipo de dato del campo de otra tabla. Pongamos que, por error, hemos escrito mal el nombre del segundo campo: en este caso, modificaremos el nombre del campo de la primera tabla, y al realizar el cambio del tipo de dato nos dará un error, pues este campo no existe.

Lo típico será ir al fichero de migración y corregir el nombre mal escrito, para ejecutar de nuevo la migración. Nos volverá a dar error, pues el primer campo si fue modificado, y el nombre ya no coincide con el de la migración.
En este punto, no servirá un rollback!, puesto que la migración no consta como realizada. Las posibles soluciones son: modificar directamente las tablas en base de datos o bien modificar momentáneamente la migración para que no haga la primera parte.
Para prevenir esto, lo correcto sería no agrupar migraciones que se vayan a realizar en varios pasos en el mismo fichero.