From 86ae7fb8f81bb9cb74c7a76215cbef49624e0d09 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 12:52:30 -0300 Subject: [PATCH] los posts pueden ser indexados MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `IndexedPost` es una representación indexada por PG de `Post`. ambos están relacionados por el UUID de `Post`, de forma que se puede traer el artículo completo (por ejemplo al previsualizar o editar). cada artículo está indexado según su idioma. para eso convertimos el locale en el equivalente en el diccionario de PG. `Site#index_posts` es un método para indexar todos los artículos en masa. `Post#to_index` genera el `IndexedPost` correspondiente `IndexedPost.search(:es, 'hola')` busca "hola" en todos los artículos utilizando el diccionario de castellano. esto no quiere decir que busque en todos los artículos en castellano. por ahora para eso hay que hacer algo como: ```ruby site = Site.find 1 site.indexed_posts.where(locale: :english).search(:en, 'hello') ``` para encontrar todos los artículos en inglés del sitio con id 1 --- app/models/indexed_post.rb | 38 ++++++++++++++++++++++++ app/models/post.rb | 4 +++ app/models/post/indexable.rb | 56 ++++++++++++++++++++++++++++++++++++ app/models/site.rb | 2 ++ 4 files changed, 100 insertions(+) create mode 100644 app/models/indexed_post.rb create mode 100644 app/models/post/indexable.rb diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb new file mode 100644 index 00000000..6456a824 --- /dev/null +++ b/app/models/indexed_post.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# La representación indexable de un artículo +class IndexedPost < ApplicationRecord + include PgSearch::Model + + # La traducción del locale según Sutty al locale según PostgreSQL + DICTIONARIES = { + es: 'spanish', + en: 'english' + }.freeze + + # TODO: Los indexed posts tienen que estar scopeados al idioma actual, + # no buscar sobre todos + pg_search_scope :search, + lambda { |locale, query| + { + against: :content, + query: query, + using: { + tsearch: { + dictionary: IndexedPost.to_dictionary(locale: locale), + tsvector_column: 'indexed_content' + } + } + } + } + + belongs_to :site + + # Convertir locale a direccionario de PG + # + # @param [String,Symbol] + # @return [String] + def self.to_dictionary(locale:) + DICTIONARIES[locale.to_sym] || 'simple' + end +end diff --git a/app/models/post.rb b/app/models/post.rb index a0e16706..6ef7a2ec 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -17,6 +17,8 @@ class Post attr_reader :attributes, :errors, :layout, :site, :document + include Post::Indexable + class << self # Obtiene el layout sin leer el Document # @@ -191,6 +193,8 @@ class Post post: self, required: true) end + alias locale lang + # TODO: Mover a method_missing def uuid @metadata[:uuid] ||= MetadataUuid.new(document: document, site: site, layout: layout, name: :uuid, type: :uuid, diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb new file mode 100644 index 00000000..f25f28d2 --- /dev/null +++ b/app/models/post/indexable.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +class Post + # Vuelve indexables a los Posts + module Indexable + extend ActiveSupport::Concern + + included do + # Devuelve una versión indexable del Post + # + # @return [IndexedPosts] + def to_index + @to_index ||= IndexedPost.find_or_create_by(id: uuid.value).tap do |indexed_post| + indexed_post.layout = layout.name + indexed_post.site_id = site.id + indexed_post.path = path.basename + indexed_post.locale = IndexedPost.to_dictionary(locale: locale.value) + indexed_post.title = title.value + indexed_post.front_matter = indexable_front_matter + indexed_post.content = indexable_content + end + end + + 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. + # + # @return [Hash] + def indexable_front_matter + return {} unless attribute? :categories + + { categories: categories.indexable_values } + end + + # Devuelve un documento indexable en texto plano + # + # XXX: No memoizamos para permitir actualizaciones, aunque + # probablemente se indexe una sola vez. + # + # @return [String] + def indexable_content + indexable_attributes.map do |attr| + self[attr].to_s + end.join("\n") + end + + def indexable_attributes + @indexable_attributes ||= attributes.select do |attr| + self[attr].indexable? + end + end + end + end +end diff --git a/app/models/site.rb b/app/models/site.rb index 1c62e9bc..14238e1a 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -7,6 +7,7 @@ class Site < ApplicationRecord include Site::Forms include Site::FindAndReplace include Site::Api + include Site::Index include Tienda # Cifrar la llave privada que cifra y decifra campos ocultos. Sutty @@ -37,6 +38,7 @@ class Site < ApplicationRecord belongs_to :design belongs_to :licencia + has_many :indexed_posts, dependent: :destroy has_many :log_entries, dependent: :destroy has_many :deploys, dependent: :destroy has_many :build_stats, through: :deploys