From 1be1977c343a6d6b6a9a379d9414872db9830f8b Mon Sep 17 00:00:00 2001 From: jazzari Date: Wed, 23 Aug 2023 14:13:25 -0300 Subject: [PATCH 01/26] fix: fix minima design's link to demostration site #14131 --- db/seeds/designs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds/designs.yml b/db/seeds/designs.yml index a04c99c1..46f1cd0d 100644 --- a/db/seeds/designs.yml +++ b/db/seeds/designs.yml @@ -18,7 +18,7 @@ - name_en: 'Minima' name_es: 'Mínima' gem: 'sutty-minima' - url: 'https://0xacab.org/sutty/jekyll/minima' + url: 'https://minima.sutty.nl/' description_en: "Sutty Minima is based on [Minima](https://jekyll.github.io/minima/), a blog-focused theme for Jekyll." description_es: 'Sutty Mínima es una plantilla para blogs basada en [Mínima](https://jekyll.github.io/minima/).' license: 'https://0xacab.org/sutty/jekyll/minima/-/blob/master/LICENSE.txt' From bfd31d051fb002e303ddcd0f253b1e2a7aa50f49 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 25 Sep 2023 14:15:32 -0300 Subject: [PATCH 02/26] =?UTF-8?q?fix:=20lanzar=20una=20excepci=C3=B3n=20co?= =?UTF-8?q?ntundente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/initializers/core_extensions.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index 2e9ed7e1..d6039e16 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -93,8 +93,8 @@ module Jekyll unless spec I18n.with_locale(locale) do - raise ArgumentError, I18n.t('activerecord.errors.models.site.attributes.design_id.missing_gem', theme: name) - rescue ArgumentError => e + raise Jekyll::Errors::InvalidThemeName, I18n.t('activerecord.errors.models.site.attributes.design_id.missing_gem', theme: name) + rescue Jekyll::Errors::InvalidThemeName => e ExceptionNotifier.notify_exception(e, data: { theme: name, site: File.basename(site.source) }) raise end From d601f370455ce1ff7dad48210d74899de8639280 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 09:54:25 -0300 Subject: [PATCH 03/26] =?UTF-8?q?feat:=20a=20partir=20del=20post=20indexad?= =?UTF-8?q?o=20obtener=20la=20ubicaci=C3=B3n=20del=20archivo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/indexed_post.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index 7f6865f6..7e58b7bc 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -36,6 +36,13 @@ class IndexedPost < ApplicationRecord belongs_to :site + # La ubicación del Post en el disco + # + # @return [String] + def full_path + @full_path ||= File.join(site.path, "_#{locale}", "#{path}.markdown") + end + # Convertir locale a direccionario de PG # # @param [String,Symbol] From b928fa7bde27ecb5b4e9126253dff9a86fa3936b Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 09:56:12 -0300 Subject: [PATCH 04/26] BREAKING CHANGE: las colecciones no generan instancias de document --- app/models/indexed_post.rb | 7 +++++++ app/models/site.rb | 13 +++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index 7e58b7bc..e59e1a2f 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -43,6 +43,13 @@ class IndexedPost < ApplicationRecord @full_path ||= File.join(site.path, "_#{locale}", "#{path}.markdown") end + # La colección + # + # @return [Jekyll::Collection] + def collection + site.collections[locale.to_s] + end + # Convertir locale a direccionario de PG # # @param [String,Symbol] diff --git a/app/models/site.rb b/app/models/site.rb index a8c5e376..690264b4 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -218,17 +218,10 @@ class Site < ApplicationRecord jekyll.data end - # Traer las colecciones. Todos los artículos van a estar dentro de - # colecciones. + # Trae las colecciones desde el sitio, sin leer su contenido + # + # @return [Hash] def collections - unless @read - run_in_path do - jekyll.reader.read_collections - end - - @read = true - end - jekyll.collections end From ae745b3d46b830b55f0d60b7bba4755b9541a682 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 09:57:21 -0300 Subject: [PATCH 05/26] feat: obtener el document a partir del post indexado --- app/models/indexed_post.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index e59e1a2f..5c4b151e 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -50,6 +50,13 @@ class IndexedPost < ApplicationRecord site.collections[locale.to_s] end + # Obtiene el documento + # + # @return [Jekyll::Document] + def document + @document ||= Jekyll::Document.new(full_path, site: site.jekyll, collection: collection) + end + # Convertir locale a direccionario de PG # # @param [String,Symbol] From ecf44311eacb88ed88f0846cd5c67dcb7300fb04 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 09:57:44 -0300 Subject: [PATCH 06/26] feat: obtener el post a partir del post indexado --- app/models/indexed_post.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index 5c4b151e..44a970c3 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -57,6 +57,22 @@ class IndexedPost < ApplicationRecord @document ||= Jekyll::Document.new(full_path, site: site.jekyll, collection: collection) end + # El Post + # + # @todo Decidir qué pasa si el archivo ya no existe + # @return [Post] + def post + @post ||= Post.new(document: document, site: site, layout: schema) + end + + # Devuelve el esquema de datos + # + # @todo Renombrar + # @return [Layout] + def schema + site.layouts[layout.to_sym] + end + # Convertir locale a direccionario de PG # # @param [String,Symbol] From 84419c05f815da0671e40601995237b2071f2bbf Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 09:59:42 -0300 Subject: [PATCH 07/26] feat: obtener valores desde la base de datos #7537 --- app/models/indexed_post.rb | 14 ++++++++++++++ app/models/metadata_template.rb | 4 +++- app/models/site.rb | 18 ------------------ 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index 44a970c3..ef239a7d 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -34,6 +34,20 @@ class IndexedPost < ApplicationRecord scope :in_category, ->(category) { where("front_matter->'categories' ? :category", category: category.to_s) } scope :by_usuarie, ->(usuarie) { where("front_matter->'usuaries' @> :usuarie::jsonb", usuarie: usuarie.to_s) } + # Trae todos los valores únicos para un atributo + # + # @param :attribute [String,Symbol] + # @return [Array] + scope :everything_of, ->(attribute) do + where('front_matter ? :attribute', attribute: attribute) + .pluck( + Arel.sql( + ActiveRecord::Base::sanitize_sql(['front_matter -> :attribute', attribute: attribute]) + ) + ) + .flatten.uniq + end + belongs_to :site # La ubicación del Post en el disco diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 5de54be1..6012fe87 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -74,8 +74,10 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, # Valores posibles, busca todos los valores actuales en otros # artículos del mismo sitio + # + # @return [Array] def values - site.everything_of(name, lang: lang) + site.indexed_posts.everything_of(name) end # Valor actual o por defecto. Al memoizarlo podemos modificarlo diff --git a/app/models/site.rb b/app/models/site.rb index 690264b4..2f0c56bc 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -317,24 +317,6 @@ class Site < ApplicationRecord end end - # Trae todos los valores disponibles para un campo - # - # TODO: Traer recursivamente, si el campo contiene Hash - # - # TODO: Mover a PostRelation#pluck - # - # @param attr [Symbol|String] El atributo a buscar - # @return Array - def everything_of(attr, lang: nil) - Rails.cache.fetch("#{cache_key_with_version}/everything_of/#{lang}/#{attr}", expires_in: 1.hour) do - attr = attr.to_sym - - posts(lang: lang).flat_map do |p| - p[attr].value if p.attribute? attr - end.uniq.compact - end - end - # Poner en la cola de compilación def enqueue! update(status: 'enqueued') if waiting? From 6946e86a025a5729fea1d8b16ac8d1eccd8e89c5 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:00:50 -0300 Subject: [PATCH 08/26] feat: reordenar usando los posts indexados --- app/services/post_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/post_service.rb b/app/services/post_service.rb index 4631a9a4..d59bc8c0 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -74,7 +74,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do # { uuid => 2, uuid => 1, uuid => 0 } def reorder reorder = params.require(:post).permit(reorder: {})&.dig(:reorder)&.transform_values(&:to_i) - posts = site.posts(lang: locale).where(uuid: reorder.keys) + posts = site.indexed_posts(locale: locale).where(post_id: reorder.keys).map(&:post) files = posts.map do |post| next unless post.attribute? :order From ab98119cfeb902ecab150e9b1898fa88890a2b25 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:04:53 -0300 Subject: [PATCH 09/26] =?UTF-8?q?feat:=20encontrar=20el=20post=20a=20edita?= =?UTF-8?q?r=20desde=20su=20indexaci=C3=B3n=20#7537?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/posts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 9720fe13..77354be1 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -160,7 +160,7 @@ class PostsController < ApplicationController end def post - @post ||= site.posts(lang: locale).find(params[:post_id] || params[:id]) + @post ||= site.indexed_posts.find_by!(locale: locale, path: params[:post_id] || params[:id]).post end # Recuerda el nombre del servicio de subida de archivos From d58cb05c7aef36b46f7ff3a1a79803a775f5c688 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:06:28 -0300 Subject: [PATCH 10/26] =?UTF-8?q?feat:=20relacionar=20posts=20desde=20su?= =?UTF-8?q?=20indexaci=C3=B3n=20#7537?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_belongs_to.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/metadata_belongs_to.rb b/app/models/metadata_belongs_to.rb index be1fa670..204d7bed 100644 --- a/app/models/metadata_belongs_to.rb +++ b/app/models/metadata_belongs_to.rb @@ -67,13 +67,17 @@ class MetadataBelongsTo < MetadataRelatedPosts end # El Post relacionado con este artículo + # + # @return [Post,nil] def belongs_to - posts.find(value, uuid: true) if value.present? + posts.find_by(post_id: value)&.post if value.present? end # El artículo relacionado anterior + # + # @return [Post,nil] def belonged_to - posts.find(value_was, uuid: true) if value_was.present? + posts.find_by(post_id: value_was)&.post if value_was.present? end def related_posts? From 95e3defd8b2420a3f3fa82ce5b98fe67fd6cb62b Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:11:30 -0300 Subject: [PATCH 11/26] =?UTF-8?q?feat:=20relacionar=20posts=20por=20su=20i?= =?UTF-8?q?ndexaci=C3=B3n=20#7537?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_has_many.rb | 8 ++++++-- app/models/metadata_related_posts.rb | 26 ++++++++++++++++---------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/models/metadata_has_many.rb b/app/models/metadata_has_many.rb index 13f0dcf5..82ec333a 100644 --- a/app/models/metadata_has_many.rb +++ b/app/models/metadata_has_many.rb @@ -7,17 +7,21 @@ # apuntando a un Post, que se mantiene actualizado como el actual. class MetadataHasMany < MetadataRelatedPosts # Todos los Post relacionados + # + # @return [Array] def has_many return default_value if value.blank? - posts.where(uuid: value) + posts.where(post_id: value).map(&:post) end # La relación anterior + # + # @return [Array] def had_many return default_value if value_was.blank? - posts.where(uuid: value_was) + posts.where(post_id: value_was).map(&:post) end def inverse? diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index 092f219a..b1ebfe4e 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -3,14 +3,16 @@ # Devuelve una lista de títulos y UUID de todos los posts del mismo # idioma que el actual, para usar con input-map.js class MetadataRelatedPosts < MetadataArray - # Genera un Hash de { title | slug => uuid } y excluye el Post actual + # Genera un Hash de { title (schema) => uuid } para usar en + # options_for_select + # # @return [Hash] def values - @values ||= posts.map do |p| - next if p.uuid.value == post.uuid.value - - [title(p), p.uuid.value] - end.compact.to_h + @values ||= posts.pluck(:title, :layout, :post_id).map do |row| + row.tap do |value| + value[0] = "#{value[0]} (#{site.layouts[value.delete_at(1)].humanized_name})" + end + end.to_h end # Las relaciones nunca son privadas @@ -23,21 +25,25 @@ class MetadataRelatedPosts < MetadataArray end def indexable_values - posts.where(uuid: value).map(&:title).map(&:value) + posts.where(post_id: value).pluck(:title) end private - # Obtiene todos los posts y opcionalmente los filtra + # Obtiene todos los posts menos el actual y opcionalmente los filtra + # + # @return [IndexedPost::ActiveRecord_AssociationRelation] def posts - site.posts(lang: lang).where(**filter) + site.indexed_posts.where(locale: locale).where.not(post_id: post.uuid.value).where(**filter) end def title(post) "#{post&.title&.value || post&.slug&.value} (#{post.layout.humanized_name})" end - # Encuentra el filtro + # Encuentra el filtro desde el esquema del atributo + # + # @return [Hash] def filter layout.metadata.dig(name, 'filter')&.to_h&.symbolize_keys || {} end From ea251e5d5b3abfa9d8c0f7140bade9bff79d56f5 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:12:18 -0300 Subject: [PATCH 12/26] =?UTF-8?q?feat:=20indexar=20m=C3=A1s=20atributos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/post/indexable.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index 4e46d7b2..9629efd0 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -40,16 +40,19 @@ class Post private - # Los metadatos que se almacenan como objetos JSON. Empezamos con - # las categorías porque se usan para filtrar en el listado de - # artículos. + # Los metadatos que se almacenan como objetos JSON. # # @return [Hash] def indexable_front_matter {}.tap do |ifm| ifm[:usuaries] = usuaries.map(&:id) ifm[:draft] = attribute?(:draft) ? draft.value : false - ifm[:categories] = categories.indexable_values if attribute? :categories + + indexable_attributes.select do |attr| + self[attr].front_matter? + end.each do |attr| + ifm[attr] = self[attr].indexable_values + end end end From 59828b3f6a735de6d6e82b0e279ae190e943d240 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:13:32 -0300 Subject: [PATCH 13/26] feat: generar un post nuevo --- app/models/post.rb | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index fab9ab06..fff9e17f 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -31,14 +31,34 @@ class Post def find_layout(path) File.foreach(path).lazy.grep(/^layout: /).take(1).first&.split(' ')&.last&.tr('\'', '')&.tr('"', '')&.to_sym end + + # Genera un Post nuevo + # + # @params :site [Site] + # @params :locale [String, Symbol] + # @params :document [Jekyll::Document] + # @params :layout [String,Symbol] + # @return [Post] + def build(**args) + args[:document] ||= + begin + site = args[:site] + collection = site.collections[args[:locale].to_s] + + Jekyll::Document.new('', site: site.jekyll, collection: collection).tap do |doc| + doc.data['date'] = Date.today.to_time + end + end + + Post.new(**args) + end end # Redefinir el inicializador de OpenStruct # - # @param site: [Site] el sitio en Sutty - # @param document: [Jekyll::Document] el documento leído por Jekyll - # @param layout: [Layout] la plantilla - # + # @param :site [Site] el sitio en Sutty + # @param :document [Jekyll::Document] el documento leído por Jekyll + # @param :layout [Layout] la plantilla def initialize(**args) default_attributes_missing(**args) From 1ad4eeccb4e93b5ccaa2ffdfca3f8b1c71f324af Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:14:13 -0300 Subject: [PATCH 14/26] fix: no usar PostRelation para instanciar un nuevo Post #7537 obligaba a cargar casi todo el sitio en memoria y muchas lecturas de disco --- app/controllers/posts_controller.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 77354be1..078e16f8 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -54,9 +54,11 @@ class PostsController < ApplicationController def new authorize Post - @post = site.posts(lang: locale).build(layout: params[:layout]) - breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), '' + layout = site.layouts[params[:layout].to_sym] + @post = Post.build(locale: locale, layout: layout, site: site) + + breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: layout.humanized_name.downcase), '' end def create From 15fa653a6ddd4288283806738a5d706ee643bd14 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:15:13 -0300 Subject: [PATCH 15/26] feat: obtener el post indexado a partir del post --- app/models/post.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index fff9e17f..ab91bd43 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -54,6 +54,11 @@ class Post end end + # @return [IndexedPost,nil] + def indexed_post + site.indexed_posts.find_by(locale: lang.value, path: id) + end + # Redefinir el inicializador de OpenStruct # # @param :site [Site] el sitio en Sutty @@ -307,8 +312,6 @@ class Post def destroy run_callbacks :destroy do FileUtils.rm_f path.absolute - - site.delete_post self end end alias destroy! destroy From 5c8d1df6c7fe3e5d70537b4dc999bc37a8f41ad9 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:15:43 -0300 Subject: [PATCH 16/26] BREAKING CHANGE: deprecar PostRelation --- app/models/post_relation.rb | 156 ------------------------------------ 1 file changed, 156 deletions(-) delete mode 100644 app/models/post_relation.rb diff --git a/app/models/post_relation.rb b/app/models/post_relation.rb deleted file mode 100644 index 531d3cc4..00000000 --- a/app/models/post_relation.rb +++ /dev/null @@ -1,156 +0,0 @@ -# frozen_string_literal: true - -# La relación de un sitio con sus artículos, esto nos permite generar -# artículos como si estuviésemos usando ActiveRecord. -class PostRelation < Array - # No necesitamos cambiar el sitio - attr_reader :site, :lang - - def initialize(site:, lang:) - @site = site - @lang = lang - # Proseguimos la inicialización sin valores por defecto - super() - end - - # Genera un artículo nuevo con los parámetros que le pasemos y lo suma - # al array - def build(**args) - args[:lang] = lang - args[:document] ||= build_document(collection: args[:lang]) - args[:layout] = build_layout(args[:layout]) - - post = Post.new(site: site, **args) - - self << post - post - end - - def create(**args) - post = build(**args) - post.save - post - end - - alias sort_by_generic sort_by - alias sort_by_generic! sort_by! - - # Permite ordenar los artículos por sus atributos - # - # XXX: Prestar atención cuando estamos mezclando artículos con - # diferentes tipos de atributos. - def sort_by(*attrs) - sort_by_generic do |post| - attrs.map do |attr| - # TODO: detectar el tipo de atributo faltante y obtener el valor - # por defecto para hacer la comparación - if post.attributes.include? attr - post.public_send(attr).value - else - 0 - end - end - end - end - - def sort_by!(*attrs) - replace sort_by(*attrs) - end - - alias find_generic find - - # Encontrar un post por su UUID - def find(id, uuid: false) - find_generic do |p| - if uuid - p.uuid.value == id - else - p.id == id - end - end - end - - # Encuentra el primer post por el valor de los atributos - # - # @param [Hash] - # @return [Post] - def find_by(**args) - find_generic do |post| - args.map do |attr, value| - post.attribute?(attr) && - post.public_send(attr).value == value - end.all? - end - end - - # Encuentra todos los Post que cumplan las condiciones - # - # TODO: Implementar caché - # - # @param [Hash] Mapa de atributo => valor. Valor puede ser un Array - # de valores - # @return [PostRelation] - def where(**args) - return self if args.empty? - - begin - PostRelation.new(site: site, lang: lang).concat(select do |post| - result = args.map do |attr, value| - next unless post.attribute?(attr) - - attribute = post[attr] - - # TODO: Si el valor del atributo también es un Array deberíamos - # cruzar ambas. - case value - when Array then value.include? attribute.value - else - case attribute.value - when Array then attribute.value.include? value - else attribute.value == value - end - end - end.compact - - # Un Array vacío devuelve true para all? - result.present? && result.all? - end) - end - end - - # Como Array#select devolviendo una relación - # - # @return [PostRelation] - alias array_select select - def select(&block) - PostRelation.new(site: site, lang: lang).concat array_select(&block) - end - - # Intenta guardar todos y devuelve true si pudo - def save_all(validate: true) - map do |post| - post.save(validate: validate) - end.all? - end - - private - - def build_layout(layout = nil) - return layout if layout.is_a? Layout - - site.layouts[layout&.to_sym || :post] - end - - # Devuelve una colección Jekyll que hace pasar el documento - def build_collection(label:) - Jekyll::Collection.new(site.jekyll, label.to_s) - end - - # Un documento borrador con algunas propiedades por defecto - def build_document(collection:) - col = build_collection(label: collection) - doc = Jekyll::Document.new('', site: site.jekyll, collection: col) - doc.data['date'] = Date.today.to_time - doc - end -end From d4f85da0188ba77c483f943f8558cd409e399a51 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:15:57 -0300 Subject: [PATCH 17/26] feat: detectar si un post existe en disco --- app/models/indexed_post.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index ef239a7d..02679ec0 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -87,6 +87,13 @@ class IndexedPost < ApplicationRecord site.layouts[layout.to_sym] end + # Existe físicamente? + # + # @return [Boolean] + def exist? + File.exist?(full_path) + end + # Convertir locale a direccionario de PG # # @param [String,Symbol] From b2e6b76870b2c7dc4088f4ec0fa576cc48dd5fea Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:16:47 -0300 Subject: [PATCH 18/26] fix: no usar post relation --- app/services/post_service.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/services/post_service.rb b/app/services/post_service.rb index d59bc8c0..0d989871 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -7,8 +7,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do # # @return Post def create - self.post = site.posts(lang: locale) - .build(layout: layout) + self.post = Post.build(site: site, locale: locale, layout: layout) post.usuaries << usuarie params[:post][:draft] = true if site.invitade? usuarie @@ -29,8 +28,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do def create_anonymous # XXX: Confiamos en el parámetro de idioma porque estamos # verificándolos en Site#posts - self.post = site.posts(lang: locale) - .build(layout: layout) + self.post = Post.build(site: site, locale: locale, layout: layouts) # Los artículos anónimos siempre son borradores params[:draft] = true @@ -118,12 +116,16 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do end end + # @return [Symbol] def locale params.dig(:post, :lang)&.to_sym || I18n.locale end + # @return [Layout] def layout - params.dig(:post, :layout) || params[:layout] + site.layouts[ + (params.dig(:post, :layout) || params[:layout]).to_sym + ] end # Actualiza los artículos relacionados según los métodos que los From 7df1af2a5400e646218b5d00b3a22fa78a9e3e0a Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:17:26 -0300 Subject: [PATCH 19/26] BREAKING CHANGE: eliminar Site#posts --- app/models/site.rb | 50 ---------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/app/models/site.rb b/app/models/site.rb index 2f0c56bc..3ccd27d3 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -230,55 +230,6 @@ class Site < ApplicationRecord @config ||= Site::Config.new(self) end - # Los posts en el idioma actual o en uno en particular - # - # @param lang: [String|Symbol] traer los artículos de este idioma - def posts(lang: nil) - # Traemos los posts del idioma actual por defecto o el que haya - lang ||= locales.include?(I18n.locale) ? I18n.locale : default_locale - lang = lang.to_sym - - # Crea un Struct dinámico con los valores de los locales, si - # llegamos a pasar un idioma que no existe vamos a tener una - # excepción NoMethodError - @posts ||= Struct.new(*locales).new - - return @posts[lang] unless @posts[lang].blank? - - @posts[lang] = PostRelation.new site: self, lang: lang - - # No fallar si no existe colección para este idioma - # XXX: queremos fallar silenciosamente? - (collections[lang.to_s]&.docs || []).each do |doc| - layout = layouts[Post.find_layout(doc.path)] - - @posts[lang].build(document: doc, layout: layout, lang: lang) - rescue TypeError => e - ExceptionNotifier.notify_exception(e, data: { site: name, site_id: id, path: doc.path }) - end - - @posts[lang] - end - - # Todos los Post del sitio para poder buscar en todos. - # - # @return PostRelation - def docs - @docs ||= PostRelation.new(site: self, lang: :docs).push(locales.flat_map do |locale| - posts(lang: locale) - end).flatten! - end - - # Elimina un artículo de la colección - def delete_post(post) - lang = post.lang.value - - collections[lang.to_s].docs.delete(post.document) && - posts(lang: lang).delete(post) - - post - end - # Obtiene todas las plantillas de artículos # # @return [Hash] { post: Layout } @@ -433,7 +384,6 @@ class Site < ApplicationRecord @incompatible_layouts = nil @jekyll = nil @config = nil - @posts = nil @docs = nil end From 6f9fe8fdbc5f627753a887a160b1ee74bb2474b5 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:18:29 -0300 Subject: [PATCH 20/26] fix: encontrar la licencia por el post indexado --- app/services/site_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/site_service.rb b/app/services/site_service.rb index 7022244c..9d748f8f 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -144,7 +144,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do return true if site.licencia.custom? with_all_locales do |locale| - post = site.posts(lang: locale).find_by(layout: 'license') + post = site.indexed_posts(locale: locale).find_by(layout: 'license')&.post change_licencia(post: post) if post end.compact.map(&:valid?).all? From 9cb2850a13eefbbc1566d893108ffc4c870a3760 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 10:19:25 -0300 Subject: [PATCH 21/26] fix: encontrar el post relacionado por su indexacion --- app/views/posts/attribute_ro/_related_posts.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/attribute_ro/_related_posts.haml b/app/views/posts/attribute_ro/_related_posts.haml index c43b589e..6014e160 100644 --- a/app/views/posts/attribute_ro/_related_posts.haml +++ b/app/views/posts/attribute_ro/_related_posts.haml @@ -3,7 +3,7 @@ %td %ul{ dir: dir, lang: locale } - metadata.value.each do |v| - - p = site.posts(lang: post.lang.value).find(v, uuid: true) + - p = site.indexed_posts(locale: post.lang.value).find_by(post_id: v)&.post -# XXX: Ignorar todos los posts no encontrados (ej: fueron borrados o el uuid cambió) From ad3d52e2e78ce58cb100728aa27b1ba6b159e7b6 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 17:23:55 -0300 Subject: [PATCH 22/26] fix: usar posts indexados para relacionar traducciones --- app/models/metadata_locales.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/models/metadata_locales.rb b/app/models/metadata_locales.rb index 37b50286..85419da8 100644 --- a/app/models/metadata_locales.rb +++ b/app/models/metadata_locales.rb @@ -33,17 +33,15 @@ class MetadataLocales < MetadataHasAndBelongsToMany # # @return [Array] def other_locales - site.locales.reject do |locale| - locale == post.lang.value.to_sym + @other_locales ||= site.locales.reject do |other_locale| + other_locale.to_s == locale end end # Obtiene todos los posts de los otros locales con el mismo layout # - # @return [PostRelation] + # @return [IndexedPost::ActiveRecord_AssociationRelation] def posts - other_locales.map do |locale| - site.posts(lang: locale).where(layout: post.layout.value) - end.reduce(&:concat) || PostRelation.new(site: site, lang: 'any') + site.indexed_posts(locale: other_locales).where(layout: post.layout.value).where.not(post_id: post.uuid.value) end end From 5b9df0d02013ee0b3d7ffce212390256e950e0e8 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 17:30:37 -0300 Subject: [PATCH 23/26] fix: locales --- app/models/metadata_locales.rb | 19 +++++++++++-------- app/models/metadata_related_posts.rb | 8 ++------ app/views/posts/attributes/_locales.haml | 1 - 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/app/models/metadata_locales.rb b/app/models/metadata_locales.rb index 85419da8..b4ddf485 100644 --- a/app/models/metadata_locales.rb +++ b/app/models/metadata_locales.rb @@ -6,11 +6,16 @@ class MetadataLocales < MetadataHasAndBelongsToMany # # @return { lang: { title: uuid } } def values - @values ||= site.locales.map do |locale| - [locale, posts.where(lang: locale).map do |post| - [title(post), post.uuid.value] - end.to_h] - end.to_h + @values ||= other_locales.to_h do |other_locale| + [ + other_locale, + posts.where(locale: other_locale).pluck(:title, :layout, :post_id).to_h do |row| + row.tap do |value| + value[0] = "#{value[0]} (#{site.layouts[value.delete_at(1)].humanized_name})" + end + end + ] + end end # Siempre hay una relación inversa @@ -33,9 +38,7 @@ class MetadataLocales < MetadataHasAndBelongsToMany # # @return [Array] def other_locales - @other_locales ||= site.locales.reject do |other_locale| - other_locale.to_s == locale - end + @other_locales ||= site.locales - [locale] end # Obtiene todos los posts de los otros locales con el mismo layout diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index b1ebfe4e..50b8660e 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -8,11 +8,11 @@ class MetadataRelatedPosts < MetadataArray # # @return [Hash] def values - @values ||= posts.pluck(:title, :layout, :post_id).map do |row| + @values ||= posts.pluck(:title, :layout, :post_id).to_h do |row| row.tap do |value| value[0] = "#{value[0]} (#{site.layouts[value.delete_at(1)].humanized_name})" end - end.to_h + end end # Las relaciones nunca son privadas @@ -37,10 +37,6 @@ class MetadataRelatedPosts < MetadataArray site.indexed_posts.where(locale: locale).where.not(post_id: post.uuid.value).where(**filter) end - def title(post) - "#{post&.title&.value || post&.slug&.value} (#{post.layout.humanized_name})" - end - # Encuentra el filtro desde el esquema del atributo # # @return [Hash] diff --git a/app/views/posts/attributes/_locales.haml b/app/views/posts/attributes/_locales.haml index 4978f6b4..05592fbd 100644 --- a/app/views/posts/attributes/_locales.haml +++ b/app/views/posts/attributes/_locales.haml @@ -6,7 +6,6 @@ post: post, attribute: attribute, metadata: metadata - site.locales.each do |locale| - - next if post.lang.value == locale - locale_t = t("locales.#{locale}.name", default: locale.to_s.humanize) - value = metadata.value.find do |v| - metadata.values[locale].values.include? v From 35e4ef045d8710a7b2200254d523f30dc0315f18 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 17:33:21 -0300 Subject: [PATCH 24/26] fix: usar el orden indexado --- app/models/metadata_order.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_order.rb b/app/models/metadata_order.rb index 1b33a388..577e3c85 100644 --- a/app/models/metadata_order.rb +++ b/app/models/metadata_order.rb @@ -5,7 +5,7 @@ class MetadataOrder < MetadataTemplate # El valor según la posición del post en la relación ordenada por # fecha, a fecha más alta, posición más alta def default_value - super || site.posts(lang: lang).sort_by(:date).index(post) + super || (site.indexed_posts(locale: locale).first.order + 1) end def save From c241f251056319acda6e6b7439bed4cda133d81c Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 17:34:30 -0300 Subject: [PATCH 25/26] fix: deprecar lang en favor de locale --- app/models/metadata_template.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 6012fe87..a56f0bad 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -62,12 +62,15 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, end # Trae el idioma actual del sitio o del panel + # + # @deprecated Empezar a usar locale # @return [String] def lang @lang ||= post&.lang&.value || I18n.locale end - # El valor por defecto + alias_method :locale, :lang + def default_value layout.metadata.dig(name, 'default', lang.to_s) end From 833022c9c4d901634f2a431627060ea1754ad6ea Mon Sep 17 00:00:00 2001 From: f Date: Fri, 6 Oct 2023 17:35:54 -0300 Subject: [PATCH 26/26] fix: MetadataTemplate#locale es una String --- app/models/metadata_template.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index a56f0bad..6e13aecd 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -66,13 +66,16 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, # @deprecated Empezar a usar locale # @return [String] def lang - @lang ||= post&.lang&.value || I18n.locale + @lang ||= post&.lang&.value || I18n.locale.to_s end alias_method :locale, :lang + # El valor por defecto desde el esquema de datos + # + # @return [any] def default_value - layout.metadata.dig(name, 'default', lang.to_s) + layout.metadata.dig(name, 'default', lang) end # Valores posibles, busca todos los valores actuales en otros