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
), 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