5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-11-26 15:46:22 +00:00

guardar cambios en post

This commit is contained in:
f 2019-08-07 12:10:14 -03:00
parent efe401d740
commit ad1ae26e4d
No known key found for this signature in database
GPG key ID: 2AE5A13E321F953D
3 changed files with 73 additions and 170 deletions

View file

@ -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,34 +177,18 @@ 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
# def add_post_to_site! # def add_post_to_site!
# @site.jekyll.collections[@collection].docs << @post # @site.jekyll.collections[@collection].docs << @post
# @site.jekyll.collections[@collection].docs.sort! # @site.jekyll.collections[@collection].docs.sort!
# unless @site.collections[@collection].include? self # unless @site.collections[@collection].include? self
# @site.collections[@collection] << self # @site.collections[@collection] << self
# @site.collections[@collection].sort! # @site.collections[@collection].sort!
# end # end
# end # end
# Cambiar el nombre del archivo si cambió el título o la fecha. # Cambiar el nombre del archivo si cambió el título o la fecha.
# Como Jekyll no tiene métodos para modificar un Document, lo # Como Jekyll no tiene métodos para modificar un Document, lo
@ -238,60 +200,21 @@ class Post < OpenStruct
Rails.logger.info I18n.t('posts.logger.rm', path: path) Rails.logger.info I18n.t('posts.logger.rm', path: path)
FileUtils.rm @post.path FileUtils.rm @post.path
# replace_post! # replace_post!
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

View file

@ -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

View file

@ -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