el slug y la fecha son campos tambien

This commit is contained in:
f 2019-08-07 18:35:37 -03:00
parent ad1ae26e4d
commit ada14b2294
No known key found for this signature in database
GPG key ID: 2AE5A13E321F953D
6 changed files with 177 additions and 67 deletions

View file

@ -0,0 +1,11 @@
# Maneja la fecha del document
class MetadataDocumentDate < MetadataTemplate
# La fecha por defecto es ahora!
def default_value
Date.today.to_time
end
def value
self[:value] || document.date || default_value
end
end

View file

@ -0,0 +1,42 @@
require 'jekyll/utils'
# El slug es el nombre del archivo sin la fecha ni la extensión y se
# deriva del título.
#
# Si el slug del document es distinto al que le asignaría el título
# slugificado, quiere decir que lo indicamos manualmente y no hay que
# cambiarlo.
#
# Pero si cambiamos el slug manualmente, tenemos que darle prioridad a
# ese cambio.
#
# El slug por defecto es el asignado por el documento.
#
# Si no hay slug, slugificamos el título.
#
# Si cambiamos el título y el slug coincide con el slug del título
# anterior, también lo cambiamos para mantener las URLs consistentes.
# Luego podemos usar el plugin que guarda los slugs anteriores para
# generar links.
#
# TODO: Transliterar tildes?
class MetadataSlug < MetadataTemplate
# Trae el slug desde el título si existe o una string al azar
def default_value
if title
Jekyll::Utils.slugify(title)
else
SecureRandom.hex
end
end
def value
self[:value] || document.data.fetch('slug', default_value)
end
private
def title
layout.metadata[:title].try(:value)
end
end

View file

@ -5,7 +5,7 @@
# TODO: Validar el tipo de valor pasado a value= según el :type
MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
:value, :help, :required, :errors,
keyword_init: true) do
:layout, keyword_init: true) do
# El valor por defecto
def default_value
raise NotImplementedError

View file

@ -36,9 +36,7 @@ class Post < OpenStruct
# TODO: Mover a su propia clase para poder hacer limpiezas
# independientemente
self.content = document.content
self.date = document.date
# TODO: idem content
self.slug = document.data['slug']
self.errors = {}
# Genera un atributo por cada uno de los campos de la plantilla,
# MetadataFactory devuelve un tipo de campo por cada campo. A
@ -49,11 +47,18 @@ class Post < OpenStruct
MetadataFactory.build(document: document,
site: site,
name: name,
layout: layout,
type: template['type'],
label: template['label'],
help: template['help'],
required: template['required'])
end
load_slug!
load_date!
# Leer el documento
read
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
@ -81,7 +86,15 @@ class Post < OpenStruct
method: mid)
end
# Definir los attribute_*
new_attribute_was(mid)
new_attribute_changed(mid)
# OpenStruct
super(mid, *args)
# Devolver lo mismo que devuelve el método después de definirlo
send(mid, *args)
end
# Detecta si es un atributo válido o no, a partir de la tabla de la
@ -137,11 +150,23 @@ class Post < OpenStruct
end
alias save! save
# Lee el documento
def read
Dir.chdir(site.path) do
document.read
end
end
# Devuelve la ruta del post, si se cambió alguno de los datos,
# generamos una ruta nueva para tener siempre la ruta actualizada.
def path
document.path
end
alias relative_path path
def absolute_path
File.join site.path, path
end
# Detecta si el artículo es válido para guardar
def valid?
@ -161,22 +186,6 @@ class Post < OpenStruct
end
alias validate! validate
def basename_changed?
@post.try(:basename) != basename_from_front_matter
end
def slug_changed?
new? || @post.data.dig('slug') != slug
end
# devuelve las plantillas como strong params, primero los valores
# simples, luego los arrays y al final los hashes
def template_params
@template_params ||= template_fields.map(&:to_param).sort_by do |s|
s.is_a?(Symbol) ? 0 : 1
end
end
# Solo agregar el post al sitio una vez que lo guardamos
#
# TODO no sería la forma correcta de hacerlo en Rails
@ -211,17 +220,6 @@ class Post < OpenStruct
# new_post
# end
def cleanup!
default_date_is_today!
clean_content!
slugify_title!
end
# Aplica limpiezas básicas del contenido
def clean_content!
content.try(:delete!, "\r")
end
# Guarda los cambios en el archivo destino
def write
return true if persisted?
@ -243,22 +241,57 @@ class Post < OpenStruct
# Verifica si hace falta escribir cambios
def persisted?
File.exist?(path) && full_content == File.read(path)
File.exist?(absolute_path) && full_content == File.read(absolute_path)
end
private
def default_date_is_today!
self.date ||= Time.now
def new_attribute_was(method)
attr_was = (attribute_name(method).to_s + '_was').to_sym
return attr_was if singleton_class.method_defined? attr_was
define_singleton_method(attr_was) do
name = attribute_name(attr_was)
name == :content ? document.content : document.data[name.to_s]
end
attr_was
end
def slugify_title!
self.slug = Jekyll::Utils.slugify(title) if slug.blank?
end
# Pregunta si el atributo cambió
# rubocop:disable Metrics/AbcSize
def new_attribute_changed(method)
attr_changed = (attribute_name(method).to_s + '_changed?').to_sym
# Obtiene el nombre del atributo sin
return attr_changed if singleton_class.method_defined? attr_changed
define_singleton_method(attr_changed) do
name = attribute_name(attr_changed)
name_was = (name.to_s + '_was').to_sym
(send(name).try(:value) || send(name)) != send(name_was)
end
end
# rubocop:enable Metrics/AbcSize
# Obtiene el nombre del atributo a partir del nombre del método
def attribute_name(attr)
attr.to_s.split('=').first.to_sym
# XXX: Los simbolos van al final
%w[_was _changed? ? =].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,
required: true)
end
def load_date!
self.date = MetadataDocumentDate.new(document: document, site: site,
layout: layout, name: :date,
required: true)
end
end
# rubocop:enable Metrics/ClassLength

View file

@ -100,4 +100,3 @@ Al instanciar un `Post`, se pasan el sitio y la plantilla por defecto.
* Detectar cambio de nombre (fecha y slug)
* Crear artículos nuevos
* Convertir layout a params
* Guardar cambios

View file

@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'test_helper'
class PostTest < ActiveSupport::TestCase
setup do
# Trabajamos con el sitio de sutty porque tiene artículos
@ -41,36 +43,24 @@ class PostTest < ActiveSupport::TestCase
FileUtils.mv tmp, @post.path
end
test 'se puede ver el contenido completo' do
tmp = Tempfile.new
test 'se puede ver el contenido completo después de guardar' do
assert @post.save
@post.document.read
begin
tmp.write(@post.full_content)
tmp.close
# Queremos saber si todos los atributos del post terminaron en el
# archivo
@post.attributes.each do |attr|
metadata = @post.send(attr)
# ignorar atributos que no son datos
next unless metadata.is_a? MetadataTemplate
collection = Jekyll::Collection.new(@site.jekyll, I18n.locale.to_s)
document = Jekyll::Document.new(tmp.path, site: @site.jekyll,
collection: collection)
document.read
document.data['categories'].try(:delete_if) do |x|
x == 'tmp'
if metadata.empty?
assert_not @post.document.data[attr.to_s].present?
elsif attr == :date
assert_equal metadata.value, @post.document.date
else
assert_equal metadata.value, @post.document.data[attr.to_s]
end
# Queremos saber si todos los atributos del post terminaron en el
# archivo
@post.attributes.each do |attr|
template = @post.send(attr)
# ignorar atributos que no son datos
next unless template.is_a? MetadataTemplate
if template.empty?
assert_not document.data[attr.to_s].present?
else
assert_equal template.value, document.data[attr.to_s]
end
end
ensure
tmp.unlink
end
end
@ -103,4 +93,39 @@ class PostTest < ActiveSupport::TestCase
assert_equal 'post', document.data['layout']
end
end
test 'attribute_name' do
assert_equal :hola, @post.send(:attribute_name, :hola)
assert_equal :hola, @post.send(:attribute_name, :hola?)
assert_equal :hola, @post.send(:attribute_name, :hola_was)
assert_equal :hola, @post.send(:attribute_name, :hola_changed?)
end
test 'se puede cambiar el slug' do
assert_equal @post.slug_was, @post.slug.value
assert_not @post.slug_changed?
assert @post.slug.valid?
ex_slug = @post.slug.value
@post.slug.value = SecureRandom.hex
assert_not_equal ex_slug, @post.slug.value
assert_equal ex_slug, @post.slug_was
assert @post.slug_changed?
assert @post.slug.valid?
end
test 'se puede cambiar la fecha' do
assert_equal @post.date_was, @post.date.value
assert_not @post.date_changed?
assert @post.date.valid?
ex_date = @post.date.value
@post.date.value = 2.days.ago
assert_not_equal ex_date, @post.date.value
assert_equal ex_date, @post.date_was
assert @post.date_changed?
assert @post.date.valid?
end
end