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
|
name.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attributes
|
||||||
|
@attributes ||= metadata.keys.map(&:to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
# Busca la traducción del Layout en el sitio o intenta humanizarlo
|
# Busca la traducción del Layout en el sitio o intenta humanizarlo
|
||||||
# según Rails.
|
# según Rails.
|
||||||
#
|
#
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
# Esta clase representa un post en un sitio jekyll e incluye métodos
|
# Esta clase representa un post en un sitio jekyll e incluye métodos
|
||||||
# para modificarlos y crear nuevos.
|
# para modificarlos y crear nuevos.
|
||||||
#
|
#
|
||||||
# rubocop:disable Metrics/ClassLength
|
# * Los metadatos se tienen que cargar dinámicamente, solo usamos los
|
||||||
# rubocop:disable Style/MissingRespondToMissing
|
# que necesitamos
|
||||||
class Post < OpenStruct
|
#
|
||||||
|
#
|
||||||
|
class Post
|
||||||
# Atributos por defecto
|
# Atributos por defecto
|
||||||
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
|
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
|
||||||
# Otros atributos que no vienen en los metadatos
|
# Otros atributos que no vienen en los metadatos
|
||||||
|
@ -13,6 +15,8 @@ class Post < OpenStruct
|
||||||
PUBLIC_ATTRIBUTES = %i[lang date uuid].freeze
|
PUBLIC_ATTRIBUTES = %i[lang date uuid].freeze
|
||||||
ATTR_SUFFIXES = %w[? =].freeze
|
ATTR_SUFFIXES = %w[? =].freeze
|
||||||
|
|
||||||
|
attr_reader :attributes, :errors, :layout, :site, :document
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
# Obtiene el layout sin leer el Document
|
# Obtiene el layout sin leer el Document
|
||||||
#
|
#
|
||||||
|
@ -31,41 +35,20 @@ class Post < OpenStruct
|
||||||
#
|
#
|
||||||
def initialize(**args)
|
def initialize(**args)
|
||||||
default_attributes_missing(**args)
|
default_attributes_missing(**args)
|
||||||
super(**args)
|
|
||||||
|
|
||||||
# Genera un método con todos los atributos disponibles
|
# Genera un método con todos los atributos disponibles
|
||||||
self.attributes = layout.metadata.keys.map(&:to_sym) + PUBLIC_ATTRIBUTES
|
@layout = args[:layout]
|
||||||
self.errors = {}
|
@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,
|
# Inicializar valores
|
||||||
# MetadataFactory devuelve un tipo de campo por cada campo. A
|
attributes.each do |attr|
|
||||||
# partir de ahí se pueden obtener los valores actuales y una lista
|
public_send(attr)&.value = args[attr] if args.key?(attr)
|
||||||
# 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'])
|
|
||||||
end
|
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
|
# XXX: No usamos Post#read porque a esta altura todavía no sabemos
|
||||||
# nada del Document
|
# nada del Document
|
||||||
document.read! if File.exist? document.path
|
document.read! if File.exist? document.path
|
||||||
|
@ -108,7 +91,8 @@ class Post < OpenStruct
|
||||||
html.css('img').each do |img|
|
html.css('img').each do |img|
|
||||||
next if %r{\Ahttps?://} =~ img.attributes['src']
|
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
|
end
|
||||||
|
|
||||||
# Notificar a les usuaries que están viendo una previsualización
|
# Notificar a les usuaries que están viendo una previsualización
|
||||||
|
@ -152,29 +136,65 @@ class Post < OpenStruct
|
||||||
@modified_at ||= Time.now
|
@modified_at ||= Time.now
|
||||||
end
|
end
|
||||||
|
|
||||||
# Solo ejecuta la magia de OpenStruct si el campo existe en la
|
def [](attr)
|
||||||
# plantilla
|
public_send attr
|
||||||
#
|
end
|
||||||
# XXX: Reemplazarlo por nuestro propio método, mantener todo lo demás
|
|
||||||
# compatible con OpenStruct
|
# Define metadatos a demanda
|
||||||
#
|
def method_missing(name, *_args)
|
||||||
# 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
|
# Limpiar el nombre del atributo, para que todos los ayudantes
|
||||||
# reciban el método en limpio
|
# reciban el método en limpio
|
||||||
name = attribute_name mid
|
|
||||||
|
|
||||||
unless attribute? name
|
unless attribute? name
|
||||||
raise NoMethodError, I18n.t('exceptions.post.no_method',
|
raise NoMethodError, I18n.t('exceptions.post.no_method',
|
||||||
method: mid)
|
method: name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# OpenStruct
|
define_singleton_method(name) do
|
||||||
super(mid, *args)
|
template = layout.metadata[name.to_s]
|
||||||
|
|
||||||
# Devolver lo mismo que devuelve el método después de definirlo
|
@metadata[name] ||=
|
||||||
send(mid, *args)
|
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
|
end
|
||||||
|
|
||||||
# Detecta si es un atributo válido o no, a partir de la tabla de la
|
# Detecta si es un atributo válido o no, a partir de la tabla de la
|
||||||
|
@ -189,11 +209,14 @@ class Post < OpenStruct
|
||||||
included
|
included
|
||||||
end
|
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
|
def params
|
||||||
attributes.map do |attr|
|
attributes.map do |attr|
|
||||||
send(attr).to_param
|
public_send(attr)&.to_param
|
||||||
end
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
# Genera el post con metadatos en YAML
|
# Genera el post con metadatos en YAML
|
||||||
|
@ -201,8 +224,8 @@ class Post < OpenStruct
|
||||||
# TODO: Cachear por un minuto
|
# TODO: Cachear por un minuto
|
||||||
def full_content
|
def full_content
|
||||||
body = ''
|
body = ''
|
||||||
yaml = layout.metadata.keys.map(&:to_sym).map do |metadata|
|
yaml = layout.attributes.map do |attr|
|
||||||
template = send(metadata)
|
template = public_send attr
|
||||||
|
|
||||||
unless template.front_matter?
|
unless template.front_matter?
|
||||||
body += "\n\n"
|
body += "\n\n"
|
||||||
|
@ -212,7 +235,7 @@ class Post < OpenStruct
|
||||||
|
|
||||||
next if template.empty?
|
next if template.empty?
|
||||||
|
|
||||||
[metadata.to_s, template.value]
|
[attr.to_s, template.value]
|
||||||
end.compact.to_h
|
end.compact.to_h
|
||||||
|
|
||||||
# TODO: Convertir a Metadata?
|
# TODO: Convertir a Metadata?
|
||||||
|
@ -281,7 +304,7 @@ class Post < OpenStruct
|
||||||
|
|
||||||
# Detecta si el artículo es válido para guardar
|
# Detecta si el artículo es válido para guardar
|
||||||
def valid?
|
def valid?
|
||||||
self.errors = {}
|
@errors = {}
|
||||||
|
|
||||||
layout.metadata.keys.map(&:to_sym).each do |metadata|
|
layout.metadata.keys.map(&:to_sym).each do |metadata|
|
||||||
template = send(metadata)
|
template = send(metadata)
|
||||||
|
@ -339,9 +362,7 @@ class Post < OpenStruct
|
||||||
# Levanta un error si al construir el artículo no pasamos un atributo.
|
# Levanta un error si al construir el artículo no pasamos un atributo.
|
||||||
def default_attributes_missing(**args)
|
def default_attributes_missing(**args)
|
||||||
DEFAULT_ATTRIBUTES.each do |attr|
|
DEFAULT_ATTRIBUTES.each do |attr|
|
||||||
i18n = I18n.t("exceptions.post.#{attr}_missing")
|
raise ArgumentError, I18n.t("exceptions.post.#{attr}_missing") unless args[attr].present?
|
||||||
|
|
||||||
raise ArgumentError, i18n unless args[attr].present?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -349,57 +370,11 @@ class Post < OpenStruct
|
||||||
document.data.fetch('usuaries', [])
|
document.data.fetch('usuaries', [])
|
||||||
end
|
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
|
# Ejecuta la acción de guardado en cada atributo
|
||||||
|
# TODO: Solo guardar los que se modificaron
|
||||||
def save_attributes!
|
def save_attributes!
|
||||||
attributes.map do |attr|
|
attributes.map do |attr|
|
||||||
send(attr).save
|
public_send(attr).save
|
||||||
end.all?
|
end.all?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/ClassLength
|
|
||||||
# rubocop:enable Style/MissingRespondToMissing
|
|
||||||
|
|
|
@ -95,11 +95,6 @@ class PostTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
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
|
test 'se puede cambiar el slug' do
|
||||||
@post.title.value = SecureRandom.hex
|
@post.title.value = SecureRandom.hex
|
||||||
assert_not @post.slug.changed?
|
assert_not @post.slug.changed?
|
||||||
|
|
Loading…
Reference in a new issue