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
Deja un comentario
*: campos obligatorios. La dirección de correo no será publicada