optimizar posts

* eliminar dependencia en OpenStruct
* los metadata se instancian a medida que se usan
* la lista de atributos se envió a Layout
This commit is contained in:
f 2021-02-17 18:38:02 -03:00
parent d3890a6117
commit 1a2d7931e9
3 changed files with 87 additions and 113 deletions

View file

@ -9,6 +9,10 @@ Layout = Struct.new(:site, :name, :meta, :metadata, keyword_init: true) do
name.to_s
end
def attributes
@attributes ||= metadata.keys.map(&:to_sym)
end
# Busca la traducción del Layout en el sitio o intenta humanizarlo
# según Rails.
#

View file

@ -3,9 +3,11 @@
# Esta clase representa un post en un sitio jekyll e incluye métodos
# para modificarlos y crear nuevos.
#
# rubocop:disable Metrics/ClassLength
# rubocop:disable Style/MissingRespondToMissing
class Post < OpenStruct
# * Los metadatos se tienen que cargar dinámicamente, solo usamos los
# que necesitamos
#
#
class Post
# Atributos por defecto
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
# Otros atributos que no vienen en los metadatos
@ -13,6 +15,8 @@ class Post < OpenStruct
PUBLIC_ATTRIBUTES = %i[lang date uuid].freeze
ATTR_SUFFIXES = %w[? =].freeze
attr_reader :attributes, :errors, :layout, :site, :document
class << self
# Obtiene el layout sin leer el Document
#
@ -31,41 +35,20 @@ class Post < OpenStruct
#
def initialize(**args)
default_attributes_missing(**args)
super(**args)
# Genera un método con todos los atributos disponibles
self.attributes = layout.metadata.keys.map(&:to_sym) + PUBLIC_ATTRIBUTES
self.errors = {}
@layout = args[:layout]
@site = args[:site]
@document = args[:document]
@attributes = layout.attributes + PUBLIC_ATTRIBUTES
@errors = {}
@metadata = {}
# Genera un atributo por cada uno de los campos de la plantilla,
# MetadataFactory devuelve un tipo de campo por cada campo. A
# partir de ahí se pueden obtener los valores actuales y una lista
# de valores por defecto.
#
# XXX: En el primer intento de hacerlo más óptimo, movimos esta
# lógica a instanciación bajo demanda, pero no solo no logramos
# optimizar sino que aumentamos el tiempo de carga :/
layout.metadata.each_pair do |name, template|
send "#{name}=".to_sym,
MetadataFactory.build(document: document,
post: self,
site: site,
name: name.to_sym,
value: args[name.to_sym],
layout: layout,
type: template['type'],
label: template['label'],
help: template['help'],
required: template['required'])
# Inicializar valores
attributes.each do |attr|
public_send(attr)&.value = args[attr] if args.key?(attr)
end
# TODO: Llamar dinámicamente
load_lang!
load_slug!
load_date!
load_path!
load_uuid!
# XXX: No usamos Post#read porque a esta altura todavía no sabemos
# nada del Document
document.read! if File.exist? document.path
@ -108,7 +91,8 @@ class Post < OpenStruct
html.css('img').each do |img|
next if %r{\Ahttps?://} =~ img.attributes['src']
img.attributes['src'].value = Rails.application.routes.url_helpers.site_static_file_url(site, file: img.attributes['src'].value)
img.attributes['src'].value = Rails.application.routes.url_helpers.site_static_file_url(site,
file: img.attributes['src'].value)
end
# Notificar a les usuaries que están viendo una previsualización
@ -152,29 +136,65 @@ class Post < OpenStruct
@modified_at ||= Time.now
end
# Solo ejecuta la magia de OpenStruct si el campo existe en la
# plantilla
#
# XXX: Reemplazarlo por nuestro propio método, mantener todo lo demás
# compatible con OpenStruct
#
# XXX: rubocop dice que tenemos que usar super cuando ya lo estamos
# usando...
def method_missing(mid, *args)
def [](attr)
public_send attr
end
# Define metadatos a demanda
def method_missing(name, *_args)
# Limpiar el nombre del atributo, para que todos los ayudantes
# reciban el método en limpio
name = attribute_name mid
unless attribute? name
raise NoMethodError, I18n.t('exceptions.post.no_method',
method: mid)
method: name)
end
# OpenStruct
super(mid, *args)
define_singleton_method(name) do
template = layout.metadata[name.to_s]
# Devolver lo mismo que devuelve el método después de definirlo
send(mid, *args)
@metadata[name] ||=
MetadataFactory.build(document: document,
post: self,
site: site,
name: name,
layout: layout,
type: template['type'],
label: template['label'],
help: template['help'],
required: template['required'])
end
public_send name
end
# TODO: Mover a method_missing
def slug
@metadata[:slug] ||= MetadataSlug.new(document: document, site: site, layout: layout, name: :slug, type: :slug,
post: self, required: true)
end
# TODO: Mover a method_missing
def date
@metadata[:date] ||= MetadataDocumentDate.new(document: document, site: site, layout: layout, name: :date,
type: :document_date, post: self, required: true)
end
# TODO: Mover a method_missing
def path
@metadata[:path] ||= MetadataPath.new(document: document, site: site, layout: layout, name: :path, type: :path,
post: self, required: true)
end
# TODO: Mover a method_missing
def lang
@metadata[:lang] ||= MetadataLang.new(document: document, site: site, layout: layout, name: :lang, type: :lang,
post: self, required: true)
end
# TODO: Mover a method_missing
def uuid
@metadata[:uuid] ||= MetadataUuid.new(document: document, site: site, layout: layout, name: :uuid, type: :uuid,
post: self, required: true)
end
# Detecta si es un atributo válido o no, a partir de la tabla de la
@ -189,11 +209,14 @@ class Post < OpenStruct
included
end
# Devuelve los strong params para el layout
# Devuelve los strong params para el layout.
#
# XXX: Nos gustaría no tener que instanciar Metadata acá, pero depende
# del valor por defecto que a su vez depende de Layout.
def params
attributes.map do |attr|
send(attr).to_param
end
public_send(attr)&.to_param
end.compact
end
# Genera el post con metadatos en YAML
@ -201,8 +224,8 @@ class Post < OpenStruct
# TODO: Cachear por un minuto
def full_content
body = ''
yaml = layout.metadata.keys.map(&:to_sym).map do |metadata|
template = send(metadata)
yaml = layout.attributes.map do |attr|
template = public_send attr
unless template.front_matter?
body += "\n\n"
@ -212,7 +235,7 @@ class Post < OpenStruct
next if template.empty?
[metadata.to_s, template.value]
[attr.to_s, template.value]
end.compact.to_h
# TODO: Convertir a Metadata?
@ -281,7 +304,7 @@ class Post < OpenStruct
# Detecta si el artículo es válido para guardar
def valid?
self.errors = {}
@errors = {}
layout.metadata.keys.map(&:to_sym).each do |metadata|
template = send(metadata)
@ -339,9 +362,7 @@ class Post < OpenStruct
# Levanta un error si al construir el artículo no pasamos un atributo.
def default_attributes_missing(**args)
DEFAULT_ATTRIBUTES.each do |attr|
i18n = I18n.t("exceptions.post.#{attr}_missing")
raise ArgumentError, i18n unless args[attr].present?
raise ArgumentError, I18n.t("exceptions.post.#{attr}_missing") unless args[attr].present?
end
end
@ -349,57 +370,11 @@ class Post < OpenStruct
document.data.fetch('usuaries', [])
end
# Obtiene el nombre del atributo a partir del nombre del método
def attribute_name(attr)
# XXX: Los simbolos van al final
@attribute_name_cache ||= {}
@attribute_name_cache[attr] ||= ATTR_SUFFIXES.reduce(attr.to_s) do |a, suffix|
a.chomp suffix
end.to_sym
end
def load_slug!
self.slug = MetadataSlug.new(document: document, site: site,
layout: layout, name: :slug, type: :slug,
post: self,
required: true)
end
def load_date!
self.date = MetadataDocumentDate.new(document: document, site: site,
layout: layout, name: :date,
type: :document_date,
post: self,
required: true)
end
def load_path!
self.path = MetadataPath.new(document: document, site: site,
layout: layout, name: :path,
type: :path, post: self,
required: true)
end
def load_lang!
self.lang = MetadataLang.new(document: document, site: site,
layout: layout, name: :lang,
type: :lang, post: self,
required: true)
end
def load_uuid!
self.uuid = MetadataUuid.new(document: document, site: site,
layout: layout, name: :uuid,
type: :uuid, post: self,
required: true)
end
# Ejecuta la acción de guardado en cada atributo
# TODO: Solo guardar los que se modificaron
def save_attributes!
attributes.map do |attr|
send(attr).save
public_send(attr).save
end.all?
end
end
# rubocop:enable Metrics/ClassLength
# rubocop:enable Style/MissingRespondToMissing

View file

@ -95,11 +95,6 @@ class PostTest < ActiveSupport::TestCase
end
end
test 'attribute_name' do
assert_equal :hola, @post.send(:attribute_name, :hola)
assert_equal :hola, @post.send(:attribute_name, :hola?)
end
test 'se puede cambiar el slug' do
@post.title.value = SecureRandom.hex
assert_not @post.slug.changed?