Necesitas algo más flexible que Module#delegate de Rails, entonces Module#delegate_method

Rails a través de una extensión de la clase Module en ActiveSupport permite delegar métodos de manera comoda. Veamos un ejemplo, para entenderlo:

class A
  def a
    'a'
  end 
  def aa
    'aa'
  end
end

class B
  attr_accessor :the_a

  def initialize(the_a)
    @the_a = the_a
  end  

  delegate :a, :to => :the_a
end

the_b = B.new(A.new)
the_b.a    # => "a"

Interesante, pero con limitaciones. Probemos otras posibiliades:

class B
  delegate :a2, :to => :the_a
end

the_b = B.new(A.new)
the_b.a2  # => NoMethodError: undefined method `a2' ...

the_b = B.new(nil)
the_b.a     # => NoMethodError: You have a nil object...
the_b.a2   # => NoMethodError: You have a nil object...

El código causante de este comportamiento tiene la siguiente pinta:

def delegate(*methods)
  options = methods.pop
  unless options.is_a?(Hash) && to = options[:to]
    raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
  end

  methods.each do |method|
    module_eval("def \#{method}(*args, &block)\n\#{to}.__send__(\#{method.inspect}, *args, &block)\nend\n", "(__DELEGATION__)", 1)
  end
end

Esto no nos agrada del todo, ya que lo interesante sería que se pudiesen escribir cosas como las siguientes sin preocuparse:

p1.product.name   # => 'FypURL'
p1.product.brand.name   # => "Trabe"
p1.product.brand_name   # => "Trabe"
p2.product.name   #  =>  "Unbranded product"
p2.product.brand.name   # =>  NoMethodError: You have a nil object...
p2.product.brand_name   # => "unknown"

Aqui os pongo nuestro Module#delegate_method que permite todo esto:

class Module

  def delegate_method(method, options)    

    to,to_method = options[:to].to_s.split('.')
    to_method = method if to_method.nil?
    default = options[:default]

    code = %{
      def #{method}(*args, &block)
        if #{to}        
           #{to}.__send__(#{to_method.inspect}, *args, &block) || #{default.inspect}
        else 
           #{default.inspect}
        end
      end
      }

  module_eval code, __FILE__, __LINE__    
  end 
end
Esta versión se diferencia de Module#delegate_method anterior en lo siguiente:
  • No reescribimos el método original ya que la implementación no es compatible al 100% con la anterior. No soportamos la definición de multiples delegaciones y no queremeos que código ya escrito deje de funcionar. Si queremos delegar del modo Rails seguimos teniendo Module#delegate. La posibilidad de definir varias delegaciones en una sola sentencia no es una característica fundamental (para delegar muchos métodos a lo mejor compensa trastear con el method_missing).

  • No chequeamos la existencia del parametro :to (presuponemos que vamos a escirbir correctamente el código y si no es asís ya saltará otra expceción.
  • Permitimos delegar en un método diferente al método delegado. Esto es super útil como se vió en el ejemplo de productos y marcas.

  • Si no existe el delegado o éste no tiene valor obtenemos nil y no una sucia excpeción, o lo que es mejor, obtenemos un valor por defecto.

Probemos de nuevo el código con el que abrimos el post sutituyendo delegate por delegate_method

class B
  ... 
  delegate_method :a, :to => :the_a, :default => 'default'
  delegate_method :aa, :to => 'the_a.aa'
end

the_b = B.new(A.new)
the_b.a       # => "a"
the_b.aa     # => "aa"

the_b = B.new(nil)
the_b.a      # => default
the_b.aa    # => nil

Extensiones al core de Ruby, copia profunda de objetos con el api de marshalling (serializando, vamos)

Una necesidad típica a la hora de programar es copiar o clonar objetos. Los métodos de Ruby Object#clone y Object#dup realizan copias superficiales (shallow), es decir, si el objeto contiene referencias a otros objetos se duplican las referencias pero no los objetos apuntados. Si la copia fuese profunda (deep), también se duplicarían los objetos referenciados. Ejemplo:

h = { 1 => ['a','b'], 2 => ['c'] }
h1 = h.clone
h2 = h.deep_clone
h1[1].shift    # => "a"
h   # =>  {1=>["b"], 2=>["c"]}
h1  # =>  {1=>["b"], 2=>["c"]}
h2  # =>  {1=>["a", "b"], 2=>["c"]}

Lo primero que se nos puede ocurrir es crear un método deep_clone específico para cada objeto (o incluso uno más genérico que, mediante introspección del objeto supiese que copiar). Ésto es tedioso y tiene sus problemas (básicamente el meanejo de referencias cruzadas), además, eso no es DRY. Por fortuna Ruby tiene soporte para serialización (módulo Marshal). El truco es sencillo: serializo, deserializo.

class Object
  def deep_clone
    # Abracadabra !!!
    Marshal::load(Marshal::dump(self))
  end
end

Sencillo, bello, y todas esas cosas que tanto gustan de Ruby.

A esta técnica sólo podemos ponerle dos pegas: 1) si una parte de la clase no es serializable no podremos copiarla, y 2) es cierto que una implementación ad hoc de copia puede ser más rápida (pero yo, al menos, estoy dispuesto a perder esa velocidad).

PD: Por cierto, esta técnica tan sencilla es trasladable directamente a cualquier otro lenguaje/plataforma con soporte para serialización: Java, .NET, etc.

Ponencia de Trabe Soluciones aceptada en la Conferencia Rails 2007

Ayer, la organización de la Conferencia Rails 2007 me confirmó la inclusión de una de nuestras ponencias en el programa de las conferencias. Podéis ver la relación completa en la dirección http://ponencias.conferenciarails.org.

De las dos propuestas que enviamos aceptaron una que titulamos Proyectos de bajo coste con limitaciones severas de tiempo y recursos y dejaron fuera otra de APIs Rest y proxificación, lo cual me ha sorprendido bastante. Sin quitarle interés a la primera ponencia, creo que la segunda era mucho más interesante y amena (mucho más tecnológica y menos empresarial). En cualquier caso nos vemos en la Conferencia Rails en Noviembre.

PD: Aunque el que escribe aparece como ponente en la página web, debo decir que este punto no está confirmado y puede dar la charla cualquier otro miembro de Trabe. Más adelante os confirmo a quién le podéis tirar los tomates el día de la charla.

Symbol#to_proc de rails y otras alternativas (o locuras, según se mire)

Este post es consecuencia directa de un comentario de Sergio Gil a mi post anterior: Extensiones al core de Ruby, pequeños pedazos de código que harán tu vida más sencilla. Hoy Enumerable.pluck y Enumerable.invoke.

Sergio, acertadamente comenta:

ActiveSupport añade a each y a collect/map una versión abreviada de la sintaxis (realmente lo que hace es definir Symbol#to_proc, si no me equivoco) que la deja muy parecida a lo que habéis creado, que dejaría el ejemplo así:
objects.collect(&:key)

a lo que nosotros respondemos:

Tienes razón Sergio, existe esa posibilidad, aunque aquí en Trabe consideramos que es una construcción un poco oscura. Por otro lado la versión que utilizamos nosotros, permite hacer alguna cosa adicional fácilmente, por ejemplo, utilizar una cadena como parámetro en lugar de un símbolo (que puede considerarse algo menor) o más interesante, pasar parámetros a la función que se invoca (que como pone el post, queda como ejercicio para el lector ;)
Ejemplo práctico:
%w(un ejemplo).pluck(’*’,2) => [“unun”, “ejemploejemplo”]

Aprovecho ahora para resolver el ejercicio de programación que os propuse:

module Enumerable
  def invoke(method_name, *args)    
    self.each { |i| i.send(method_name, *args) }
  end
  def pluck(method_name, *args)    
    self.collect { |i| i.send(method_name, *args) }
  end  
end

Hasta aquí todo bien. Lo que pasa, es que cuando se escribe en un blog y se reciben comentarios uno se ve obligado a realizar un ejercicio mental y reflexionar mucho sobre lo que escribe. Así que me he tomado un ratito para investigar algo más sobre este tema.

La primera carencia que apuntamos acerca de la solución con Symbol#to_proc es fácilmente superable, sólo necesitamos definir nuestro propio String#to_proc. La falta de parámetros en el método es algo que me llama especialmente la atención. Estuve haciendo unas pruebas en la consola, y no conseguí pasar nigún parametro, así que me fui al código fuente y esto es lo que pone:

def to_proc
  Proc.new { |*args| args.shift.__send__(self, *args) }
end

Dado que no soy un gurú ni un semidios de Ruby no puedo afirmar esto con rotundidad, pero, a primera vista parece que to_proc devolvería un Proc capaz de recibir un número variable de argumentos.

Ante la duda, Google, porque no estamos sólos en elmundo.

Lo que descubrí, a parte de confirmar que Symbol#to_proc no puede recibir argumentos, es la Methodphitamine (algo así como la método-fetamina), una suerte de locura sintáctica que permite hacer algo como:

(1..100).select &it % 2 == 0
User.find(:all).map &its.contacts.map(&its.last_name.capitalize)

¡Qué locura!, ¿Efectos de la droga?, ¿Tendrá este hombre que mear en un vaso y pasar un anti-dopping? en fin. Yo, personalmente, comulgo con un comentario de Sam Aaron aparecido en The Methodphitamine: A Better Symbol#to_proc?.

I’m not a big fan of the to_proc hack in the first place. To my eyes, it’s ugly, and Methodphitamine is even more ugly. I believe that there’s a balance to be struck between readability and succinctness. I think that the Ruby philosophy (or way) leans more towards the readability side of the spectrum. Check out Matz’s essay in the new O’Reilly book ‘Beautiful Code’ where he likens code to an essay. i.e. essays generally have messages, but the message is mostly meaningless unless it can be read and understood by other people. It’s a good read.

Beautiful Code es un libro que tengo en casa en la cola de lectura, cuando le eche un vistazo os comento.

De todos modos, quién este interesado en este tipo de syntactic sugar también puede echarle un ojo a Array#to_proc – Nested properties for to_proc hack, que es una solución para el encadenamiento más legible y en la línea de Symbol#to_proc o bien New magical version of Symbol.to_proc que permite escribir algo como list.map &:to_i elegantemente como list.to_is con una extensión que sólo puede considerarse como “code hell”.

Extensiones al core de Ruby, pequeños pedazos de código que harán tu vida más sencilla. Hoy Enumerable.pluck y Enumerable.invoke

En Trabe Soluciones vamos mejorando las librerías que utilizamos para desarrollar con Rails aplicación a aplicación. Puede decirse que somos como una esponja que va cogiendo buenas ideas de aquí y de allá. Hoy vamos a hablar de una par de extensiones al core de Ruby que utilizamos,

Primero el código:

module Enumerable
  def invoke(method_name)    
    self.each { |o| o.send(method_name) }
  end    
  def pluck(method_name)    
    self.collect { |o| o.send(method_name) }
  end  
end

Cómo podeís apreciar son unas sencillas funciones que solucionan dos tareas más que habituales cuando trabajamos con colecciones: invoke que invoca un método de cada elemento de un Enumerable y pluck que recolecta el resultado de invocar un método concreto sobre cada elemento de un Enumerable. Vamos, que abrevian dos construcciones típicas del lenguaje. Las hemos definido en el módulo Enumerable y no en la clase Array directamente para poder utilizarlas con cualquier clase que incluya este módulo (Array por ejemplo).

La inspiración de estas funciones se la debemos a Prototype, la famosa librería Javascript, que curiosamente se inspira a su vez en ruby:

Most of Prototype’s idioms are influenced by Ruby, and those that have no analog in Ruby defer to existing or emerging web standards

Para terminar un ejemplo autoexplicativo de su utilidad.

# ANTES
def find_by_object_keys(objects)
  find(:conditions => { :key => objects.collect { |o| o.key} }
end

# DESPUES
def find_by_object_keys(objects)
  find(:conditions => { :key => objects.pluck(:key) }
end

Ejercicio para lectores avispados: Ahora sólo tenéis que modificar las funciones para que acepten parámetros para el método invocado. Seguramente lo necesitéis en algún momento.

Rails plugins (2): Annotate Models. Documenta tus modelos ActiveRecord automáticamente

El plugin que recomendamos hoy es annotate_models. Esta pequeña extensión ni modifica ni añade comportamiento a nuestras aplicaciones Rails y tampoco nos evita escribir código. No obstante, no debemos despreciar su valiosa ayuda.

annotate models añade una simpática tarea annotate_models al rakefile del proyecto que se ocupa de añadir al comienzo de los ficheros .rb que contienen modelos ActiveRecord un comentario con la información del schema. Esto es, nos anota los modelos con los atributos de la tabla de base de datos asociada al modelo. Veamos una anotación real como ejemplo:

# == Schema Information
# Schema version: 34
# Annotated on: Wed Oct 10 10:38:58 +0200 2007
#
# Table name: brands
#
#  id         :integer(11)   not null, primary key
#  name_es    :string(255)   
#  name_en    :string(255)   
#  created_at :datetime      not null
#  updated_at :datetime      not null
#  image      :string(255)   
#  complete   :boolean(1)    
#  url        :string(255)
#  margin     :integer(11)   
#

Pequeño pero matón. Una gran ayuda mientras el soporte de los IDE de desarrollo como Netbeans o RadRails no mejore y autocompleten como Dios manda.

Sólo dos pegas (pequeñas, sí, pero ahi están). La primera: siempre anota los modelos, aun cuando no ha cambiado el esquema. Si trabajamos con un control de versiones (porque trabajamos con control de versiones ¿no?) el histórico de revisiones queda un pelín falseado, porque los ficheros han cambiado (esa línea Annotated on maldita), aunque no hayamos modificado el código.

La segunda: sólo anota los modelos de la carpeta model, cuando lo óptimo es que buscase por toda la carpeta del proyecto buscando subclases de ActiveRecord::Base. Si tenéis modelos en otra carpeta tendréis que modifcar el código fuente del plugin (que nadie se asuste, es bastante sencillo).

Resumiendo: anotad vuestros modelos

Este es el segundo post de la fabulosa serie “Rails Plugins”

Pretty URLs mediante reescritura en aplicaciones JEE (J2EE) sin depender de Apache

Este es uno de esos artículos "de propósito específico". Supongo que los que trabajen en entornos "con mucha burocracia" entenderán mejor por qué no siempre es posible tocar el apache. La idea de este post es ver como podemos tener una funcionalidad decente de reescritura de URLs directamente en el contenedor de aplicaciones.

La pieza clave para conseguir nuestro objetivo es UrlRewriteFilter, que como sus propios autores proclaman "Es un filtro basado en mod_rewrite válido para cualquier contenedor estándar de aplicaciones JEE que permite reescribir URLs antes de que lleguen a nuestro código". Y además funciona.

El filtro es muy sencillo de instalar, tan solo son necesarios unos pocos pasos muy sencillos:

  • Guardar el jar en el lib de nuestra aplicación.
  • Modificar el web.xml para que haga uso del filtro:
<filter>
  <filter-name>UrlRewriteFilter</filter-name>
  <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
  <init-param>
    <param-name>logLevel</param-name>
    <param-value>WARN</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>UrlRewriteFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
  • Incorporar en el WEB-INF de nuestra aplicación un fichero urlrewrite.xml, que contendrá las reglas para las reescrituras

Una vez realizados estos pasos solo nos queda escribir las reglas que consideremos necesarias en nuestro caso. El fichero urlrewrite.xml de ejemplo incluye nociones básicas al respecto. A modo de ilustración, para convertir una URL como /searchPage.do?term=casco&locale=es&make=honda en algo como /es/honda/que-contengan/casco, necesitaríamos una regla como la siguiente:

<rule>
  <from>/([a-z]+)/([a-zA-Z0-9]+)/que-contengan/(.*)</from>
  <to>/searchPage.do?term=$3&amp;locale=$1&amp;make=$2</to>
</rule>

Con estos sencillos pasos ya tenemos un soporte decente para construir URLs bonitas en aplicaciones Java, sin tener que hacerlo todo "a mano" ni depender del Apache.

Extraer datos de un PLC (autómata) Siemens S7-200 utilizando un cable USB y Excel

Los autómatas programables son unos aparatos muy interesantes de los que aún no habíamos hablado nunca en este blog. Permiten controlar todo tipo de procesos más o menos complejos y almacenan información que luego puede ser interesante tratar en un PC. Sin embargo, muchas veces no resulta trivial hacer que esta información que maneja el autómata sea exportada a un PC. A la hora de extraer datos de la memoria de un autómata existen varias posibilidades.

Programar un protocolo específico

Esta opción es bastante laboriosa y presenta el problema de tener que reinventar la rueda en cada escenario de implantación. Sin embargo es la que da una mayor flexibilidad, y puede ser la única opción válida cuando nuestro PLC interactúa con otros dispositivos hardware

Utilizar una librería de comunicación

Existen en el mercado diferentes librerías para acceder de una forma simplificadada a los datos de un autómata. Estas librerías nos evitan tener que pelearnos con los protocolos de comunicación y nos presentan una vista de más alto nivel. Siemens dispone de sus propias librerías (de pago, como todo lo que tenga que ver con PLCs Siemens), pero existen otras opciones muy interesantes como la librería libnodave. Utilizar una librería es una opción muy razonable cuando necesitamos acceder a los datos del autómata desde nuestros programas y no queremos/no podemos utilizar un servidor OPC.

Utilizar un servidor OPC

Hace ya algún tiempo, la industria comenzó a demandar a los fabricantes de sistemas de automatización una cierta tasa de interoperabilidad. OPC permite controlar a través de un PC dispositivos de distintos fabricantes. En el caso de los S7-200, existe un software de Siemens denominado "PC Access" que viene siendo un "mini servidor" OPC. Utilizando este software es bastante sencillo acceder a los datos del PLC desde Excel utilizando VBA(Visual Basic for Applications). Dado que esta solución es estándar, sencilla y funciona, será la elegida en este caso.

Configurar el ordenador

Esta parte del problema es bastante sencilla. Basta con instalar el software de Siemens y seguir los pasos descritos para instalar el componente en excel. Una vez realizados correctamente estos pasos, tendremos una barra de botones adicional en Excel que nos permite iniciar/detener el refresco de datos. En este ejemplo no vamos a utilizar estas herramientas, pues recuperaremos los datos directamente desde código VBA

¿Qué datos vamos a recuperar?

Los autómatas de la serie 200 tienen varias "zonas" de memoria, pensadas para distintos propósitos. Hay una zona de flags, una zona de datos de usuario, etc. Todas las zonas son accesibles a través de OPC, tanto en modo lectura como en modo lectura/escritura (cuando procede). En este ejemplo vamos a recuperar 100 valores de un tamaño de un byte almacenados en la memoria V, entre las posiciones 0 y 99.

Para conseguir nuestro propósito tendremos que hacer un bucle en el que se vayan leyendo las posiciones necesarias e iremos almacenando los valores en la columna A de nuestra hoja de cálculo, de modo que en la la fila de número i tengamos almacenado el valor correspondiente a la variableVBi-1

Cómo lo hacemos

Muy sencillo. Una vez configurado el equipo abrimos una nueva hoja excel y cargamos el "complemento" de siemens tal como indica la documentación. Sobre una hoja en blanco creamos un botón y asociamos el siguiente código a su evento de click:

MsgBox "Comenzando el proceso de importación de datos"
For i=0 To 99
    parameters = "2,VB" & i & ",BYTE,R"
    sStr = Excel.Application.Run("OPCS7200ExcelAddin.XLA!OPCRead", parameters, "")
    Cells(i+1,1) = sStr
Next i
MsgBox "Proceso finalizado"

El código es tan sencillo que no merece mayor explicación. Únicamente comentar que en la linea en que se asigna valor a parameters se le indica al servidor OPC con que dispositivo queremos "hablar", que posición queremos leer, el tipo y si queremos utilizar la posición en modo lectura o lectura/escritura

Conclusiones

Hemos visto que los autómatas no son máquinas infernales con las que es imposible hacer cosas. Es fácil obtener datos de ellos si sabemos cómo hacerlo. Obviamente recuperar los datos es solo el principio del problema, pero es la parte que puede parecer más compleja cuando nos hablan en abstracto de este tipo de dispositivos.