diff --git a/app/models/metadata_belongs_to.rb b/app/models/metadata_belongs_to.rb index b94fbc2..2119547 100644 --- a/app/models/metadata_belongs_to.rb +++ b/app/models/metadata_belongs_to.rb @@ -73,6 +73,14 @@ class MetadataBelongsTo < MetadataRelatedPosts @belonged_to ||= posts.find(document.data[name.to_s], uuid: true) end + def related_posts? + true + end + + def related_methods + @related_methods ||= %i[belongs_to belonged_to].freeze + end + private def sanitize(uuid) diff --git a/app/models/metadata_has_many.rb b/app/models/metadata_has_many.rb new file mode 100644 index 0000000..03eb986 --- /dev/null +++ b/app/models/metadata_has_many.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# La diferencia con MetadataRelatedPosts es que la relación también +# actualiza los Posts remotos. +# +# 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 según la relación remota + def has_many_remote + @has_many_remote ||= posts.where(inverse => post.uuid.value) + end + + # Todos los Post relacionados + def has_many + @has_many ||= {} + @has_many[value.hash.to_s] ||= posts.where(uuid: value) + end + + # La relación anterior + def had_many + return [] if document.data[name.to_s].blank? + + @had_many ||= posts.where(uuid: document.data[name.to_s]) + end + + def inverse? + inverse.present? + end + + # La relación inversa + # + # @return [Nil,Symbol] + def 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 + self[:value] = sanitize value + + 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 + + private + + def sanitize(sanitizable) + sanitizable.map do |uuid| + uuid.gsub(/[^a-f0-9\-]/, '') + end + end +end diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 80c99e4..6242f8f 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -10,14 +10,6 @@ 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 @@ -85,6 +77,14 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, true end + def related_posts? + false + end + + def related_methods + raise NotImplementedError + end + private # Si es obligatorio no puede estar vacío @@ -106,6 +106,3 @@ 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 d847a9d..46e7a64 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -118,7 +118,9 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do files = [post.path.absolute] post.attributes.each do |a| - MetadataTemplate::RELATED_METHODS.each do |m| + next unless post[a].related_posts? + + post[a].related_methods.each do |m| next unless post[a].respond_to? m # La respuesta puede ser una PostRelation también diff --git a/app/views/posts/attribute_ro/_has_many.haml b/app/views/posts/attribute_ro/_has_many.haml new file mode 100644 index 0000000..d6b51a7 --- /dev/null +++ b/app/views/posts/attribute_ro/_has_many.haml @@ -0,0 +1,6 @@ +%tr{ id: attribute } + %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) diff --git a/app/views/posts/attributes/_has_many.haml b/app/views/posts/attributes/_has_many.haml new file mode 100644 index 0000000..13439fa --- /dev/null +++ b/app/views/posts/attributes/_has_many.haml @@ -0,0 +1,22 @@ +.form-group + = label_tag "post_#{attribute}", post_label_t(attribute, post: post) + + .mapable{ dir: dir, lang: locale, + data: { values: metadata.value.to_json, + 'default-values': metadata.values.to_json, + name: "post[#{attribute}][]", list: id_for_datalist(attribute), + remove: 'false', legend: post_label_t(attribute, post: post), + button: t('posts.attributes.add'), + described: id_for_help(attribute) } } + + = text_field(*field_name_for('post', attribute, '[]'), + value: metadata.value.join(', '), + dir: dir, lang: locale, + **field_options(attribute, metadata)) + + = render 'posts/attribute_feedback', + post: post, attribute: attribute, metadata: metadata + + %datalist{ id: id_for_datalist(attribute) } + - metadata.values.keys.each do |value| + %option{ value: value }