mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-22 22:06:22 +00:00
refactor: guardar las asociaciones en PostService
(cherry picked from commit 4cf57a441a
)
This commit is contained in:
parent
26965ea120
commit
232cda1379
8 changed files with 124 additions and 210 deletions
23
app/models/concerns/metadata/inverse_concern.rb
Normal file
23
app/models/concerns/metadata/inverse_concern.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Metadata
|
||||
module InverseConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Hay una relación inversa?
|
||||
#
|
||||
# @return [Boolean]
|
||||
def inverse?
|
||||
inverse.present?
|
||||
end
|
||||
|
||||
# La relación inversa
|
||||
#
|
||||
# @return [Nil,Symbol]
|
||||
def inverse
|
||||
@inverse ||= layout.metadata.dig(name, 'inverse')&.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,8 @@
|
|||
# Almacena el UUID de otro Post y actualiza el valor en el Post
|
||||
# relacionado.
|
||||
class MetadataBelongsTo < MetadataRelatedPosts
|
||||
include Metadata::InverseConcern
|
||||
|
||||
# TODO: Convertir algunos tipos de valores en módulos para poder
|
||||
# implementar varios tipos de campo sin repetir código
|
||||
#
|
||||
|
@ -20,86 +22,12 @@ class MetadataBelongsTo < MetadataRelatedPosts
|
|||
document.data[name.to_s]
|
||||
end
|
||||
|
||||
def validate
|
||||
super
|
||||
|
||||
errors << I18n.t('metadata.belongs_to.missing_post') unless post_exists?
|
||||
|
||||
errors.empty?
|
||||
end
|
||||
|
||||
# Guardar y guardar la relación inversa también, eliminando la
|
||||
# relación anterior si existía.
|
||||
def save
|
||||
super
|
||||
|
||||
# Si no hay relación inversa, no hacer nada más
|
||||
return true unless changed?
|
||||
return true unless inverse?
|
||||
|
||||
# Si estamos cambiando la relación, tenemos que eliminar la relación
|
||||
# anterior
|
||||
if belonged_to.present?
|
||||
belonged_to[inverse].value = belonged_to[inverse].value.reject do |rej|
|
||||
rej == post.uuid.value
|
||||
end
|
||||
end
|
||||
|
||||
# No duplicar las relaciones
|
||||
belongs_to[inverse].value = (belongs_to[inverse].value.dup << post.uuid.value) unless belongs_to.blank? || included?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# El Post actual está incluido en la relación inversa?
|
||||
def included?
|
||||
belongs_to[inverse].value.include?(post.uuid.value)
|
||||
end
|
||||
|
||||
# Hay una relación inversa y el artículo existe?
|
||||
def inverse?
|
||||
inverse.present?
|
||||
end
|
||||
|
||||
# El campo que es la relación inversa de este
|
||||
def inverse
|
||||
@inverse ||= layout.metadata.dig(name, 'inverse')&.to_sym
|
||||
end
|
||||
|
||||
# El Post relacionado con este artículo
|
||||
#
|
||||
# @return [Post,nil]
|
||||
def belongs_to
|
||||
posts.find_by(post_id: value)&.post if value.present?
|
||||
end
|
||||
|
||||
# El artículo relacionado anterior
|
||||
#
|
||||
# @return [Post,nil]
|
||||
def belonged_to
|
||||
posts.find_by(post_id: value_was)&.post if value_was.present?
|
||||
end
|
||||
|
||||
def related_posts?
|
||||
true
|
||||
end
|
||||
|
||||
def related_methods
|
||||
@related_methods ||= %i[belongs_to belonged_to].freeze
|
||||
end
|
||||
|
||||
def indexable_values
|
||||
belongs_to&.title&.value
|
||||
posts.find_by_post_uuid(value).try(:title)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def post_exists?
|
||||
return true if sanitize(value).blank?
|
||||
|
||||
sanitize(value).present? && belongs_to.present?
|
||||
end
|
||||
|
||||
def sanitize(uuid)
|
||||
uuid.to_s.gsub(/[^a-f0-9\-]/i, '')
|
||||
end
|
||||
|
|
|
@ -1,46 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Establece una relación de muchos a muchos artículos. Cada campo es un
|
||||
# Array de UUID que se mantienen sincronizados.
|
||||
#
|
||||
# Por ejemplo:
|
||||
#
|
||||
# Un libro puede tener muches autores y une autore muchos libros. La
|
||||
# relación has_many tiene que traer todes les autores relacionades con
|
||||
# el libro actual. La relación belongs_to tiene que traer todes les
|
||||
# autores que tienen este libro. La relación es bidireccional, no hay
|
||||
# diferencia entre has_many y belongs_to.
|
||||
# Establece una relación de muchos a muchos artículos
|
||||
class MetadataHasAndBelongsToMany < MetadataHasMany
|
||||
# Mantiene la relación inversa si existe.
|
||||
#
|
||||
# La relación belongs_to se mantiene actualizada en la modificación
|
||||
# actual. Lo que buscamos es mantener sincronizada esa relación.
|
||||
#
|
||||
# Buscamos en belongs_to la relación local, si se eliminó hay que
|
||||
# quitarla de la relación remota, sino hay que agregarla.
|
||||
#
|
||||
def save
|
||||
# XXX: No usamos super
|
||||
self[:value] = sanitize value
|
||||
|
||||
return true unless changed?
|
||||
return true unless inverse?
|
||||
|
||||
# XXX: Usamos asignación para aprovechar value= que setea el valor
|
||||
# anterior en @value_was
|
||||
(had_many - has_many).each do |remove|
|
||||
remove[inverse].value = remove[inverse].value.reject do |rej|
|
||||
rej == post.uuid.value
|
||||
end
|
||||
end
|
||||
|
||||
(has_many - had_many).each do |add|
|
||||
next unless add[inverse]
|
||||
next if add[inverse].value.include? post.uuid.value
|
||||
|
||||
add[inverse].value = (add[inverse].value.dup << post.uuid.value)
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,59 +6,5 @@
|
|||
# Localmente tenemos un Array de UUIDs. Remotamente tenemos una String
|
||||
# apuntando a un Post, que se mantiene actualizado como el actual.
|
||||
class MetadataHasMany < MetadataRelatedPosts
|
||||
# Todos los Post relacionados
|
||||
#
|
||||
# @return [Array<Post>]
|
||||
def has_many
|
||||
return default_value if value.blank?
|
||||
|
||||
posts.where(post_id: value).map(&:post)
|
||||
end
|
||||
|
||||
# La relación anterior
|
||||
#
|
||||
# @return [Array<Post>]
|
||||
def had_many
|
||||
return default_value if value_was.blank?
|
||||
|
||||
posts.where(post_id: value_was).map(&:post)
|
||||
end
|
||||
|
||||
def inverse?
|
||||
inverse.present?
|
||||
end
|
||||
|
||||
# La relación inversa
|
||||
#
|
||||
# @return [Nil,Symbol]
|
||||
def inverse
|
||||
@inverse ||= layout.metadata.dig(name, 'inverse')&.to_sym
|
||||
end
|
||||
|
||||
# Actualizar las relaciones inversas. Hay que buscar la diferencia
|
||||
# entre had y has_many.
|
||||
def save
|
||||
super
|
||||
|
||||
return true unless changed?
|
||||
return true unless inverse?
|
||||
|
||||
(had_many - has_many).each do |remove|
|
||||
remove[inverse]&.value = remove[inverse].default_value
|
||||
end
|
||||
|
||||
(has_many - had_many).each do |add|
|
||||
add[inverse]&.value = post.uuid.value
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def related_posts?
|
||||
true
|
||||
end
|
||||
|
||||
def related_methods
|
||||
@related_methods ||= %i[has_many had_many].freeze
|
||||
end
|
||||
include Metadata::InverseConcern
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Este servicio se encarga de crear artículos y guardarlos en git,
|
||||
# asignándoselos a une usuarie
|
||||
PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||
# Crea un artículo nuevo
|
||||
# Crea un artículo nuevo y modificar las asociaciones
|
||||
#
|
||||
# @return Post
|
||||
def create
|
||||
|
@ -15,9 +15,21 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
post.slug.value = p[:slug] if p[:slug].present?
|
||||
end
|
||||
|
||||
commit(action: :created, add: update_related_posts) if post.update(post_params)
|
||||
if post.update(post_params)
|
||||
added_paths = []
|
||||
added_paths << post.path.value
|
||||
|
||||
update_site_license!
|
||||
# Recorrer todas las asociaciones y agregarse donde corresponda
|
||||
update_associations_forward(post)
|
||||
|
||||
associated_posts_to_save.each do |associated_post|
|
||||
next unless associated_post.save(validate: false)
|
||||
|
||||
added_paths << associated_post.path.value
|
||||
end
|
||||
|
||||
commit(action: :created, add: added_paths)
|
||||
end
|
||||
|
||||
# Devolver el post aunque no se haya salvado para poder rescatar los
|
||||
# errores
|
||||
|
@ -25,6 +37,8 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
end
|
||||
|
||||
# Crear un post anónimo, con opciones más limitadas. No usamos post.
|
||||
#
|
||||
# @todo Permitir asociaciones?
|
||||
def create_anonymous
|
||||
# XXX: Confiamos en el parámetro de idioma porque estamos
|
||||
# verificándolos en Site#posts
|
||||
|
@ -36,20 +50,30 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
post
|
||||
end
|
||||
|
||||
# Al actualizar, modificamos un post pre-existente, todas las
|
||||
# relaciones anteriores y las relaciones actuales.
|
||||
def update
|
||||
post.usuaries << usuarie
|
||||
params[:post][:draft] = true if site.invitade? usuarie
|
||||
|
||||
# Eliminar ("mover") el archivo si cambió de ubicación.
|
||||
if post.update(post_params)
|
||||
# Eliminar ("mover") el archivo si cambió de ubicación.
|
||||
rm = []
|
||||
rm << post.path.value_was if post.path.changed?
|
||||
|
||||
# Es importante que el artículo se guarde primero y luego los
|
||||
# relacionados.
|
||||
commit(action: :updated, add: update_related_posts, rm: rm)
|
||||
added_paths = []
|
||||
added_paths << post.path.value
|
||||
|
||||
update_site_license!
|
||||
# Recorrer todas las asociaciones y agregarse donde corresponda
|
||||
update_associations_forward(post)
|
||||
|
||||
associated_posts_to_save.each do |associated_post|
|
||||
next unless associated_post.save(validate: false)
|
||||
|
||||
added_paths << associated_post.path.value
|
||||
end
|
||||
|
||||
commit(action: :updated, add: added_paths, rm: rm)
|
||||
end
|
||||
|
||||
# Devolver el post aunque no se haya salvado para poder rescatar los
|
||||
|
@ -128,30 +152,6 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
]
|
||||
end
|
||||
|
||||
# Actualiza los artículos relacionados según los métodos que los
|
||||
# metadatos declaren.
|
||||
#
|
||||
# Este método se asegura que todos los artículos se guardan una sola
|
||||
# vez.
|
||||
#
|
||||
# @return [Array] Lista de archivos modificados
|
||||
def update_related_posts
|
||||
posts = Set.new
|
||||
|
||||
post.attributes.each do |a|
|
||||
post[a].related_methods.each do |m|
|
||||
next unless post[a].respond_to? m
|
||||
|
||||
# La respuesta puede ser una PostRelation también
|
||||
posts.merge [post[a].public_send(m)].flatten.compact
|
||||
end
|
||||
end
|
||||
|
||||
posts.map do |p|
|
||||
p.path.absolute if p.save(validate: false)
|
||||
end.compact << post.path.absolute
|
||||
end
|
||||
|
||||
# Si les usuaries modifican o crean una licencia, considerarla
|
||||
# personalizada en el panel.
|
||||
def update_site_license!
|
||||
|
@ -159,4 +159,62 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
site.update licencia: Licencia.find_by_icons('custom')
|
||||
end
|
||||
end
|
||||
|
||||
# @return [Array<String>]
|
||||
def associated_posts_to_save
|
||||
@associated_posts_to_save ||= Set.new
|
||||
end
|
||||
|
||||
# Recolectar campos asociados que no estén vacíos
|
||||
#
|
||||
# @param [Post]
|
||||
# @return [Array<Symbol>]
|
||||
def association_attributes(post)
|
||||
post.attributes.select do |attribute|
|
||||
post[attribute].try(:inverse?)
|
||||
end
|
||||
end
|
||||
|
||||
# @param :post_ids [Array<String>]
|
||||
# @return [Association]
|
||||
def associated_posts(post_ids)
|
||||
site.indexed_posts.where(post_id: post_ids).map(&:post)
|
||||
end
|
||||
|
||||
# Modificar las asociaciones.
|
||||
#
|
||||
# Si el valor actual es una String, es un BelongsTo
|
||||
#
|
||||
#
|
||||
def update_associations_forward(post)
|
||||
association_attributes(post).each do |attribute|
|
||||
metadata = post[attribute]
|
||||
|
||||
next unless metadata.changed?
|
||||
|
||||
inverse_attribute = post[attribute].inverse
|
||||
value_was = metadata.value_was.dup
|
||||
value = metadata.value.dup
|
||||
|
||||
case metadata.type
|
||||
when 'has_and_belongs_to_many'
|
||||
binding.pry
|
||||
associated_posts(value_was - value).each do |remove_post|
|
||||
remove_post[inverse_attribute].value.delete(post.uuid.value)
|
||||
|
||||
associated_posts_to_save << remove_post
|
||||
end
|
||||
|
||||
associated_posts(value - value_was).each do |add_post|
|
||||
add_post[inverse_attribute].value << post.uuid.value
|
||||
add_post[inverse_attribute].value.uniq!
|
||||
|
||||
associated_posts_to_save << add_post
|
||||
end
|
||||
when 'has_many'
|
||||
when 'belongs_to'
|
||||
when 'locales'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
%tr{ id: attribute }
|
||||
%th= post_label_t(attribute, post: post)
|
||||
%td{ dir: dir, lang: locale }
|
||||
- p = metadata.belongs_to
|
||||
- p = site.indexed_posts.find_by_post_id(metadata.value)
|
||||
- if p
|
||||
= link_to p.title.value, site_post_path(site, p.id)
|
||||
= link_to p.title, site_post_path(site, p.path)
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
%th= post_label_t(attribute, post: post)
|
||||
%td
|
||||
%ul{ dir: dir, lang: locale }
|
||||
- metadata.has_many.each do |p|
|
||||
%li= link_to p.title.value, site_post_path(site, p.id)
|
||||
- site.indexed_posts.where(post_id: metadata.value).find_each do |p|
|
||||
%li= link_to p.title, site_post_path(site, p.path)
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
%th= post_label_t(attribute, post: post)
|
||||
%td
|
||||
%ul{ dir: dir, lang: locale }
|
||||
- metadata.has_many.each do |p|
|
||||
%li= link_to p.title.value, site_post_path(site, p.id)
|
||||
- site.indexed_posts.where(post_id: metadata.value).find_each do |p|
|
||||
%li= link_to p.title, site_post_path(site, p.path)
|
||||
|
|
Loading…
Reference in a new issue