diff --git a/app/models/metadata_belongs_to.rb b/app/models/metadata_belongs_to.rb index 941273a..b2d0ef7 100644 --- a/app/models/metadata_belongs_to.rb +++ b/app/models/metadata_belongs_to.rb @@ -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) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 6d490b5..8c016b1 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -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 diff --git a/app/services/post_service.rb b/app/services/post_service.rb index 48dd917..5f9c222 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -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 diff --git a/app/views/posts/attribute_ro/_belongs_to.haml b/app/views/posts/attribute_ro/_belongs_to.haml new file mode 100644 index 0000000..2f7ee46 --- /dev/null +++ b/app/views/posts/attribute_ro/_belongs_to.haml @@ -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) diff --git a/app/views/posts/attributes/_belongs_to.haml b/app/views/posts/attributes/_belongs_to.haml new file mode 100644 index 0000000..b74b09d --- /dev/null +++ b/app/views/posts/attributes/_belongs_to.haml @@ -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