mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-23 01:46:21 +00:00
guardar cambios en post
This commit is contained in:
parent
efe401d740
commit
ad1ae26e4d
3 changed files with 73 additions and 170 deletions
|
@ -6,6 +6,8 @@ require 'jekyll/utils'
|
||||||
# para modificarlos y crear nuevos.
|
# para modificarlos y crear nuevos.
|
||||||
#
|
#
|
||||||
# rubocop:disable Metrics/ClassLength
|
# rubocop:disable Metrics/ClassLength
|
||||||
|
# rubocop:disable Style/MethodMissingSuper
|
||||||
|
# rubocop:disable Style/MissingRespondToMissing
|
||||||
class Post < OpenStruct
|
class Post < OpenStruct
|
||||||
# Atributos por defecto
|
# Atributos por defecto
|
||||||
# XXX: Volver document opcional cuando estemos creando
|
# XXX: Volver document opcional cuando estemos creando
|
||||||
|
@ -31,8 +33,11 @@ class Post < OpenStruct
|
||||||
layout.metadata.keys.map(&:to_sym)
|
layout.metadata.keys.map(&:to_sym)
|
||||||
|
|
||||||
# El contenido
|
# El contenido
|
||||||
|
# TODO: Mover a su propia clase para poder hacer limpiezas
|
||||||
|
# independientemente
|
||||||
self.content = document.content
|
self.content = document.content
|
||||||
self.date = document.date
|
self.date = document.date
|
||||||
|
# TODO: idem content
|
||||||
self.slug = document.data['slug']
|
self.slug = document.data['slug']
|
||||||
|
|
||||||
# Genera un atributo por cada uno de los campos de la plantilla,
|
# Genera un atributo por cada uno de los campos de la plantilla,
|
||||||
|
@ -70,8 +75,6 @@ class Post < OpenStruct
|
||||||
#
|
#
|
||||||
# XXX: rubocop dice que tenemos que usar super cuando ya lo estamos
|
# XXX: rubocop dice que tenemos que usar super cuando ya lo estamos
|
||||||
# usando...
|
# usando...
|
||||||
#
|
|
||||||
# rubocop:disable Style/MethodMissingSuper
|
|
||||||
def method_missing(mid, *args)
|
def method_missing(mid, *args)
|
||||||
unless attribute? mid
|
unless attribute? mid
|
||||||
raise NoMethodError, I18n.t('exceptions.post.no_method',
|
raise NoMethodError, I18n.t('exceptions.post.no_method',
|
||||||
|
@ -80,7 +83,6 @@ class Post < OpenStruct
|
||||||
|
|
||||||
super(mid, *args)
|
super(mid, *args)
|
||||||
end
|
end
|
||||||
# rubocop:enable Style/MethodMissingSuper
|
|
||||||
|
|
||||||
# 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
|
||||||
# plantilla
|
# plantilla
|
||||||
|
@ -93,6 +95,7 @@ class Post < OpenStruct
|
||||||
end
|
end
|
||||||
|
|
||||||
# Genera el post con metadatos en YAML
|
# Genera el post con metadatos en YAML
|
||||||
|
# rubocop:disable Metrics/AbcSize
|
||||||
def full_content
|
def full_content
|
||||||
yaml = layout.metadata.keys.map(&:to_sym).map do |metadata|
|
yaml = layout.metadata.keys.map(&:to_sym).map do |metadata|
|
||||||
template = send(metadata)
|
template = send(metadata)
|
||||||
|
@ -100,6 +103,9 @@ class Post < OpenStruct
|
||||||
{ metadata.to_s => template.value } unless template.empty?
|
{ metadata.to_s => template.value } unless template.empty?
|
||||||
end.compact.inject(:merge)
|
end.compact.inject(:merge)
|
||||||
|
|
||||||
|
# Asegurarse que haya un layout
|
||||||
|
yaml['layout'] = layout.name.to_s
|
||||||
|
|
||||||
"#{yaml.to_yaml}---\n\n#{content}"
|
"#{yaml.to_yaml}---\n\n#{content}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -117,23 +123,16 @@ class Post < OpenStruct
|
||||||
!File.exist?(path) && !site.posts(lang: lang).include?(self)
|
!File.exist?(path) && !site.posts(lang: lang).include?(self)
|
||||||
end
|
end
|
||||||
alias destroy! destroy
|
alias destroy! destroy
|
||||||
|
# rubocop:enable Metrics/AbcSize
|
||||||
|
|
||||||
# Guarda los cambios.
|
# Guarda los cambios
|
||||||
#
|
|
||||||
# Recién cuando vamos a guardar creamos el Post, porque ya tenemos
|
|
||||||
# todos los datos para escribir el archivo, que es la condición
|
|
||||||
# necesaria para poder crearlo :P
|
|
||||||
def save
|
def save
|
||||||
cleanup!
|
|
||||||
|
|
||||||
return false unless valid?
|
return false unless valid?
|
||||||
|
return false unless write
|
||||||
return unless write
|
|
||||||
return unless detect_file_rename!
|
|
||||||
|
|
||||||
# Vuelve a leer el post para tomar los cambios
|
# Vuelve a leer el post para tomar los cambios
|
||||||
document.read
|
document.read
|
||||||
# add_post_to_site!
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
alias save! save
|
alias save! save
|
||||||
|
@ -162,16 +161,6 @@ class Post < OpenStruct
|
||||||
end
|
end
|
||||||
alias validate! validate
|
alias validate! validate
|
||||||
|
|
||||||
# Permite ordenar los posts
|
|
||||||
def <=>(other)
|
|
||||||
@post <=> other.post
|
|
||||||
end
|
|
||||||
|
|
||||||
# ****************
|
|
||||||
# A PARTIR DE ACA ESTAMOS CONSIDERANDO CUALES METODOS QUEDAN Y CUALES
|
|
||||||
# NO
|
|
||||||
# ****************
|
|
||||||
|
|
||||||
def basename_changed?
|
def basename_changed?
|
||||||
@post.try(:basename) != basename_from_front_matter
|
@post.try(:basename) != basename_from_front_matter
|
||||||
end
|
end
|
||||||
|
@ -180,17 +169,6 @@ class Post < OpenStruct
|
||||||
new? || @post.data.dig('slug') != slug
|
new? || @post.data.dig('slug') != slug
|
||||||
end
|
end
|
||||||
|
|
||||||
# Detecta si un valor es un archivo
|
|
||||||
def url?(name)
|
|
||||||
path = get_front_matter(name)
|
|
||||||
return false unless path.is_a?(String) || path.is_a?(Array)
|
|
||||||
|
|
||||||
# El primer valor es '' porque la URL empieza con /
|
|
||||||
[path].flatten.map do |p|
|
|
||||||
p.split('/').second == 'public'
|
|
||||||
end.all?
|
|
||||||
end
|
|
||||||
|
|
||||||
# devuelve las plantillas como strong params, primero los valores
|
# devuelve las plantillas como strong params, primero los valores
|
||||||
# simples, luego los arrays y al final los hashes
|
# simples, luego los arrays y al final los hashes
|
||||||
def template_params
|
def template_params
|
||||||
|
@ -199,22 +177,6 @@ class Post < OpenStruct
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Completa el front_matter a partir de las variables de otro post que
|
|
||||||
# le sirve de plantilla
|
|
||||||
def front_matter_from_template
|
|
||||||
# XXX: Llamamos a @template en lugar de template porque sino
|
|
||||||
# entramos en una race condition
|
|
||||||
return {} unless @template
|
|
||||||
|
|
||||||
ft = template_fields.map(&:to_front_matter).reduce({}, :merge)
|
|
||||||
# Convertimos el slug en layout
|
|
||||||
ft['layout'] = template.slug
|
|
||||||
|
|
||||||
ft
|
|
||||||
end
|
|
||||||
|
|
||||||
# Solo agregar el post al sitio una vez que lo guardamos
|
# Solo agregar el post al sitio una vez que lo guardamos
|
||||||
#
|
#
|
||||||
# TODO no sería la forma correcta de hacerlo en Rails
|
# TODO no sería la forma correcta de hacerlo en Rails
|
||||||
|
@ -242,56 +204,17 @@ class Post < OpenStruct
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reemplaza el post en el sitio por uno nuevo
|
# Reemplaza el post en el sitio por uno nuevo
|
||||||
def replace_post!
|
# TODO: refactorizar
|
||||||
@old_post = @site.jekyll.collections[@lang].docs.delete @post
|
# def replace_post!
|
||||||
|
# @old_post = @site.jekyll.collections[@lang].docs.delete @post
|
||||||
|
|
||||||
new_post
|
# new_post
|
||||||
end
|
# end
|
||||||
|
|
||||||
# Obtiene el nombre del archivo a partir de los datos que le
|
|
||||||
# pasemos
|
|
||||||
def basename_from_front_matter
|
|
||||||
ext = get_front_matter('ext') || '.markdown'
|
|
||||||
|
|
||||||
"#{date_as_string}-#{slug}#{ext}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Toma los datos del front matter local y los mueve a los datos
|
|
||||||
# que van a ir al post. Si hay símbolos se convierten a cadenas,
|
|
||||||
# porque Jekyll trabaja con cadenas. Se excluyen otros datos que no
|
|
||||||
# van en el frontmatter
|
|
||||||
def merge_with_front_matter!(params)
|
|
||||||
@front_matter.merge! Hash[params.to_hash.map do |k, v|
|
|
||||||
[k, v] unless REJECT_FROM_DATA.include? k
|
|
||||||
end.compact]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Carga una copia de los datos del post original excluyendo datos
|
|
||||||
# que no nos interesan
|
|
||||||
def load_front_matter!
|
|
||||||
@front_matter = @post.data.reject do |key, _|
|
|
||||||
REJECT_FROM_DATA.include? key
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def cleanup!
|
def cleanup!
|
||||||
default_date_is_today!
|
default_date_is_today!
|
||||||
clean_content!
|
clean_content!
|
||||||
slugify_title!
|
slugify_title!
|
||||||
update_translations!
|
|
||||||
put_in_order!
|
|
||||||
create_glossary!
|
|
||||||
end
|
|
||||||
|
|
||||||
# Busca las traducciones y actualiza el frontmatter si es necesario
|
|
||||||
def update_translations!
|
|
||||||
return unless translated?
|
|
||||||
return unless slug_changed?
|
|
||||||
|
|
||||||
find_translations.each do |post|
|
|
||||||
post.update_attributes(lang: get_front_matter('lang'))
|
|
||||||
post.save
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Aplica limpiezas básicas del contenido
|
# Aplica limpiezas básicas del contenido
|
||||||
|
@ -301,87 +224,43 @@ class Post < OpenStruct
|
||||||
|
|
||||||
# Guarda los cambios en el archivo destino
|
# Guarda los cambios en el archivo destino
|
||||||
def write
|
def write
|
||||||
r = File.open(path, File::RDWR | File::CREAT, 0o640) do |f|
|
return true if persisted?
|
||||||
# Bloquear el archivo para que no sea accedido por otro
|
|
||||||
# proceso u otra editora
|
|
||||||
f.flock(File::LOCK_EX)
|
|
||||||
|
|
||||||
# Empezar por el principio
|
Site::Writer.new(site: site, file: path,
|
||||||
f.rewind
|
content: full_content, usuarie: usuarie,
|
||||||
|
message: title.value).save
|
||||||
# Escribir
|
|
||||||
f.write(full_content)
|
|
||||||
|
|
||||||
# Eliminar el resto
|
|
||||||
f.flush
|
|
||||||
f.truncate(f.pos)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return true if r.zero?
|
# Devuelve le autore de un artículo
|
||||||
|
# XXX: Si se cambia le autore en el editor también cambia quién hace
|
||||||
add_error file: I18n.t('posts.errors.file')
|
# un cambio y se le puede asignar a cualquier otre.
|
||||||
false
|
# TODO: Mover la escritura a un servicio que combine escritura, commit
|
||||||
|
# y current_usuarie
|
||||||
|
def usuarie
|
||||||
|
OpenStruct.new(email: author.value.first,
|
||||||
|
name: author.value.first.split('@', 2).first)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_error(hash)
|
# Verifica si hace falta escribir cambios
|
||||||
hash.each_pair do |k, i|
|
def persisted?
|
||||||
@errors[k] = if @errors.key?(k)
|
File.exist?(path) && full_content == File.read(path)
|
||||||
[@errors[k], i]
|
|
||||||
else
|
|
||||||
i
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@errors
|
private
|
||||||
end
|
|
||||||
|
|
||||||
def default_date_is_today!
|
def default_date_is_today!
|
||||||
date ||= Time.now
|
self.date ||= Time.now
|
||||||
end
|
end
|
||||||
|
|
||||||
def slugify_title!
|
def slugify_title!
|
||||||
self.slug = Jekyll::Utils.slugify(title) if slug.blank?
|
self.slug = Jekyll::Utils.slugify(title) if slug.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Agregar al final de la cola si no especificamos un orden
|
|
||||||
#
|
|
||||||
# TODO si el artículo tiene una fecha que lo coloca en medio de
|
|
||||||
# la colección en lugar de al final, deberíamos reordenar?
|
|
||||||
def put_in_order!
|
|
||||||
return unless order.nil?
|
|
||||||
|
|
||||||
@front_matter['order'] = @site.posts_for(@collection).count
|
|
||||||
end
|
|
||||||
|
|
||||||
# Crea el artículo de glosario para cada categoría o tag
|
|
||||||
def create_glossary!
|
|
||||||
return unless site.glossary?
|
|
||||||
|
|
||||||
%i[tags categories].each do |i|
|
|
||||||
send(i).each do |c|
|
|
||||||
# TODO: no hardcodear, hacer _configurable
|
|
||||||
next if c == 'Glossary'
|
|
||||||
next if site.posts.find do |p|
|
|
||||||
p.title == c
|
|
||||||
end
|
|
||||||
|
|
||||||
glossary = Post.new(site: site, lang: lang)
|
|
||||||
glossary.update_attributes(
|
|
||||||
title: c,
|
|
||||||
layout: 'glossary',
|
|
||||||
categories: 'Glossary'
|
|
||||||
)
|
|
||||||
|
|
||||||
glossary.save!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Obtiene el nombre del atributo sin
|
# Obtiene el nombre del atributo sin
|
||||||
def attribute_name(attr)
|
def attribute_name(attr)
|
||||||
attr.to_s.split('=').first.to_sym
|
attr.to_s.split('=').first.to_sym
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/ClassLength
|
# rubocop:enable Metrics/ClassLength
|
||||||
|
# rubocop:enable Style/MethodMissingSuper
|
||||||
|
# rubocop:enable Style/MissingRespondToMissing
|
||||||
|
|
|
@ -94,3 +94,10 @@ Al instanciar un `Post`, se pasan el sitio y la plantilla por defecto.
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* Leer artículos a medida que se los necesita en lugar de todos juntos.
|
* Leer artículos a medida que se los necesita en lugar de todos juntos.
|
||||||
|
* Reimplementar glosario (se crea un artículo por cada categoría
|
||||||
|
utilizada)
|
||||||
|
* Reimplementar orden de artículos (ver doc)
|
||||||
|
* Detectar cambio de nombre (fecha y slug)
|
||||||
|
* Crear artículos nuevos
|
||||||
|
* Convertir layout a params
|
||||||
|
* Guardar cambios
|
||||||
|
|
|
@ -85,5 +85,22 @@ class PostTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'se pueden guardar los cambios' do
|
test 'se pueden guardar los cambios' do
|
||||||
|
title = SecureRandom.hex
|
||||||
|
@post.title.value = title
|
||||||
|
@post.categories.value << title
|
||||||
|
|
||||||
|
assert @post.save
|
||||||
|
|
||||||
|
Dir.chdir(@site.path) do
|
||||||
|
collection = Jekyll::Collection.new(@site.jekyll, I18n.locale.to_s)
|
||||||
|
document = Jekyll::Document.new(@post.path,
|
||||||
|
site: @site.jekyll,
|
||||||
|
collection: collection)
|
||||||
|
document.read
|
||||||
|
|
||||||
|
assert document.data['categories'].include?(title)
|
||||||
|
assert_equal title, document.data['title']
|
||||||
|
assert_equal 'post', document.data['layout']
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue