diff --git a/app/models/layout.rb b/app/models/layout.rb new file mode 100644 index 0000000..43892be --- /dev/null +++ b/app/models/layout.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Una plantilla agrupa metadatos que va a tener un artículo +Layout = Struct.new(:site, :name, :metadata, keyword_init: true) diff --git a/app/models/metadata_array.rb b/app/models/metadata_array.rb new file mode 100644 index 0000000..06bd947 --- /dev/null +++ b/app/models/metadata_array.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Una lista de valores +class MetadataArray < MetadataTemplate + # El valor por defecto es una array vacía + def default_value + [] + end +end diff --git a/app/models/metadata_factory.rb b/app/models/metadata_factory.rb new file mode 100644 index 0000000..d7a2e03 --- /dev/null +++ b/app/models/metadata_factory.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Devuelve metadatos de cierto tipo +class MetadataFactory + def self.build(**args) + "Metadata#{args[:type].camelcase}".constantize.new(args) + end +end diff --git a/app/models/metadata_image.rb b/app/models/metadata_image.rb new file mode 100644 index 0000000..817914d --- /dev/null +++ b/app/models/metadata_image.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Define un campo de imagen +class MetadataImage < MetadataTemplate + # Una ruta vacía a la imagen con una descripción vacía + def default_value + { path: '', description: '' } + end + + def empty? + value == default_value + end +end diff --git a/app/models/metadata_string.rb b/app/models/metadata_string.rb new file mode 100644 index 0000000..4cc2ff1 --- /dev/null +++ b/app/models/metadata_string.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Un campo de texto +class MetadataString < MetadataTemplate + # Una string vacía + def default_value + '' + end +end diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb new file mode 100644 index 0000000..8d13daa --- /dev/null +++ b/app/models/metadata_template.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# Representa la plantilla de un campo en los metadatos del artículo +MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, + :value, :help, :required, + keyword_init: true) do + # El valor por defecto + def default_value + raise NotImplementedError + end + + # Valores posibles, busca todos los valores actuales en otros + # artículos del mismo sitio + def values + site.everything_of(name.to_s) + end + + # Valor actual o por defecto + def value + super || document.data.dig(name.to_s, default_value) + end +end diff --git a/doc/posts.md b/doc/posts.md new file mode 100644 index 0000000..f99e08e --- /dev/null +++ b/doc/posts.md @@ -0,0 +1,96 @@ +# Posts + +Los posts no corresponden con un modelo en la base de datos, porque se +nos va a complicar sincronizar los datos con el repositorio. + +## Jekyll + +Como en Jekyll, los artículos son archivos con el siguiente formato: + +* Ubicación: _lang/ en lugar de guardar los archivos en _posts, usamos + i18n y guardamos cada uno en el directorio de su propio idioma. + +* Nombre: el nombre del archivo contiene la fecha en formato aaaa-mm-dd, + seguidos de un guión, el título en minúsculas y con los espacios + convertidos en guiones. La extensión es .markdown. + + Esto también funciona como identificador único. + +* Metadatos: Los metadatos del artículo se vuelcan en formato YAML al + principio del archivo, el bloque comienza y termina con tres guiones + ("---") en líneas separadas + +* Contenido: El contenido del artículo va a continuación de los + metadatos, en formato Markdown + +## I18n + +Todos los artículos están listos para ser traducidos. Esto ya no es +opcional como antes porque nos genera muchos casos especiales. + +La i18n incluye una lista de los nombres de archivo que identifican a +los posts en otros idiomas, para intervincularlos. + +## Plantillas de metadatos + +Todos los campos de la sección de metadatos son plantillas, incluso para +los artículos, hay una plantilla por defecto con los campos más comunes. + +Esto nos permite adaptar distintos tipos de artículos y unificar todo el +desarrollo en una sola lógica. + +El sitio contiene una descripción de todas las plantillas. Cuando se +crea un artículo del tipo correspondiente con la plantilla, el artículo +hereda todas las plantillas como atributos y valida la presencia de los +obligatorios y los tipos de datos incorporados. + +## Archivos + +El nombre de la plantilla de metadatos corresponde con una plantilla +HTML: + +```bash +_data/templates/post.yml +_layouts/post.html +``` + +Al crear el artículo, el metadato `layout:` se completa con el nombre de +la plantilla. Todavía no tenemos un método para cambiar un artículo de +plantilla, ya que requiriría recargar todos los campos. + +## Implementación + +La clase `MetadataTemplate` representa los valores posibles de un +metadato abstracto. Cada tipo de valor específico tiene su +`MetadataType` donde `Type` es el tipo de metadato. Esto +permite que cada tipo sepa como cargar sus valores por defecto y demás. + +La clase `Template` contiene un hash de `{ campo: MetadataTemplate }`. +Su nombre es el nombre de la plantilla. + +`Site#templates` contiene un hash de `{ nombre: Template }` para +búsqueda rápida. + +`Post#template` es el `Site#templates[:template]` correspondiente. + +`Post` es un tipo de `OpenStruct` que valida los valores contra las +plantillas de `Post#template`. Además, puede obtener valores por +defecto. Para esto, se lo construye con el sitio y plantilla. A partir +de esto se generan los `MetadataType` correspondientes, que son +los capaces de generar sus propios valores por defecto. + +De dónde obtienen los valores actuales los `MetadataType`? + +Cada atributo es una instancia de `PostMetadataType`. + +`Post#title` trae el título desde los metadatos leídos o el título por +defecto. `Post#title.values` trae los valores posibles. + +Los `Post` se generan desde `Site#posts` que es una relación, al estilo +ActiveRecord. + +Al instanciar un `Post`, se pasan el sitio y la plantilla por defecto. + +## TODO + +* Leer artículos a medida que se los necesita en lugar de todos juntos.