Dagi3d v4

Validando las asociaciones de ActiveRecord con RSpec

Después de estar un tiempo sin tocar Rails para nada, me puse el otro día a cacharrerar un poco con RSpec y se me ocurrió escribir un matcher para validar las asociaciones de los modelos, ya que consideraba que un simple @objeto.should respond_to(:metodo) realmente tampoco garantiza nada.
Lo único 'interesante' que puede aportar este código es que a la hora de escribir nuestros specs, basta con poner directamente el nombre de la relación, ya que la clase asociada se obtiene de manera automática(bendita sea la convención sobre la configuración face-smile.png), al contrario que el resto de ejemplos que pude encontrar por ahí, donde se indica la clase y si se desea, se indica aparte el nombre de la relación(y creo que así se aporta algo de legibilidad a los specs) :

@record.should have_many(:songs) # utiliza la clase Song

@record.should belong_to(:artist) # utiliza la clase Artist­

@record.should have_one(:cover) # utiliza la clase Cover

Si fuese necesario también se puede indicar de manera manual la clase del modelo relacionado:

@record.should have_many(:favorite_songs).from_class(Song)
module ARAssociationsMatchers
  
  # ARAssociationMatcher
  #
  class ARAssociationMatcher
  
    def initialize(expected, macro)
      @expected_association = expected
      @expected_macro = macro
    end
    
    def matches?(target)
      @target = target
      
      unless @expected_class.nil?
        expected_class = @expected_class
      else
        expected_class_name = @expected_association.to_s.singularize.camelize
        expected_class = Kernel.const_get(expected_class_name)
      end
      
      reflection = target.class.reflect_on_association(@expected_association)
      
      !reflection.nil? && (reflection.macro == @expected_macro) && (reflection.klass == expected_class)
    end
    
    def from_class(expected_class)
      @expected_class = expected_class
      self
    end
    
    def failure_message
      "expected #{@target.inspect} to #{@expected_macro} #{@expected_association.inspect}, but it didn't"
    end
    
    def negative_failure_message
      "expected #{@target.inspect} not to #{@expected_macro} #{@expected_association.inspect}, but it didn't"
    end
    
  end
  
  # matchers functions
  #
  def have_many(expected)
    ARAssociationMatcher.new(expected, :has_many)
  end

  def have_one(expected)
    ARAssociationMatcher.new(expected, :has_one)
  end
  
  def belong_to(expected)
    ARAssociationMatcher.new(expected, :belongs_to)
  end
  
end
La idea inicial está tomada de ­este enlace

Añadiendo nuevos tipos en las migraciones de Rails

En el proyecto con el que ando liado en mis r­atos libres, necesitaba añadir a varios modelos atributos que almaceneran decimales. En principio era tan simple como crear en cada migración las columnas con su tipo de dato correspondiente:

t.column :price, :precision => 6, :scale => 2, :default => nil

El caso es ­que se  me hacía un tanto repetitivo estar añadiendo la misma línea en todas las migraciones donde me hacía falta(sé que no es para tanto, pero a veces la vagancia me puede) y además no estaba usando el estilo de los nuevos atajos que trae Rails 2.0, así que la solución era tan sencilla como reabrir la clase ActiveRecord::ConnectionAdapters::TableDefinition(bendito Ruby) y crear el método necesario:

class ActiveRecord::ConnectionAdapters::TableDefinition
  
  def currency(*columns)
    columns.each do |column|
      self.column column, :decimal, :precision => 6, :scale => 2, :default => nil
    end
  end
  
end­
­

Y ya podía usar en todas mis migraciones el método t.price teniendo que indicar únicamente el nombre de la columna(o columnas) que quería crear:

class CreateLineItems < ActiveRecord::Migration
  def self.up
    create_table :line_items do |t|
      t.references :order
      t.references :item, :polymorphic => true
      t.currency :price
      t.timestamps
    end
  end

  def self.down
    drop_table :line_items
  end
end

Charla/Taller de introducción a Ruby On Rails

Bajo el marco de las III Jornadas de Informática organizadas en la Universidad Europea de Madrid que se celebran los días 15 y 16 de noviembre, Raúl Murciano y yo impartiremos una charla-taller de introducción a Ruby On Rails. El taller tendrá lugar el jueves 15 y comenzará a las 12:00 y finalizará sobre las 14:00.
El acceso es totalmente libre y gratuito(al igual que el resto de charlas y eventos de las jornadas) y animo a que se acerque a todo aquel que quiera iniciarse en este estupendo framework de desarrollo web.

Programa de las jornadas
Cómo llegar a la UEM

Programa de facturación en Ruby On Rails

Hace poco para optar a un trabajo tuve que realizar como prueba una aplicación en Rails que gestionase un listado de facturas junto a sus clientes. El caso es que decidí añadirle alguna cosilla más y hacerle una interfaz más aceptable y liberar el código por si a alguien le podía interesar.
La aplicación puede exportar a pdf(aunque hace falta java para esto) y el diseño del pdf es totalmente personalizable a partir de un documento xhtml y css.
De momento no tiene mucha cosa, pero la idea es utilizarlo e irlo ampliando conforme lo vaya necesitando ahora que empiezo con el tema del freelanceo.

Se puede ver en funcionamiento en http://facturails.dagi3d.net/ y se puede descargar directamente desde el repositorio subversion en http://svn.dagi3d.net/rails/facturails/trunk (bajo licencia MIT)

Sumando varios elementos de un array

Realizando una aplicación en Rails necesitaba sumar varios atributos de los objetos almacenados en un array. El problema es que cuando se trata de sumar un único atributo, se suele utilizar el método inject:

total = mi_array.inject { |sum, obj| sum + obj.value }

pero en este caso no me terminaba de convecer estar llamando al método inject tantas veces como atributos quisiera sumar, así que en teoría la solución pasaría por iterar sobre el array e ir sumando:

var1 = 0
var2 = 0
var3 = 0
...
mi_array.each do |obj|
  var1 += obj.value1
  var2 += obj.value2
  var3 += obj.value3
  ...
end

Funcionar, funcionaba, pero digamos que el código quedaba algo feo, así que para seguir trasteando, intenté encontrar una solución más 'ruby' y esto fue lo que hice:

class Array
  
  def accumulate(fields)
    
    results = fields.dup
    
    self.each do |obj|
      fields.each_key do |key|
        results[key] += obj.send(key) if obj.respond_to?(key)
      end
    end
    
    results
  end
end

Ahora bastaba con llamar al método accumulate sobre el array de objetos e indicar qué atributos quería sumar para obtener un hash con los resultados:

# la lista de objetos
foos = [
  OpenStruct.new(:foo => 1, :bar => 2, :foobar => 3),
  OpenStruct.new(:foo => 3, :bar => 4, :foobar => 5),
  OpenStruct.new(:foo => 5, :bar => 6, :foobar => 7)
]

foos.accumulate(:foo =>­; 0, :bar => 0, :foobar => 0) 
# {:foo=>9, :bar=>12, :foobar=>15}­