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:
parent
d3890a6117
commit
1a2d7931e9
3 changed files with 87 additions and 113 deletions
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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)
|
||||
# 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)
|
||||
def [](attr)
|
||||
public_send attr
|
||||
end
|
||||
|
||||
# OpenStruct
|
||||
super(mid, *args)
|
||||
# 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
|
||||
unless attribute? name
|
||||
raise NoMethodError, I18n.t('exceptions.post.no_method',
|
||||
method: name)
|
||||
end
|
||||
|
||||
# Devolver lo mismo que devuelve el método después de definirlo
|
||||
send(mid, *args)
|
||||
define_singleton_method(name) do
|
||||
template = layout.metadata[name.to_s]
|
||||
|
||||
@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
|
||||
|
|
|
@ -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?
|
||||
|
|
Loading…
Reference in a new issue