From 0e010e804ca78e5c9192af4dd637164af8551f56 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 17 Apr 2024 17:58:14 -0300 Subject: [PATCH 1/7] fix: el valor original viene del documento y se cambia una sola vez --- app/models/metadata_template.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 823443d2..89fd8315 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -38,18 +38,10 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, "#{cache_key}-#{cache_version}" end - # XXX: Deberíamos sanitizar durante la asignación? - def value=(new_value) - @value_was = value - self[:value] = new_value - end - # Siempre obtener el valor actual y solo obtenerlo del documento una # vez. def value_was - return @value_was if instance_variable_defined? '@value_was' - - @value_was = document_value + @value_was ||= document_value.nil? ? default_value : document_value end def changed? From 9fb3601d9274c67202797a6dfefe35cc11a1a413 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 17 Apr 2024 18:04:19 -0300 Subject: [PATCH 2/7] fix: cambiar el valor una sola vez --- app/models/metadata_template.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 89fd8315..03d42339 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -161,7 +161,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, # once => el campo solo se puede modificar si estaba vacío def writable? case layout.metadata.dig(name, 'writable') - when 'once' then value.blank? + when 'once' then value_was.blank? else true end end From 42e9d5fd6612887d106a917e72731874da487800 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 17 Apr 2024 18:05:01 -0300 Subject: [PATCH 3/7] feat: poder asignar atributos sin guardar cambios --- app/models/post.rb | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index 8885897f..60382baf 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -15,6 +15,9 @@ class Post PUBLIC_ATTRIBUTES = %i[lang date uuid created_at].freeze ATTR_SUFFIXES = %w[? =].freeze + class PostError < StandardError; end + class UnknownAttributeError < PostError; end + attr_reader :attributes, :errors, :layout, :site, :document # TODO: Modificar el historial de Git con callbacks en lugar de @@ -50,12 +53,33 @@ class Post @errors = {} @metadata = {} - # Inicializar valores + # Leer el documento si existe + # @todo Asignar todos los valores a self[:value] luego de leer + document&.read! unless new? + + # Inicializar valores o modificar los que vengan del documento + assignable_attributes = args.slice(*attributes) + assign_attributes(assignable_attributes) if assignable_attributes.present? + end + + # Asignar atributos, ignorando atributos que no se pueden modificar + # o inexistentes + # + # @param attrs [Hash] + def assign_attributes(attrs) + attrs = attrs.transform_keys(&:to_sym) + attributes.each do |attr| - public_send(attr)&.value = args[attr] if args.key?(attr) + self[attr].value = attrs[attr] if attrs.key?(attr) && self[attr].writable? end - document.read! unless new? + unknown_attrs = attrs.keys.map(&:to_sym) - attributes + + if unknown_attrs.present? + raise UnknownAttributeError, "Unknown attribute(s) #{unknown_attrs.map(&:to_s).join(', ')} for Post" + end + + nil end def inspect @@ -165,8 +189,7 @@ class Post # Limpiar el nombre del atributo, para que todos los ayudantes # reciban el método en limpio unless attribute? name - raise NoMethodError, I18n.t('exceptions.post.no_method', - method: name) + raise UnknownAttributeError, I18n.t('exceptions.post.no_method', method: name) end define_singleton_method(name) do @@ -386,11 +409,7 @@ class Post end def update_attributes(hashable) - hashable.to_hash.each do |attr, value| - next unless self[attr].writable? - - self[attr].value = value - end + assign_attributes(hashable) save end From ce3ff1bdab28f0e0b19079c19c7374150f7d1bb1 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 17 Apr 2024 18:06:56 -0300 Subject: [PATCH 4/7] refactor: asignar atributos --- app/services/post_service.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/post_service.rb b/app/services/post_service.rb index 4631a9a4..5770a717 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -10,13 +10,14 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do self.post = site.posts(lang: locale) .build(layout: layout) post.usuaries << usuarie - params[:post][:draft] = true if site.invitade? usuarie + post.draft.value = true if site.invitade? usuarie + post.assign_attributes(post_params) params.require(:post).permit(:slug).tap do |p| post.slug.value = p[:slug] if p[:slug].present? end - commit(action: :created, add: update_related_posts) if post.update(post_params) + commit(action: :created, add: update_related_posts) if post.save update_site_license! From 363e2d8385dcc8f822f7a39045522c8c8dfb29ad Mon Sep 17 00:00:00 2001 From: f Date: Wed, 17 Apr 2024 18:07:42 -0300 Subject: [PATCH 5/7] refactor: llevar un staging de archivos a agregar --- app/services/post_service.rb | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/app/services/post_service.rb b/app/services/post_service.rb index 5770a717..20256d68 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -17,7 +17,9 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do post.slug.value = p[:slug] if p[:slug].present? end - commit(action: :created, add: update_related_posts) if post.save + update_related_posts + + commit(action: :created, add: files) if post.save update_site_license! @@ -35,7 +37,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do # Los artículos anónimos siempre son borradores params[:draft] = true - commit(action: :created, add: [post.path.absolute]) if post.update(anon_post_params) + commit(action: :created, add: files) if post.update(anon_post_params) post end @@ -48,9 +50,11 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do rm = [] rm << post.path.value_was if post.path.changed? + update_related_posts + # Es importante que el artículo se guarde primero y luego los # relacionados. - commit(action: :updated, add: update_related_posts, rm: rm) + commit(action: :updated, add: files, rm: rm) update_site_license! end @@ -97,6 +101,15 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do private + # Una lista de archivos a modificar + # + # @return [Set] + def files + @files ||= Set.new.tap do |f| + f << post.path.absolute + end + end + def commit(action:, add: [], rm: []) site.repository.commit(add: add, rm: rm, @@ -147,8 +160,10 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do end posts.map do |p| - p.path.absolute if p.save(validate: false) - end.compact << post.path.absolute + next unless p.save(validate: false) + + files << p.path.absolute + end end # Si les usuaries modifican o crean una licencia, considerarla From 5e2bddf7cc49fbd8519698d871eeeea09ae5b7fd Mon Sep 17 00:00:00 2001 From: f Date: Wed, 17 Apr 2024 18:09:05 -0300 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20crear=20art=C3=ADculos=20anidados?= =?UTF-8?q?=20#15066?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_template.rb | 4 ++++ app/models/post.rb | 7 +++++++ app/services/post_service.rb | 20 +++++++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 03d42339..5af68099 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -12,6 +12,10 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, false end + def nested? + false + end + def inspect "#<#{self.class} site=#{site.name.inspect} post=#{post.id.inspect} value=#{value.inspect}>" end diff --git a/app/models/post.rb b/app/models/post.rb index 60382baf..953598a9 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -423,6 +423,13 @@ class Post @usuaries ||= document_usuaries.empty? ? [] : Usuarie.where(id: document_usuaries).to_a end + # Todos los atributos anidados + # + # @return [Array] + def nested_attributes + @nested_attributes ||= attributes.map { |a| self[a] }.select(&:nested?).map(&:name) + end + private # Levanta un error si al construir el artículo no pasamos un atributo. diff --git a/app/services/post_service.rb b/app/services/post_service.rb index 20256d68..08a46ae7 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -17,6 +17,8 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do post.slug.value = p[:slug] if p[:slug].present? end + # Crea los posts anidados + create_nested_posts! post, params[:post] update_related_posts commit(action: :created, add: files) if post.save @@ -122,7 +124,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do # Solo permitir cambiar estos atributos de cada articulo def post_params - params.require(:post).permit(post.params) + @post_params ||= params.require(:post).permit(post.params).to_h end # Eliminar metadatos internos @@ -173,4 +175,20 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do site.update licencia: Licencia.find_by_icons('custom') end end + + # Encuentra todos los posts anidados y los crea o modifica + def create_nested_posts!(post, params) + post.nested_attributes.each do |nested_attribute| + nested_metadata = post[nested_attribute] + # @todo find_or_initialize + nested_post = site.posts(lang: post.lang.value).build(layout: nested_metadata.nested) + nested_params = params.require(nested_attribute).permit(nested_post.params).to_hash + + # Completa la relación 1:1 + nested_params[nested_metadata.inverse.to_s] = post.uuid.value + post[nested_attribute].value = nested_post.uuid.value + + files << nested_post.path.absolute if nested_post.update(nested_params) + end + end end From 0cf2ec04ba9b54c4e74001923003dc308a60ca04 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 17 Apr 2024 18:09:55 -0300 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20poder=20anidar=20un=20art=C3=ADculo?= =?UTF-8?q?=20#15066?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_has_one_nested.rb | 11 +++++++++++ app/views/posts/_attributes.haml | 16 ++++++++++++++++ app/views/posts/_attributes_nested.haml | 19 +++++++++++++++++++ app/views/posts/_form.haml | 11 +---------- .../posts/attribute_ro/_has_one_nested.haml | 6 ++++++ .../posts/attributes/_has_one_nested.haml | 6 ++++++ 6 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 app/models/metadata_has_one_nested.rb create mode 100644 app/views/posts/_attributes.haml create mode 100644 app/views/posts/_attributes_nested.haml create mode 100644 app/views/posts/attribute_ro/_has_one_nested.haml create mode 100644 app/views/posts/attributes/_has_one_nested.haml diff --git a/app/models/metadata_has_one_nested.rb b/app/models/metadata_has_one_nested.rb new file mode 100644 index 00000000..ed509013 --- /dev/null +++ b/app/models/metadata_has_one_nested.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class MetadataHasOneNested < MetadataHasOne + def nested + @nested ||= layout.metadata.dig(name, 'nested') + end + + def nested? + true + end +end diff --git a/app/views/posts/_attributes.haml b/app/views/posts/_attributes.haml new file mode 100644 index 00000000..ed958d08 --- /dev/null +++ b/app/views/posts/_attributes.haml @@ -0,0 +1,16 @@ +-# + @param base [String] + @param locale [String] + @param post [Post] + @param site [Site] + @param dir [String] +- post.attributes.each do |attribute| + - metadata = post[attribute] + - type = metadata.type + + - cache [metadata, I18n.locale] do + = render("posts/attributes/#{type}", + base: base, post: post, attribute: attribute, + metadata: metadata, site: site, + dir: dir, locale: locale, + autofocus: (post.attributes.first == attribute)) diff --git a/app/views/posts/_attributes_nested.haml b/app/views/posts/_attributes_nested.haml new file mode 100644 index 00000000..33996aa3 --- /dev/null +++ b/app/views/posts/_attributes_nested.haml @@ -0,0 +1,19 @@ +-# + @param inverse [Symbol] + @param base [String] + @param locale [String] + @param post [Post] + @param site [Site] + @param dir [String] +- post.attributes.each do |attribute| + - next if attribute == :date + - next if attribute == :draft + - next if attribute == inverse + - metadata = post[attribute] + - type = metadata.type + + - cache [metadata, I18n.locale] do + = render "posts/attributes/#{type}", + base: base, post: post, attribute: attribute, + metadata: metadata, site: site, + dir: dir, locale: locale, autofocus: false diff --git a/app/views/posts/_form.haml b/app/views/posts/_form.haml index 7de0ea79..92bee939 100644 --- a/app/views/posts/_form.haml +++ b/app/views/posts/_form.haml @@ -41,16 +41,7 @@ = hidden_field_tag 'post[layout]', post.layout.name -# Dibuja cada atributo - - post.attributes.each do |attribute| - - metadata = post[attribute] - - type = metadata.type - - - cache [metadata, I18n.locale] do - = render("posts/attributes/#{type}", - base: 'post', post: post, attribute: attribute, - metadata: metadata, site: site, - dir: dir, locale: @locale, - autofocus: (post.attributes.first == attribute)) + = render 'posts/attributes', site: site, post: post, dir: dir, base: 'post', locale: @locale -# Botones de guardado = render 'posts/submit', site: site, post: post diff --git a/app/views/posts/attribute_ro/_has_one_nested.haml b/app/views/posts/attribute_ro/_has_one_nested.haml new file mode 100644 index 00000000..425e659e --- /dev/null +++ b/app/views/posts/attribute_ro/_has_one_nested.haml @@ -0,0 +1,6 @@ +%tr{ id: attribute } + %th= post_label_t(attribute, post: post) + %td{ dir: dir, lang: locale } + - p = metadata.has_one + - if p + = link_to p.title.value, site_post_path(site, p.id) diff --git a/app/views/posts/attributes/_has_one_nested.haml b/app/views/posts/attributes/_has_one_nested.haml new file mode 100644 index 00000000..e98bff47 --- /dev/null +++ b/app/views/posts/attributes/_has_one_nested.haml @@ -0,0 +1,6 @@ +- new_post = site.posts(lang: locale).build(layout: metadata.nested) +- base = "#{base}[#{metadata.name}]" + +.form-group + = render 'layouts/details', id: metadata.nested, summary: site.layouts[metadata.nested].humanized_name do + = render 'posts/attributes_nested', site: site, post: new_post, dir: dir, base: base, locale: locale, inverse: metadata.inverse