el slug y la fecha son campos tambien
This commit is contained in:
parent
ad1ae26e4d
commit
ada14b2294
6 changed files with 177 additions and 67 deletions
11
app/models/metadata_document_date.rb
Normal file
11
app/models/metadata_document_date.rb
Normal 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
|
42
app/models/metadata_slug.rb
Normal file
42
app/models/metadata_slug.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue