From ec02a4500265f3944412cce65918dc9640d53b4b Mon Sep 17 00:00:00 2001 From: f Date: Wed, 22 Jul 2020 16:36:39 -0300 Subject: [PATCH] =?UTF-8?q?relaci=C3=B3n=20de=20muchos=20a=20muchos=20art?= =?UTF-8?q?=C3=ADculos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metadata_has_and_belongs_to_many.rb | 84 +++++++++++++++++++ app/services/post_service.rb | 7 +- .../_has_and_belongs_to_many.haml | 6 ++ .../attributes/_has_and_belongs_to_many.haml | 22 +++++ 4 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 app/models/metadata_has_and_belongs_to_many.rb create mode 100644 app/views/posts/attribute_ro/_has_and_belongs_to_many.haml create mode 100644 app/views/posts/attributes/_has_and_belongs_to_many.haml diff --git a/app/models/metadata_has_and_belongs_to_many.rb b/app/models/metadata_has_and_belongs_to_many.rb new file mode 100644 index 00000000..92b4581e --- /dev/null +++ b/app/models/metadata_has_and_belongs_to_many.rb @@ -0,0 +1,84 @@ +# 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. +class MetadataHasAndBelongsToMany < MetadataBelongsTo + def default_value + [] + end + + # Posts a los que pertenece. Memoizamos por value para obtener + # siempre la última relación. + # + # Buscamos todos los Post contenidos en el valor actual. No + # garantizamos el orden. + # + # @return [PostRelation] Posts + def belongs_to + @belongs_to ||= {} + @belongs_to[value.hash.to_s] ||= posts.where(uuid: value) + end + + # Devuelve la lista de Posts relacionados con este buscándolos en la + # relación inversa. #save debería mantenerlos sincronizados. + # + # @return [PostRelation] + def has_many + @has_many ||= {} + @has_many[value.hash.to_s] ||= posts.where(inverse.to_sym => post.uuid.value) + end + alias had_many has_many + + # Posts a los que pertenecía + # + # @return [PostRelation] Posts + def belonged_to + @belonged_to ||= posts.where(uuid: document.data.fetch(name.to_s, [])) + end + + # 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 + self[:value] = sanitize value + + return true unless inverse? && !included? + + (belonged_to - belongs_to).each do |p| + p[inverse].value.delete post.uuid.value + end + + (belongs_to - belonged_to).each do |p| + p[inverse].value << post.uuid.value + end + + true + end + + def sanitize(sanitizable) + sanitizable.map do |v| + super v + end + end + + def post_exists? + !belongs_to.empty? + end + + # Todos los artículos relacionados incluyen a este? + def included? + belongs_to.map do |p| + p[inverse].value.include? post.uuid.value + end.all? + end +end diff --git a/app/services/post_service.rb b/app/services/post_service.rb index 5f9c2221..d847a9dd 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -121,9 +121,10 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do 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) + # La respuesta puede ser una PostRelation también + [post[a].public_send(m)].flatten.compact.each do |p| + files << p.path.absolute if p.save(validate: false) + end end end diff --git a/app/views/posts/attribute_ro/_has_and_belongs_to_many.haml b/app/views/posts/attribute_ro/_has_and_belongs_to_many.haml new file mode 100644 index 00000000..d6b51a7a --- /dev/null +++ b/app/views/posts/attribute_ro/_has_and_belongs_to_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_and_belongs_to_many.haml b/app/views/posts/attributes/_has_and_belongs_to_many.haml new file mode 100644 index 00000000..13439fa4 --- /dev/null +++ b/app/views/posts/attributes/_has_and_belongs_to_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 }