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.

Respuestas a la pregunta(6)

Su respuesta a la pregunta