mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-23 02:46: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
|
# Almacena el UUID de otro Post y actualiza el valor en el Post
|
||||||
# relacionado.
|
# relacionado.
|
||||||
class MetadataBelongsTo < MetadataRelatedPosts
|
class MetadataBelongsTo < MetadataRelatedPosts
|
||||||
|
include Metadata::InverseConcern
|
||||||
|
|
||||||
# TODO: Convertir algunos tipos de valores en módulos para poder
|
# TODO: Convertir algunos tipos de valores en módulos para poder
|
||||||
# implementar varios tipos de campo sin repetir código
|
# implementar varios tipos de campo sin repetir código
|
||||||
#
|
#
|
||||||
|
@ -20,86 +22,12 @@ class MetadataBelongsTo < MetadataRelatedPosts
|
||||||
document.data[name.to_s]
|
document.data[name.to_s]
|
||||||
end
|
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
|
def indexable_values
|
||||||
belongs_to&.title&.value
|
posts.find_by_post_uuid(value).try(:title)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def post_exists?
|
|
||||||
return true if sanitize(value).blank?
|
|
||||||
|
|
||||||
sanitize(value).present? && belongs_to.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def sanitize(uuid)
|
def sanitize(uuid)
|
||||||
uuid.to_s.gsub(/[^a-f0-9\-]/i, '')
|
uuid.to_s.gsub(/[^a-f0-9\-]/i, '')
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,46 +1,5 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Establece una relación de muchos a muchos artículos. Cada campo es un
|
# Establece una relación de muchos a muchos artículos
|
||||||
# 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.
|
|
||||||
class MetadataHasAndBelongsToMany < MetadataHasMany
|
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
|
end
|
||||||
|
|
|
@ -6,59 +6,5 @@
|
||||||
# Localmente tenemos un Array de UUIDs. Remotamente tenemos una String
|
# Localmente tenemos un Array de UUIDs. Remotamente tenemos una String
|
||||||
# apuntando a un Post, que se mantiene actualizado como el actual.
|
# apuntando a un Post, que se mantiene actualizado como el actual.
|
||||||
class MetadataHasMany < MetadataRelatedPosts
|
class MetadataHasMany < MetadataRelatedPosts
|
||||||
# Todos los Post relacionados
|
include Metadata::InverseConcern
|
||||||
#
|
|
||||||
# @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
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# Este servicio se encarga de crear artículos y guardarlos en git,
|
# Este servicio se encarga de crear artículos y guardarlos en git,
|
||||||
# asignándoselos a une usuarie
|
# asignándoselos a une usuarie
|
||||||
PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
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
|
# @return Post
|
||||||
def create
|
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?
|
post.slug.value = p[:slug] if p[:slug].present?
|
||||||
end
|
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
|
# Devolver el post aunque no se haya salvado para poder rescatar los
|
||||||
# errores
|
# errores
|
||||||
|
@ -25,6 +37,8 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Crear un post anónimo, con opciones más limitadas. No usamos post.
|
# Crear un post anónimo, con opciones más limitadas. No usamos post.
|
||||||
|
#
|
||||||
|
# @todo Permitir asociaciones?
|
||||||
def create_anonymous
|
def create_anonymous
|
||||||
# XXX: Confiamos en el parámetro de idioma porque estamos
|
# XXX: Confiamos en el parámetro de idioma porque estamos
|
||||||
# verificándolos en Site#posts
|
# verificándolos en Site#posts
|
||||||
|
@ -36,20 +50,30 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Al actualizar, modificamos un post pre-existente, todas las
|
||||||
|
# relaciones anteriores y las relaciones actuales.
|
||||||
def update
|
def update
|
||||||
post.usuaries << usuarie
|
post.usuaries << usuarie
|
||||||
params[:post][:draft] = true if site.invitade? usuarie
|
params[:post][:draft] = true if site.invitade? usuarie
|
||||||
|
|
||||||
# Eliminar ("mover") el archivo si cambió de ubicación.
|
|
||||||
if post.update(post_params)
|
if post.update(post_params)
|
||||||
|
# Eliminar ("mover") el archivo si cambió de ubicación.
|
||||||
rm = []
|
rm = []
|
||||||
rm << post.path.value_was if post.path.changed?
|
rm << post.path.value_was if post.path.changed?
|
||||||
|
|
||||||
# Es importante que el artículo se guarde primero y luego los
|
added_paths = []
|
||||||
# relacionados.
|
added_paths << post.path.value
|
||||||
commit(action: :updated, add: update_related_posts, rm: rm)
|
|
||||||
|
|
||||||
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
|
end
|
||||||
|
|
||||||
# Devolver el post aunque no se haya salvado para poder rescatar los
|
# 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
|
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
|
# Si les usuaries modifican o crean una licencia, considerarla
|
||||||
# personalizada en el panel.
|
# personalizada en el panel.
|
||||||
def update_site_license!
|
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')
|
site.update licencia: Licencia.find_by_icons('custom')
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
%tr{ id: attribute }
|
%tr{ id: attribute }
|
||||||
%th= post_label_t(attribute, post: post)
|
%th= post_label_t(attribute, post: post)
|
||||||
%td{ dir: dir, lang: locale }
|
%td{ dir: dir, lang: locale }
|
||||||
- p = metadata.belongs_to
|
- p = site.indexed_posts.find_by_post_id(metadata.value)
|
||||||
- if p
|
- 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)
|
%th= post_label_t(attribute, post: post)
|
||||||
%td
|
%td
|
||||||
%ul{ dir: dir, lang: locale }
|
%ul{ dir: dir, lang: locale }
|
||||||
- metadata.has_many.each do |p|
|
- site.indexed_posts.where(post_id: metadata.value).find_each do |p|
|
||||||
%li= link_to p.title.value, site_post_path(site, p.id)
|
%li= link_to p.title, site_post_path(site, p.path)
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
%th= post_label_t(attribute, post: post)
|
%th= post_label_t(attribute, post: post)
|
||||||
%td
|
%td
|
||||||
%ul{ dir: dir, lang: locale }
|
%ul{ dir: dir, lang: locale }
|
||||||
- metadata.has_many.each do |p|
|
- site.indexed_posts.where(post_id: metadata.value).find_each do |p|
|
||||||
%li= link_to p.title.value, site_post_path(site, p.id)
|
%li= link_to p.title, site_post_path(site, p.path)
|
||||||
|
|
Loading…
Reference in a new issue