From 1a2d7931e9269fdfcd9b7e9f0f0674b9e681ed14 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 17 Feb 2021 18:38:02 -0300 Subject: [PATCH] optimizar posts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eliminar dependencia en OpenStruct * los metadata se instancian a medida que se usan * la lista de atributos se envió a Layout --- app/models/layout.rb | 4 + app/models/post.rb | 191 +++++++++++++++++---------------------- test/models/post_test.rb | 5 - 3 files changed, 87 insertions(+), 113 deletions(-) diff --git a/app/models/layout.rb b/app/models/layout.rb index c05a02f6..2d68273a 100644 --- a/app/models/layout.rb +++ b/app/models/layout.rb @@ -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. # diff --git a/app/models/post.rb b/app/models/post.rb index 39de2db8..8fc11b9a 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -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 diff --git a/test/models/post_test.rb b/test/models/post_test.rb index 67dd1a24..ab7dd510 100644 --- a/test/models/post_test.rb +++ b/test/models/post_test.rb @@ -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?