establecer relaciones bidireccionales entre artículos
This commit is contained in:
parent
874c1a97e8
commit
2f2cc0e3c3
5 changed files with 108 additions and 5 deletions
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Almacena el UUID de otro Post
|
||||
# Almacena el UUID de otro Post y actualiza el valor en el Post
|
||||
# relacionado.
|
||||
class MetadataBelongsTo < MetadataRelatedPosts
|
||||
# TODO: Convertir algunos tipos de valores en módulos para poder
|
||||
# implementar varios tipos de campo sin repetir código
|
||||
|
@ -25,6 +26,58 @@ class MetadataBelongsTo < MetadataRelatedPosts
|
|||
errors.empty?
|
||||
end
|
||||
|
||||
# Guardar y guardar la relación inversa también, eliminando la
|
||||
# relación anterior si existía.
|
||||
#
|
||||
# XXX: Esto es un poco enclenque, porque habría que guardar tres
|
||||
# archivos en lugar de uno solo e indicarle al artículo que tiene uno
|
||||
# o muchos que busque los datos actualizados filtrando. Pero también
|
||||
# nos ahorra recursos en la búsqueda al cachear la información. En
|
||||
# una relación HABTM también vamos a hacer lo mismo.
|
||||
def save
|
||||
return super unless inverse? && !included?
|
||||
|
||||
# Evitar que se cambie el orden de la relación
|
||||
belonged_to&.dig(inverse)&.value&.delete post.uuid.value if belonged_to != belongs_to
|
||||
|
||||
belongs_to[inverse].value << post.uuid.value unless belongs_to[inverse].value.include? post.uuid.value
|
||||
|
||||
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? && belongs_to.present?
|
||||
end
|
||||
|
||||
# El campo que es la relación inversa de este
|
||||
def inverse
|
||||
layout.metadata.dig name, 'inverse'
|
||||
end
|
||||
|
||||
# El Post relacionado con este artículo
|
||||
#
|
||||
# XXX: Memoizamos usando el valor para tener el valor siempre
|
||||
# actualizado.
|
||||
def belongs_to
|
||||
return if value.blank?
|
||||
|
||||
@belongs_to ||= {}
|
||||
@belongs_to[value] ||= posts.find(value, uuid: true)
|
||||
end
|
||||
|
||||
# El anterior artículo relacionado
|
||||
def belonged_to
|
||||
return if document.data[name.to_s].blank?
|
||||
|
||||
@belonged_to ||= posts.find(document.data[name.to_s], uuid: true)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sanitize(uuid)
|
||||
|
|
|
@ -10,6 +10,14 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
|||
:layout, keyword_init: true) do
|
||||
include ActionText::ContentHelper
|
||||
|
||||
# Métodos que tienen artículos relacionados
|
||||
#
|
||||
# Ver el final del archivo.
|
||||
#
|
||||
# XXX: Por alguna razón no se pueden definir constantes en un Struct
|
||||
#
|
||||
# RELATED_METHODS = %i[]
|
||||
|
||||
# El valor por defecto
|
||||
def default_value
|
||||
raise NotImplementedError
|
||||
|
@ -21,9 +29,10 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
|||
site.everything_of(name, lang: post.try(:lang).try(:value))
|
||||
end
|
||||
|
||||
# Valor actual o por defecto
|
||||
# Valor actual o por defecto. Al memoizarlo podemos modificarlo
|
||||
# usando otros métodos que el de asignación.
|
||||
def value
|
||||
self[:value] || document.data.fetch(name.to_s, default_value)
|
||||
self[:value] ||= document.data.fetch(name.to_s, default_value)
|
||||
end
|
||||
|
||||
# Detecta si el valor está vacío
|
||||
|
@ -89,3 +98,6 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
|||
end
|
||||
end
|
||||
# rubocop:enable Metrics/BlockLength
|
||||
|
||||
# Definir la constante después de definir el Struct.
|
||||
MetadataTemplate.const_set 'RELATED_METHODS', %i[belongs_to belonged_to].freeze
|
||||
|
|
|
@ -13,7 +13,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
post.usuaries << usuarie
|
||||
params[:post][:draft] = true if site.invitade? usuarie
|
||||
|
||||
commit(action: :created) if post.update(post_params)
|
||||
commit(action: :created, file: update_related_posts) if post.update(post_params)
|
||||
|
||||
# Devolver el post aunque no se haya salvado para poder rescatar los
|
||||
# errores
|
||||
|
@ -37,7 +37,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
post.usuaries << usuarie
|
||||
params[:post][:draft] = true if site.invitade? usuarie
|
||||
|
||||
commit(action: :updated) if post.update(post_params)
|
||||
commit(action: :updated, file: update_related_posts) if post.update(post_params)
|
||||
|
||||
# Devolver el post aunque no se haya salvado para poder rescatar los
|
||||
# errores
|
||||
|
@ -109,5 +109,25 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
def layout
|
||||
params.dig(:post, :layout) || params[:layout]
|
||||
end
|
||||
|
||||
# Actualiza los artículos relacionados según los métodos que los
|
||||
# metadatos declaren.
|
||||
#
|
||||
# @return [Array] Lista de archivos modificados
|
||||
def update_related_posts
|
||||
files = [post.path.absolute]
|
||||
|
||||
post.attributes.each do |a|
|
||||
MetadataTemplate::RELATED_METHODS.each do |m|
|
||||
next unless post[a].respond_to? m
|
||||
|
||||
p = post[a].public_send(m)
|
||||
|
||||
files << p.path.absolute if p.save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
files
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/BlockLength
|
||||
|
|
5
app/views/posts/attribute_ro/_belongs_to.haml
Normal file
5
app/views/posts/attribute_ro/_belongs_to.haml
Normal file
|
@ -0,0 +1,5 @@
|
|||
%tr{ id: attribute }
|
||||
%th= post_label_t(attribute, post: post)
|
||||
%td{ dir: dir, lang: locale }
|
||||
- p = metadata.belongs_to
|
||||
= link_to p.title.value, site_post_path(site, p.id)
|
13
app/views/posts/attributes/_belongs_to.haml
Normal file
13
app/views/posts/attributes/_belongs_to.haml
Normal file
|
@ -0,0 +1,13 @@
|
|||
.form-group
|
||||
= label_tag "post_#{attribute}", post_label_t(attribute, post: post)
|
||||
= text_field 'post', attribute, value: metadata.value,
|
||||
dir: dir, lang: locale, list: id_for_datalist(attribute),
|
||||
pattern: metadata.values.values.join('|'), autocomplete: 'off',
|
||||
**field_options(attribute, metadata)
|
||||
= render 'posts/attribute_feedback',
|
||||
post: post, attribute: attribute, metadata: metadata
|
||||
|
||||
-# TODO: Ocultar el UUID
|
||||
%datalist{ id: id_for_datalist(attribute) }
|
||||
- metadata.values.each_pair do |key, value|
|
||||
%option{ value: value }= key
|
Loading…
Reference in a new issue