ruby-on-rails - polimorficas - relaciones de tablas en ruby on rails
Estructurar una aplicación de Rails para el almacenamiento en caché de Muñeca rusa con una relación has_many (2)
Después de estudiar DHH y otros artículos de blog sobre la caducidad del caché basado en claves y el caché ruso de muñecas, todavía no estoy seguro de cómo manejar un tipo de relación. Para ser específico, una relación has_many
.
Compartiré los resultados de mi investigación en una aplicación de muestra. Es un poco de contar historias, así que espera. Digamos que tenemos los siguientes modelos de ActiveRecord. Todo lo que nos importa es un cambio adecuado de la cache_key
del modelo, ¿verdad?
class Article < ActiveRecord::Base
attr_accessible :author_id, :body, :title
has_many :comments
belongs_to :author
end
class Comment < ActiveRecord::Base
attr_accessible :article_id, :author_id, :body
belongs_to :author
belongs_to :article, touch: true
end
class Author < ActiveRecord::Base
attr_accessible :name
has_many :articles
has_many :comments
end
Ya tenemos un artículo, con un comentario. Ambos por un autor diferente. El objetivo es tener un cambio en la cache_key
para el artículo en los siguientes casos:
- Cambios de cuerpo o título del artículo
- Cambios en el cuerpo de su comentario
- El nombre del autor del artículo cambia
- El nombre del autor del comentario del artículo cambia
Entonces, por defecto, somos buenos para los casos 1 y 2.
1.9.3-p194 :034 > article.cache_key
=> "articles/1-20130412185804"
1.9.3-p194 :035 > article.comments.first.update_attribute(''body'', ''First Post!'')
1.9.3-p194 :038 > article.cache_key
=> "articles/1-20130412185913"
Pero no para el caso 3.
1.9.3-p194 :040 > article.author.update_attribute(''name'', ''Adam A.'')
1.9.3-p194 :041 > article.cache_key
=> "articles/1-20130412185913"
Definamos un método compuesto cache_key
para Article
.
class Article < ActiveRecord::Base
attr_accessible :author_id, :body, :title
has_many :comments
belongs_to :author
def cache_key
[super, author.cache_key].join(''/'')
end
end
1.9.3-p194 :007 > article.cache_key
=> "articles/1-20130412185913/authors/1-20130412190438"
1.9.3-p194 :008 > article.author.update_attribute(''name'', ''Adam B.'')
1.9.3-p194 :009 > article.cache_key
=> "articles/1-20130412185913/authors/1-20130412190849"
¡Ganar! Pero, por supuesto, esto no funciona para el caso 4.
1.9.3-p194 :012 > article.comments.first.author.update_attribute(''name'', ''Bernard A.'')
1.9.3-p194 :013 > article.cache_key
=> "articles/1-20130412185913/authors/1-20130412190849"
Entonces, ¿qué opciones quedan? Podríamos hacer algo con la asociación has_many
en Author
, pero has_many
no toma la opción {touch: true}
, y probablemente por una razón. Supongo que podría implementarse algo a lo largo de las siguientes líneas.
class Author < ActiveRecord::Base
attr_accessible :name
has_many :articles
has_many :comments
before_save do
articles.each { |record| record.touch }
comments.each { |record| record.touch }
end
end
article.comments.first.author.update_attribute(''name'', ''Bernard B.'')
article.cache_key
=> "articles/1-20130412192036"
Mientras esto funciona Tiene un gran impacto en el rendimiento al cargar, crear instancias y actualizar cada artículo y comentar por ese otro, uno por uno . No creo que sea una solución adecuada, pero ¿qué es?
Seguro que el caso / ejemplo de uso de 37signals podría ser diferente: project -> todolist -> todo
. Pero me imagino que un único artículo pendiente también pertenece a un usuario.
¿Cómo se resolvería este problema de almacenamiento en caché?
Como se recomienda aquí, https://rails.lighthouseapp.com/projects/8994/tickets/4392-add-touch-option-to-has_many-associations , en mi caso, acabo de crear una devolución de llamada after_save para actualizar las marcas de tiempo de los objetos relacionados.
def touch_line_items_and_tactics
self.line_item_advertisements.all.map(&:touch)
end
Aparte, construimos nuestra aplicación de rieles en una base de datos heredada que tiene last_modified_time
como el nombre de la columna y su semántica era "cuando el usuario la modificó por última vez". Entonces, debido a las diferentes semánticas, no pudimos usar la opción :touch
de la caja. Tuve que parchear el cache_key y tocar métodos como este https://gist.github.com/tispratik/9276110 para almacenar la marca de tiempo actualizada en memcached en lugar de la columna updated_at de las bases de datos.
También tenga en cuenta que no pude usar el cache_timestamp_format
predeterminado de Rails, ya que se proporciona con marcas de tiempo solo hasta segundos. Sentí la necesidad de tener una marca de tiempo más granular, así que elegí: nsec (nanosegundos).
Indicación de fecha y hora con cache_timestamp_format: 20140227181414
Indicación de fecha y hora con nsec: 20140227181414671756000
Un método con el que tropecé sería manejarlo a través de las teclas de caché. Agregue una relación has_many_through
para comentaristas al artículo:
class Article < ActiveRecord::Base
attr_accessible :author_id, :body, :title
has_many :comments
has_many :commenters, through: :comments, source: :author
belongs_to :author
end
Luego, en el artículo / show, construiremos la clave de caché de esta manera:
<% cache [@article, @article.commenters, @article.author] do %>
<h2><%= @article.title %></h2>
<p>Posted By: <%= @article.author.name %></p>
<p><%= @article.body %></p>
<ul><%= render @article.comments %></ul>
<% end %>
El truco es que la clave de caché generada a partir de la asociación de commenters
cambiará cada vez que se agregue, elimine o actualice un comentario. Si bien esto requiere consultas SQL adicionales para generar la clave de caché, funciona muy bien con el almacenamiento en caché de bajo nivel de Rails y la adición de algo así como la gema identity_cache puede ayudarlo fácilmente.
Me gustaría ver si otras personas tienen soluciones más claras para esto sin embargo.