5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2025-03-14 22:28:16 +00:00

Merge branch 'issue-15066' into production.panel.sutty.nl

This commit is contained in:
f 2024-04-18 15:44:09 -03:00
commit e7e332dd0f
No known key found for this signature in database
9 changed files with 142 additions and 37 deletions

View file

@ -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

View file

@ -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
@ -38,18 +42,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?
@ -169,7 +165,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

View file

@ -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
@ -54,12 +57,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
@ -169,8 +193,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
@ -390,11 +413,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
@ -408,6 +427,13 @@ class Post
@usuaries ||= document_usuaries.empty? ? [] : Usuarie.where(id: document_usuaries).to_a
end
# Todos los atributos anidados
#
# @return [Array<Symbol>]
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.

View file

@ -10,13 +10,18 @@ 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)
# Crea los posts anidados
create_nested_posts! post, params[:post]
update_related_posts
commit(action: :created, add: files) if post.save
update_site_license!
@ -34,7 +39,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
@ -47,9 +52,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
@ -96,6 +103,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,
@ -108,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
@ -146,8 +162,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
@ -157,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

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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