Pregunta

Estoy corriendo en algún comportamiento Rails 2.3.5 ActiveRecord que no entiendo. Parece ser que un objeto puede tener sus identificadores de asociación actualizan de manera inconsistente.

Esto se explica mejor con un ejemplo:

Crear un modelo Post con el atributo de cadena 'title' y un modelo Comment con la cadena atribuyen 'content'.

Aquí están las asociaciones:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

Escenario # 1: En el siguiente código se crea una Post con un Comment asociada, crear una segunda Post por find'ing el primero, añadir un segundo Comment a la primera Post y descubrir que la segunda Post tiene la segunda Comment asociado a ella sin una asignación explícita.

post1 = Post.new
post1 = Post.new(:title => 'Post 1')
comment1 = Comment.new(:content => 'content 1')
post1.comments << comment1
post1.save
# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1')
# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2')
post1.comments << comment2 
# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [12, 13]
post2.comment_ids # => [12, 13]

Escenario # 2: Ejecutar los comandos anteriores, pero esta vez insertar un comando adicional que, en vista de ello, no debería afectar a los resultados. El comando adicional es que se produce post2.comments después comment2 crear y antes añadir a comment2 post1.

post1 = Post.new
post1 = Post.new(:title => 'Post 1A')
comment1 = Comment.new(:content => 'content 1A')
post1.comments << comment1
post1.save
# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1A')
# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2A')
post2.comments # !! THIS IS THE EXTRA COMMAND !!
post1.comments << comment2 
# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [14, 15]
post2.comment_ids # => [14]

Tenga en cuenta que sólo hay un comentario asociado post2 en este escenario, mientras que en el escenario 1 había dos.

La gran pregunta: ¿Por qué correr post2.comments antes de añadir el nuevo Comment a post1 hace ninguna diferencia a la que se asociaron con comentarios post2?

¿Fue útil?

Solución

Esto tiene que ver con la forma en que Active Record almacena en caché las solicitudes y la forma en que se manejan las asociaciones has_many.

A menos que la asociación está eagerloaded con un: incluyen opción durante el hallazgo. Raíles no rellenará la asociación de los registros encontrados hasta que se necesite. Cuando se necesita la asociación alguna memoization se hace para reducir el número de consultas SQL ejecutadas.

Al entrar a través del código en la pregunta:

post1 = Post.new(:title => 'Post 1')
comment1 = Comment.new(:content => 'content 1')
post1.comments << comment1  # updates post1's internal comments cache
post1.save 

# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1') 

# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2')
post1.comments << comment2   # updates post1's internal comments cache

# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [12, 13]

# this is the first time post2.comments are loaded. 
# SELECT comments.* FROM comments JOIN comments.post_id = posts.id WHERE posts.id = #{post2.id}
post2.comment_ids # => [12, 13]

Escenario 2:

post1 = Post.new(:title => 'Post 1A')
comment1 = Comment.new(:content => 'content 1A')
post1.comments << comment1
post1.save

# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1A')

# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2A')

# first time post2.comments are loaded. 
# SELECT comments.* FROM comments JOIN comments.post_id = posts.id WHERE 
#   posts.id = post2.comments #=> Returns one comment (id = 14)
# cached internally.

post1.comments << comment2 
# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [14, 15]

# post2.comment has already been cached, so the SQL query is not executed again.

post2.comment_ids # => [14]

N.B. post2.comment_ids se define internamente como post2.comments.map(&:id)

P.S. Mi respuesta a esta pregunta podría ayudar a entender por qué post2 se actualiza a pesar de su no tocarlo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top