rspec testing has_many: through and after_save
Tengo un (creo) relativamente sencillohas_many :through
relación con una tabla de unión:
class User < ActiveRecord::Base
has_many :user_following_thing_relationships
has_many :things, :through => :user_following_thing_relationships
end
class Thing < ActiveRecord::Base
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
end
class UserFollowingThingRelationship < ActiveRecord::Base
belongs_to :thing
belongs_to :user
end
Y estas pruebas rspec (sé que estas no son necesariamente buenas pruebas, solo son para ilustrar lo que está sucediendo):
describe Thing do
before(:each) do
@user = User.create!(:name => "Fred")
@thing = Thing.create!(:name => "Foo")
@user.things << @thing
end
it "should have created a relationship" do
UserFollowingThingRelationship.first.user.should == @user
UserFollowingThingRelationship.first.thing.should == @thing
end
it "should have followers" do
@thing.followers.should == [@user]
end
end
Esto funciona bien HASTA que agregue unafter_save
alThing
modelo que hace referencia a sufollowers
. Es decir, si lo hago
class Thing < ActiveRecord::Base
after_save :do_stuff
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
def do_stuff
followers.each { |f| puts "I'm followed by #{f.name}" }
end
end
Entonces la segunda prueba falla, es decir, la relación todavía se agrega a la tabla de unión, pero@thing.followers
devuelve una matriz vacía. Además, esa parte de la devolución de llamada nunca se llama (como sifollowers
está vacío dentro del modelo). Si agrego unaputs "HI"
en la devolución de llamada antes de lafollowers.each
line, el "HI" aparece en stdout, así que sé que se está llamando la devolución de llamada. Si comento elfollowers.each
line, luego las pruebas pasan de nuevo.
Si hago esto a través de la consola, funciona bien. Es decir, puedo hacer
>> t = Thing.create!(:name => "Foo")
>> t.followers # []
>> u = User.create!(:name => "Bar")
>> u.things << t
>> t.followers # [u]
>> t.save # just to be super duper sure that the callback is triggered
>> t.followers # still [u]
¿Por qué está fallando esto en rspec? ¿Estoy haciendo algo terriblemente mal?
Actualiza
Todo funciona si defino manualmenteThing#followers
com
def followers
user_following_thing_relationships.all.map{ |r| r.user }
end
Esto me lleva a creer que quizás estoy definiendo mihas_many :through
con:source
incorrectamente
Actualiza
He creado un proyecto de ejemplo mínimo y lo puse en github:https: //github.com/dantswain/RspecHasMan
Otra actualización
Gracias a @PeterNixey y @kikuchiyo por sus sugerencias a continuación. La respuesta final resultó ser una combinación de ambas respuestas y desearía poder dividir el crédito entre ellas. He actualizado el proyecto github con lo que creo que es la solución más limpia y he introducido los cambios:https: //github.com/dantswain/RspecHasMan
Todavía me encantaría si alguien pudiera darme una explicación realmente sólida de lo que está sucediendo aquí. Lo más problemático para mí es por qué, en la declaración inicial del problema, todo (excepto la operación de la devolución de llamada en sí) funcionaría si comentara la referencia afollowers
.