5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-11-24 17:06:23 +00:00

Merge branch 'rails' of 0xacab.org:sutty/sutty into rails

This commit is contained in:
f 2021-05-17 13:15:21 -03:00
commit c3d7dc317c
22 changed files with 364 additions and 58 deletions

View file

@ -72,7 +72,8 @@ class SitesController < ApplicationController
authorize site
# XXX: Convertir en una máquina de estados?
DeployJob.perform_async site.id if site.enqueue!
site.enqueue!
DeployJob.perform_async site.id
redirect_to site_posts_path(site, locale: site.default_locale)
end

View file

@ -8,13 +8,20 @@ class DeployJob < ApplicationJob
def perform(site, notify = true)
ActiveRecord::Base.connection_pool.with_connection do
@site = Site.find(site)
@site.update_attribute :status, 'building'
# Si ya hay una tarea corriendo, aplazar esta
if @site.building?
DeployJob.perform_in(60, site, notify)
return
end
@site.update status: 'building'
# Asegurarse que DeployLocal sea el primero!
@deployed = { deploy_local: deploy_locally }
# No es opcional
unless @deployed[:deploy_local]
@site.update_attribute :status, 'waiting'
@site.update status: 'waiting'
notify_usuaries if notify
# Hacer fallar la tarea
@ -23,7 +30,7 @@ class DeployJob < ApplicationJob
deploy_others
notify_usuaries if notify
@site.update_attribute :status, 'waiting'
@site.update status: 'waiting'
end
end
# rubocop:enable Metrics/MethodLength

View file

@ -3,13 +3,6 @@
# Almacena el UUID de otro Post y actualiza el valor en el Post
# relacionado.
class MetadataBelongsTo < MetadataRelatedPosts
def value_was=(new_value)
@belongs_to = nil
@belonged_to = nil
super(new_value)
end
# TODO: Convertir algunos tipos de valores en módulos para poder
# implementar varios tipos de campo sin repetir código
#
@ -39,10 +32,14 @@ class MetadataBelongsTo < MetadataRelatedPosts
# Si estamos cambiando la relación, tenemos que eliminar la relación
# anterior
belonged_to[inverse].value.delete post.uuid.value if changed? && belonged_to.present?
if belonged_to.present?
belonged_to[inverse].value = belonged_to[inverse].value.reject do |rej|
rej == post.uuid.value
end
end
# No duplicar las relaciones
belongs_to[inverse].value << post.uuid.value unless belongs_to.blank? || included?
belongs_to[inverse].value = (belongs_to[inverse].value.dup << post.uuid.value) unless belongs_to.blank? || included?
true
end
@ -63,20 +60,13 @@ class MetadataBelongsTo < MetadataRelatedPosts
end
# El Post relacionado con este artículo
#
# XXX: Memoizamos usando el valor para tener el valor siempre
# actualizado.
def belongs_to
return if value.blank?
@belongs_to ||= posts.find(value, uuid: true)
posts.find(value, uuid: true) if value.present?
end
# El artículo relacionado anterior
def belonged_to
return if value_was.blank?
@belonged_to ||= posts.find(value_was, uuid: true)
posts.find(value_was, uuid: true) if value_was.present?
end
def related_posts?

View file

@ -8,16 +8,52 @@ class MetadataDocumentDate < MetadataTemplate
end
def value_from_document
return nil if post.new?
document.date
end
# Siempre es obligatorio
def required
true
end
def validate
super
errors << I18n.t('metadata.date.invalid_format') unless valid_format?
errors.empty?
end
# El valor puede ser un Date, Time o una String en el formato
# "yyyy-mm-dd"
#
# XXX: Date.iso8601 acepta fechas en el futuro lejano, como 20000,
# pero Jekyll las limita a cuatro cifras, así que vamos a mantener
# eso.
#
# @see {https://github.com/jekyll/jekyll/blob/master/lib/jekyll/document.rb#L15}
def value
return (self[:value] = value_from_document || default_value) if self[:value].nil?
self[:value] = Date.iso8601(self[:value]).to_time if self[:value].is_a? String
self[:value]
self[:value] =
case self[:value]
when String
begin
Date.iso8601(self[:value]).to_time
rescue Date::Error
value_from_document || default_value
end
else
self[:value] || value_from_document || default_value
end
end
private
def valid_format?
return true if self[:value].is_a?(Time)
@valid_format_re ||= /\A\d{2,4}-\d{1,2}-\d{1,2}\z/
@valid_format_re =~ self[:value].to_s
end
end

View file

@ -18,6 +18,7 @@ class MetadataHasAndBelongsToMany < MetadataHasMany
#
# Buscamos en belongs_to la relación local, si se eliminó hay que
# quitarla de la relación remota, sino hay que agregarla.
#
def save
# XXX: No usamos super
self[:value] = sanitize value
@ -25,27 +26,21 @@ class MetadataHasAndBelongsToMany < MetadataHasMany
return true unless changed?
return true unless inverse?
# XXX: Usamos asignación para aprovechar value= que setea el valor
# anterior en @value_was
(had_many - has_many).each do |remove|
remove[inverse]&.value&.delete post.uuid.value
remove[inverse].value = remove[inverse].value.reject do |rej|
rej == post.uuid.value
end
end
(has_many - had_many).each do |add|
next unless add[inverse]
next if add[inverse].value.include? post.uuid.value
add[inverse].value << post.uuid.value
add[inverse].value = (add[inverse].value.dup << post.uuid.value)
end
true
end
private
# Igual que en MetadataRelatedPosts
# TODO: Mover a un módulo
def sanitize(uuid)
super(uuid.map do |u|
u.to_s.gsub(/[^a-f0-9\-]/i, '')
end)
end
end

View file

@ -6,14 +6,6 @@
# Localmente tenemos un Array de UUIDs. Remotamente tenemos una String
# apuntando a un Post, que se mantiene actualizado como el actual.
class MetadataHasMany < MetadataRelatedPosts
# Invalidar la relación anterior
def value_was=(new_value)
@had_many = nil
@has_many = nil
super(new_value)
end
def validate
super
@ -24,14 +16,16 @@ class MetadataHasMany < MetadataRelatedPosts
# Todos los Post relacionados
def has_many
@has_many ||= posts.where(uuid: value)
return default_value if value.blank?
posts.where(uuid: value)
end
# La relación anterior
def had_many
return [] if value_was.blank?
return default_value if value_was.blank?
@had_many ||= posts.where(uuid: value_was)
posts.where(uuid: value_was)
end
def inverse?

View file

@ -22,7 +22,7 @@ class MetadataRelatedPosts < MetadataArray
# Obtiene todos los posts y opcionalmente los filtra
def posts
@posts ||= site.posts(lang: lang).where(**filter)
site.posts(lang: lang).where(**filter)
end
def title(post)

View file

@ -49,9 +49,7 @@ class Post
public_send(attr)&.value = args[attr] if args.key?(attr)
end
# XXX: No usamos Post#read porque a esta altura todavía no sabemos
# nada del Document
document.read! if File.exist? document.path
document.read! unless new?
end
def inspect
@ -230,12 +228,14 @@ class Post
template = public_send attr
unless template.front_matter?
body += "\n\n"
body += "\n\n" if body.present?
body += template.value
next
end
next if template.empty?
# Queremos mantener los Array en el resultado final para que
# siempre respondan a {% for %} en Liquid.
next if template.empty? && !template.value.is_a?(Array)
[attr.to_s, template.value]
end.compact.to_h
@ -285,6 +285,7 @@ class Post
return false unless write
# Vuelve a leer el post para tomar los cambios
document.reset
read
written?

View file

@ -93,8 +93,7 @@ class PostRelation < Array
def where(**args)
return self if args.empty?
@where ||= {}
@where[args.hash.to_s] ||= begin
begin
PostRelation.new(site: site, lang: lang).concat(select do |post|
result = args.map do |attr, value|
next unless post.attribute?(attr)

View file

@ -303,14 +303,25 @@ class Site < ApplicationRecord
# Poner en la cola de compilación
def enqueue!
!enqueued? && update_attribute(:status, 'enqueued')
update(status: 'enqueued') if waiting?
end
# Está en la cola de compilación?
#
# TODO: definir todos estos métodos dinámicamente, aunque todavía no
# tenemos una máquina de estados propiamente dicha.
def enqueued?
status == 'enqueued'
end
def waiting?
status == 'waiting'
end
def building?
status == 'building'
end
# Cargar el sitio Jekyll
#
# TODO: En lugar de leer todo junto de una vez, extraer la carga de

View file

@ -52,7 +52,7 @@ module Jekyll
# Solo lee los datos
def read_data
@site.data = DataReader.new(site).read(site.config["data_dir"])
@site.data = DataReader.new(site).read(site.config['data_dir'])
end
# Lee todos los artículos del sitio
@ -73,6 +73,15 @@ module Jekyll
Document.class_eval do
alias_method :read!, :read
def read; end
# Permitir restablecer el documento sin crear uno nuevo
def reset
@path = @extname = @has_yaml_header = @relative_path = nil
@basename_without_ext = @data = @basename = nil
@renderer = @url_placeholders = @url = nil
@to_liquid = @write_p = @excerpt_separator = @id = nil
@related_posts = @cleaned_relative_path = self.content = nil
end
end
# Prevenir la instanciación de Time

View file

@ -102,6 +102,13 @@ class SitesControllerTest < ActionDispatch::IntegrationTest
'index.html'))
end
test 'no se pueden encolar varias veces seguidas' do
assert_enqueued_jobs 2 do
post site_enqueue_url(@site), headers: @authorization
post site_enqueue_url(@site), headers: @authorization
end
end
test 'se pueden actualizar' do
name = SecureRandom.hex
design = Design.all.where.not(id: @site.design_id).sample

View file

@ -0,0 +1,2 @@
This is site where posts can have many authors and viceversa and posts
can be replies to others.

View file

@ -0,0 +1,2 @@
locales:
- en

View file

@ -0,0 +1,9 @@
---
title:
type: 'string'
required: true
posts:
type: 'has_and_belongs_to_many'
inverse: 'authors'
filter:
layout: 'post'

View file

@ -0,0 +1,23 @@
---
title:
type: 'string'
required: true
authors:
type: 'has_and_belongs_to_many'
inverse: 'posts'
filter:
layout: 'author'
posts:
type: 'has_many'
inverse: 'in_reply_to'
filter:
layout: 'post'
in_reply_to:
type: 'belongs_to'
inverse: 'posts'
filter:
layout: 'post'
recommended_posts:
type: 'related_posts'
filter:
layout: 'post'

View file

View file

@ -0,0 +1 @@
_en

View file

@ -0,0 +1,62 @@
# frozen_string_literal: true
require 'test_helper'
require_relative 'metadata_test'
class MetadataBelongsToTest < ActiveSupport::TestCase
include MetadataTest
test 'se pueden relacionar artículos' do
post = @site.posts.create(layout: :post, title: SecureRandom.hex)
reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post.uuid.value)
assert_equal post, reply.in_reply_to.belongs_to
assert_includes post.posts.has_many, reply
assert post.save
assert_equal reply.document.data['in_reply_to'], post.document.data['uuid']
assert_includes post.document.data['posts'], reply.document.data['uuid']
end
test 'se puede eliminar la relación' do
post = @site.posts.create(layout: :post, title: SecureRandom.hex)
reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post.uuid.value)
reply.in_reply_to.value = ''
assert reply.save
assert_not_equal post, reply.in_reply_to.belongs_to
assert_equal post, reply.in_reply_to.belonged_to
assert_nil reply.in_reply_to.belongs_to
assert_not_includes post.posts.has_many, reply
assert post.save
assert_nil reply.document.data['in_reply_to']
assert_not_includes post.document.data['posts'], reply.document.data['uuid']
end
test 'se puede cambiar la relación' do
post1 = @site.posts.create(layout: :post, title: SecureRandom.hex)
post2 = @site.posts.create(layout: :post, title: SecureRandom.hex)
reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post1.uuid.value)
reply.in_reply_to.value = post2.uuid.value
assert reply.save
assert_not_equal post1, reply.in_reply_to.belongs_to
assert_equal post1, reply.in_reply_to.belonged_to
assert_not_includes post1.posts.has_many, reply
assert_equal post2, reply.in_reply_to.belongs_to
assert_includes post2.posts.has_many, reply
assert post1.save
assert post2.save
assert_equal post2.document.data['uuid'], reply.document.data['in_reply_to']
assert_includes post2.document.data['posts'], reply.document.data['uuid']
assert_not_includes post1.document.data['posts'], reply.document.data['uuid']
end
end

View file

@ -0,0 +1,76 @@
# frozen_string_literal: true
require 'test_helper'
require_relative 'metadata_test'
class MetadataHasAndBelongsManyTest < ActiveSupport::TestCase
include MetadataTest
test 'se pueden relacionar artículos' do
author = @site.posts.create(layout: :author, title: SecureRandom.hex)
post = @site.posts.create(layout: :post, title: SecureRandom.hex)
post.authors.value = [author.uuid.value]
assert post.save
assert_includes author.posts.has_many, post
assert_includes post.authors.has_many, author
assert author.save
assert_includes author.document.data['posts'], post.document.data['uuid']
assert_includes post.document.data['authors'], author.document.data['uuid']
end
test 'se puede eliminar la relación' do
author = @site.posts.create(layout: :author, title: SecureRandom.hex)
post = @site.posts.create(layout: :post, title: SecureRandom.hex, authors: [author.uuid.value])
assert_includes post.authors.value, author.uuid.value
assert_includes author.posts.value, post.uuid.value
post.authors.value = []
assert post.save
assert_not_includes author.posts.has_many, post
assert_not_includes post.authors.has_many, author
assert_includes author.posts.had_many, post
assert_includes post.authors.had_many, author
assert author.save
assert_not_includes author.document.data['posts'], post.document.data['uuid']
assert_not_includes post.document.data['authors'], author.document.data['uuid']
end
test 'se puede cambiar la relación' do
author = @site.posts.create(layout: :author, title: SecureRandom.hex)
post1 = @site.posts.create(layout: :post, title: SecureRandom.hex, authors: [author.uuid.value])
post2 = @site.posts.create(layout: :post, title: SecureRandom.hex)
author.posts.value = [post2.uuid.value]
assert author.save
assert_not_includes author.posts.has_many, post1
assert_not_includes post1.authors.has_many, author
assert_includes author.posts.had_many, post1
assert_includes post1.authors.had_many, author
assert_not_includes author.posts.had_many, post2
assert_not_includes post2.authors.had_many, author
assert_includes author.posts.has_many, post2
assert_includes post2.authors.has_many, author
assert post1.save
assert post2.save
assert_not_includes author.document.data['posts'], post1.document.data['uuid']
assert_not_includes post1.document.data['authors'], author.document.data['uuid']
assert_includes author.document.data['posts'], post2.document.data['uuid']
assert_includes post2.document.data['authors'], author.document.data['uuid']
end
end

View file

@ -0,0 +1,62 @@
# frozen_string_literal: true
require 'test_helper'
require_relative 'metadata_test'
class MetadataHasManyTest < ActiveSupport::TestCase
include MetadataTest
test 'se pueden relacionar artículos' do
reply = @site.posts.create(layout: :post, title: SecureRandom.hex)
post = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value])
assert_equal post, reply.in_reply_to.belongs_to
assert_includes post.posts.has_many, reply
assert reply.save
assert_equal reply.document.data['in_reply_to'], post.document.data['uuid']
assert_includes post.document.data['posts'], reply.document.data['uuid']
end
test 'se puede eliminar la relación' do
reply = @site.posts.create(layout: :post, title: SecureRandom.hex)
post = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value])
post.posts.value = []
assert post.save
assert_not_equal post, reply.in_reply_to.belongs_to
assert_equal post, reply.in_reply_to.belonged_to
assert_nil reply.in_reply_to.belongs_to
assert_not_includes post.posts.has_many, reply
assert reply.save
assert_nil reply.document.data['in_reply_to']
assert_not_includes post.document.data['posts'], reply.document.data['uuid']
end
test 'se puede cambiar la relación' do
reply = @site.posts.create(layout: :post, title: SecureRandom.hex)
post1 = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value])
post2 = @site.posts.create(layout: :post, title: SecureRandom.hex)
reply.in_reply_to.value = post2.uuid.value
assert reply.save
assert_not_equal post1, reply.in_reply_to.belongs_to
assert_equal post1, reply.in_reply_to.belonged_to
assert_not_includes post1.posts.has_many, reply
assert_equal post2, reply.in_reply_to.belongs_to
assert_includes post2.posts.has_many, reply
assert post1.save
assert post2.save
assert_equal post2.document.data['uuid'], reply.document.data['in_reply_to']
assert_includes post2.document.data['posts'], reply.document.data['uuid']
assert_not_includes post1.document.data['posts'], reply.document.data['uuid']
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module MetadataTest
extend ActiveSupport::Concern
included do
setup do
name = SecureRandom.hex
# TODO: Poder cambiar el nombre
FileUtils.cp_r(Rails.root.join('test', 'fixtures', 'site_with_relationships'), Rails.root.join('_sites', name))
@site = create :site, name: name
end
teardown do
@site&.destroy
end
end
end